config

package module
v0.0.0-...-3488a8a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 6, 2026 License: MIT Imports: 11 Imported by: 0

README

lib-config

CI codecov Go Report Card Go version License

Строгая, типобезопасная библиотека загрузки конфигурации для Go-сервисов в организации selfshop-dev.

Конфигурация загружается из двух источников: хардкод-дефолты (map[string]any) и переменные окружения с заданным префиксом. Неизвестные ключи и несоответствия типов отклоняются при старте — ошибки конфигурации никогда не замалчиваются.

Возможности

  • Два источника: дефолты + env-переменные. Никаких файлов, никаких сторонних хранилищ.
  • Строгая типизация: WeaklyTypedInput=false"1" там, где ожидается int, вернёт ошибку.
  • Неизвестные ключи отклоняются до декодирования — опечатки в именах env-переменных видны сразу.
  • Явные теги обязательны: каждое экспортируемое поле struct обязано иметь тег koanf:"...".
  • Трёхфазная валидация: struct-теги (go-playground/validator) → кросс-поля (Validate() error) → decode-time (ErrorUnused).
  • Decode-хуки: time.Duration из строки ("1h30m"), time.Time в RFC3339, []string из CSV без пустых элементов.

Установка

go get github.com/selfshop-dev/lib-config

Использование

Минимальный пример
package main

import (
    "log"

    config "github.com/selfshop-dev/lib-config"
)

type Config struct {
    Host string `koanf:"host" validate:"required"`
    Port int    `koanf:"port" validate:"required,min=1024,max=65535"`
}

func main() {
    cfg, err := config.New[Config]("APP", map[string]any{
        "host": "localhost",
        "port": 8080,
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("listening on %s:%d", cfg.Host, cfg.Port)
}
Переменные окружения

Двойное подчёркивание __ используется как разделитель иерархии:

APP__HOST=db.internal    # -> host
APP__LOG__MIN_LEVEL=info # -> log.min_level
APP__DB__PORT=5432       # -> db.port

Префикс нормализуется автоматически: APP и APP_ эквивалентны.

Встроенная базовая конфигурация

Base — готовая struct с общими полями для всех сервисов организации. Встраивается с koanf:",squash":

type Config struct {
    config.Base `koanf:",squash"`
    DB          DBConfig `koanf:"db" validate:"required"`
}

Поля Base:

Ключ Тип Описание
app.name string Имя сервиса
app.runmode string dev или prod
log.min_level string debug, info, warn, error, panic, fatal
log.format string json, console, auto
entry.http.port uint16 TCP-порт (1024–65535)
entry.http.read_timeout duration 5s–60s
entry.http.request_timeout duration 10s–120s
entry.http.write_timeout duration 5s–90s
entry.http.idle_timeout duration 30s–180s
debug bool Режим отладки (только для dev)
Кросс-поля валидация

Реализуйте Validate() error на pointer-ресивере для правил, которые нельзя выразить struct-тегами:

func (c *Config) Validate() error {
   if c.TLS && c.TLSCert == "" {
      return errors.New("tls_cert must be set when tls is enabled")
   }
   return nil
}

Соглашение по тегам

Тег Поведение
koanf:"name" Скалярный ключ
koanf:"sub" Вложенная struct
koanf:",squash" Поля flatten-ятся в родительское пространство
koanf:"-" Поле исключается из загрузки конфига

Поле без тега — ошибка при инициализации.

Разработка

make lint   # golangci-lint run
make test   # go test -race -coverprofile=coverage.out ./...
Ветки
main ← только из dev через PR
dev  ← рабочая ветка, PR-ы сюда
Соглашение по коммитам

Проект следует Conventional Commits. Формат: <type>: <summary>, где summary — повелительное наклонение, английский язык, без точки в конце.

Тип Когда использовать
feat Новая функциональность
fix Исправление бага
refactor Рефакторинг без изменения поведения
perf Оптимизация производительности
test Добавление или изменение тестов
docs Документация
ci Изменения CI/CD и GitHub Actions
build Сборка, зависимости, инфраструктура проекта
chore Рутинные задачи, не попадающие в другие типы

Тип коммита автоматически определяет метку PR и тип следующего релиза: feat → minor, fix / perf / refactor → patch, feat! / fix! (breaking change) → major.

CI/CD

push / PR → lint → test → codecov
                        → CodeQL (безопасность)
weekly     → Trivy (уязвимости в зависимостях)
merge→main → release-drafter (обновляет черновик релиза)

Покрытие ниже 80% или падение линтера блокируют merge.

Лицензия

MIT © 2026 selfshop-dev

Documentation

Overview

Package config provides a strict, type-safe configuration loader that sources values from hardcoded defaults and environment variables.

Design philosophy

This package is an opinionated infra-layer tool, not a general-purpose configuration library. Every decision prioritises catching misconfiguration at startup rather than silently accepting a degraded runtime state.

Key properties:

  • Two sources only: defaults (map[string]any) + env vars. No file sources. In containerised / twelve-factor deployments env vars are the standard runtime knob; files add operational complexity (mounts, permissions, drift).
  • Strict typing: WeaklyTypedInput is false. Accepting "1" where int is expected hides bugs; explicit conversion must happen at the source.
  • Unknown keys are rejected before decoding. A typo in an env var name is caught immediately with a clear error instead of being silently ignored.
  • Every exported struct field must carry an explicit koanf tag. Relying on lowercase fallback names is fragile and hides intent.
  • Cross-field validation is supported via an optional Validate() error method on *T (pointer receiver).

Environment variable naming

Double-underscore (__) is used as a hierarchy separator because a single underscore is too common inside segment names (e.g. DB_MAX_CONNS would be ambiguous). Examples with prefix "APP_":

APP__DEBUG=true             -> debug
APP__DB__HOST=localhost     -> db.host
APP__ENTRY__HTTP__PORT=8080 -> entry.http.port

The prefix is normalised internally: "APP" and "APP_" are equivalent.

Struct tag contract

All exported fields must define a koanf tag explicitly:

Name    string `koanf:"name"`    // scalar leaf
Sub     Inner  `koanf:"sub"`     // nested struct
Base           `koanf:",squash"` // flatten into parent namespace
ignored string `koanf:"-"`       // excluded from config loading

Only the "squash" option is supported. Other mapstructure options (omitempty, remain, etc.) are not meaningful in an env-only strict config layer and are intentionally not supported.

Validation

Validation runs in three phases:

  1. Struct tag validation via go-playground/validator (e.g. `validate:"required,min=1"`).
  2. Cross-field semantic validation via Validate() error on *T, if implemented. Use this for constraints that cannot be expressed with struct tags, such as "TLSCert must be set when TLS is enabled". Validate() must be implemented on a pointer receiver.
  3. errors.Join is recommended inside Validate() to surface all errors at once rather than stopping at the first failure.

Index

Constants

View Source
const (
	AppRunmodeDev  = "dev"
	AppRunmodeProd = "prod"
)

Runmode constants define the supported application run modes. They are intentionally kept as untyped string constants so that they can be compared directly against the string value decoded from config without a type conversion.

View Source
const (
	LogFormatJSON    = "json"
	LogFormatConsole = "console"
	// LogFormatAuto resolves to [LogFormatConsole] in dev runmode and to
	// [LogFormatJSON] in prod runmode. Resolution happens at runtime via
	// [Base.GetLogFormat]; the raw "auto" value is never passed to the logger.
	LogFormatAuto = "auto"
)

Log format constants define the supported log output formats.

View Source
const (
	LogMinLevelDebug = "debug"
	LogMinLevelInfo  = "info"
	LogMinLevelWarn  = "warn"
	LogMinLevelError = "error"
	LogMinLevelPanic = "panic"
	LogMinLevelFatal = "fatal"
)

Log level constants define the supported minimum log severity levels in ascending order of severity.

View Source
const MinUnprivilegedPort = 1024

MinUnprivilegedPort is the lowest TCP/UDP port number that can be bound without root (CAP_NET_BIND_SERVICE) privileges on Linux. Used as the lower bound in HTTP.Port validation.

Variables

This section is empty.

Functions

func New

func New[T any](envPrefix string, defaults map[string]any) (*T, error)

New loads, validates, and returns a fully-populated config struct of type T.

Loading order (last writer wins):

  1. Hardcoded defaults supplied by the caller.
  2. Environment variables with the given prefix.

Both sources are subject to the same unknown-key guard: a key present in defaults that does not map to a field in T is rejected just like a bad env var.

T must be a struct. All exported fields must carry an explicit koanf tag; see package documentation for the full tag contract.

envPrefix is normalised internally: trailing underscores are stripped and exactly one is appended, so "APP" and "APP_" are equivalent.

Types

type App

type App struct {
	// Name is the human-readable service identifier used in logs and traces.
	Name string `koanf:"name" validate:"required"`
	// Runmode controls environment-specific behaviour such as log format
	// resolution and debug-mode gating. Must be one of [AppRunmodeDev] or [AppRunmodeProd].
	Runmode string `koanf:"runmode" validate:"required,oneof=dev prod"`
}

App holds identity and runtime-mode configuration for the service.

type Base

type Base struct {
	App   App   `koanf:"app"   validate:"required"`
	Log   Log   `koanf:"log"   validate:"required"`
	Entry Entry `koanf:"entry" validate:"required"`
	// Debug enables verbose developer tooling such as stack traces and
	// human-readable log output. When true:
	//   - runmode must be "dev" (debug in prod is forbidden by [Base.Validate])
	//   - log.min_level must be "debug"
	Debug bool `koanf:"debug"`
}

Base holds the configuration fields shared by every service. It is intended to be embedded with koanf:",squash" so that its fields appear at the root of the config namespace rather than under a "base." prefix.

func (Base) GetLogFormat

func (b Base) GetLogFormat() string

GetLogFormat resolves the effective log format, expanding LogFormatAuto to a concrete value based on the current runmode. All other format values are returned as-is. Use this method when constructing the logger - never read Log.Format directly.

func (Base) IsDev

func (b Base) IsDev() bool

IsDev reports whether the application is running in development mode.

func (Base) IsProd

func (b Base) IsProd() bool

IsProd reports whether the application is running in production mode.

func (Base) Validate

func (b Base) Validate() error

Validate enforces cross-field constraints that cannot be expressed with struct tags alone. It is called automatically by New as part of semantic validation phase.

Rules:

  • Debug mode is forbidden in prod runmode: it exposes internals and degrades performance.
  • When debug is enabled, log.min_level must be "debug": any higher level would silently swallow the debug output that debug mode is meant to show.

type Entry

type Entry struct {
	HTTP HTTP `koanf:"http" validate:"required"`
}

Entry holds inbound traffic entrypoint configuration.

type HTTP

type HTTP struct {
	// Port is the TCP port the server listens on.
	// Must be in the unprivileged range [1024, 65535].
	Port uint16 `koanf:"port" validate:"required,min=1024,max=65535"`
	// WriteTimeout is the maximum time to write a complete response.
	// Must be greater than RequestTimeout.
	WriteTimeout time.Duration `koanf:"write_timeout" validate:"required,gte=5s,lte=90s,gtfield=RequestTimeout"`
	// ReadTimeout is the maximum time to read a complete request including body.
	ReadTimeout time.Duration `koanf:"read_timeout" validate:"required,gte=5s,lte=60s"`
	// IdleTimeout is the maximum time to keep an idle keep-alive connection open.
	// Must be greater than RequestTimeout.
	IdleTimeout time.Duration `koanf:"idle_timeout" validate:"required,gte=30s,lte=180s,gtfield=RequestTimeout"`
	// RequestTimeout is the context deadline injected into each request handler.
	// Must be greater than ReadTimeout so the handler has a meaningful execution
	// budget after the full request has been read.
	RequestTimeout time.Duration `koanf:"request_timeout" validate:"required,gte=10s,lte=120s,gtfield=ReadTimeout"`
}

HTTP holds configuration for the HTTP server entrypoint.

Timeout ordering is strictly enforced:

ReadTimeout < RequestTimeout < WriteTimeout < IdleTimeout

Rationale for each constraint:

ReadTimeout    5s–60s   - time to read the full request including body.
                          A low ceiling prevents slow-loris attacks.
RequestTimeout 10s–120s - context deadline injected into each handler.
                          Must exceed ReadTimeout so the handler has a
                          meaningful budget after the request is fully read.
WriteTimeout   5s–90s   - time to write the full response.
                          Must exceed RequestTimeout so the server can
                          flush the response after the handler completes.
IdleTimeout    30s–180s - time to keep an idle keep-alive connection open.
                          Must exceed RequestTimeout so that a connection
                          is not closed while a request is still in flight.

type Log

type Log struct {
	// MinLevel is the minimum severity level at which log entries are emitted.
	// Entries below this level are discarded. Must be one of the LogMinLevel* constants.
	MinLevel string `koanf:"min_level" validate:"required,oneof=debug info warn error panic fatal"`
	// Format controls the log output encoding.
	// Use [LogFormatAuto] to let runmode decide; use [Base.GetLogFormat] to read
	// the resolved value.
	Format string `koanf:"format" validate:"required,oneof=json auto console"`
}

Log holds logging configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL