Help us shape the future of Go! Take the 2026 Go Developer Survey

config

package module
v0.0.0-...-e49c7c4 Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: MIT Imports: 14 Imported by: 2

README

Config

Config — это пакет для управления конфигурацией в Go-приложениях. Пакет предоставляет гибкий механизм загрузки конфигурации из различных источников (YAML, JSON, переменные окружения, in-memory карты), автоматическое слияние конфигураций от нескольких источников, интерполяцию шаблонов и преобразование типов.

Возможности

  • Загрузка конфигурации из YAML, JSON, переменных окружения и in-memory карт
  • Глубокое слияние конфигураций от нескольких источников
  • Интерполяция шаблонов с использованием синтаксиса Go text/template
  • Автоматическое преобразование типов (строки в числа, время, срезы)
  • Потокобезопасный доступ к конфигурации
  • Маппинг значений в структуры через тег config
  • Без учета регистра для ключей

Установка

go get github.com/shuldan/config

Быстрый старт

Простейший пример использования:

package main

import (
    "fmt"
    "github.com/shuldan/config"
)

func main() {
    cfg, err := config.Load(
        config.FromYAML("config.yaml"),
    )
    if err != nil {
        panic(err)
    }

    host, _ := config.Get[string](cfg, "server.host")
    port, _ := config.Get[int](cfg, "server.port")

    fmt.Printf("Server: %s:%d\n", host, port)
}

Загрузчики

Пакет предоставляет несколько загрузчиков для разных источников конфигурации.

YAML

Загрузка конфигурации из YAML-файла:

cfg, err := config.Load(config.FromYAML("config.yaml"))

Поддерживаются вложенные структуры:

server:
  host: localhost
  port: 8080
  timeout: 30s
JSON

Загрузка конфигурации из JSON-файла:

cfg, err := config.Load(config.FromJSON("config.json"))
Переменные окружения

Загрузка конфигурации из переменных окружения с автоматической группировкой по префиксу:

cfg, err := config.Load(config.FromENV("APP_"))

Для переменных вида APP_SERVER_PORT=8080 создается вложенная структура:

map[string]any{
    "server": map[string]any{
        "port": "8080",
    },
}

По умолчанию используется разделитель _. Если в именах переменных используется подчеркивание как часть имени, используйте двойной разделитель __ между частями ключей:

cfg, err := config.Load(config.FromENVWithSeparator("APP_", "__"))

Для переменной APP_DATABASE__CONNECTION_STRING=postgres://... создастся:

map[string]any{
    "database": map[string]any{
        "connection_string": "postgres://...",
    },
}
In-memory карта

Создание конфигурации из in-memory карты (удобно для тестов или значений по умолчанию):

defaults := map[string]any{
    "server": map[string]any{
        "port": 8080,
        "host": "localhost",
    },
}
cfg, err := config.Load(config.FromMap(defaults))
Несколько источников

Можно загрузить конфигурацию из нескольких источников. Последующие источники переопределяют предыдущие:

cfg, err := config.Load(
    config.FromYAML("config.yaml"),      // базовые значения
    config.FromJSON("config.local.json"),    // переопределяет YAML
    config.FromENV("APP_"),             // переопределяет JSON
)

Получение значений

Get

Функция Get возвращает типизированное значение из конфигурации:

// получение строки
host, err := config.Get[string](cfg, "server.host")

// получение целого числа
port, err := config.Get[int](cfg, "server.port")

// получение числа с плавающей точкой
rate, err := config.Get[float64](cfg, "server.rate")

// получение булева значения
enabled, err := config.Get[bool](cfg, "feature.enabled")

Второй необязательный параметр — значение по умолчанию:

port, err := config.Get[int](cfg, "server.port", 8080)

Если ключ не существует, возвращается значение по умолчанию. Если значение по умолчанию не указано, возвращается ошибка KeyNotFoundError.

MustGet

Функция MustGet аналогична Get, но вызывает панику при отсутствии ключа или ошибке преобразования:

port := config.MustGet[int](cfg, "server.port")

Используется для обязательных значений конфигурации при инициализации приложения.

Интерфейс any

Для получения значения без преобразования типа используйте any:

value, err := config.Get[any](cfg, "server")

Маппинг в структуру

Unmarshal

Функция Unmarshal маппит поддерево конфигурации в структуру и возвращает значение:

type ServerConfig struct {
    Host    string        `config:"host"`
    Port    int           `config:"port"`
    Timeout time.Duration `config:"timeout"`
}

server, err := config.Unmarshal[ServerConfig](cfg, "server")

Поле структуры сопоставляется с ключом конфигурации по тегу config. Если тег не указан, используется имя поля в нижнем регистре.

Пример конфигурации:

server:
  host: localhost
  port: 8080
  timeout: 30s

Пример кода для всего файла:

type Config struct {
    Server ServerConfig `config:"server"`
}

result, err := config.Unmarshal[Config](cfg, "")
// или без ключа для корневого уровня

Если cfg равен nil, Unmarshal возвращает zero-значение типа и ошибку KeyNotFoundError.

Поле структуры сопоставляется с ключом конфигурации по тегу config. Если тег не указан, используется имя поля в нижнем регистре.

Пример конфигурации:

server:
  host: localhost
  port: 8080
  timeout: 30s

Пример кода:

type Config struct {
    Server ServerConfig `config:"server"`
}

var result Config
err := config.Unmarshal[Config](cfg, "", &result)
// или без ключа для корневого уровня

Интерполяция шаблонов

Функция Interpolate разрешает выражения шаблонов в строковых значениях конфигурации, используя данные из предоставленного загрузчика:

// interpolating.yaml
url: "http://{{ .HOST }}:{{ .PORT }}{{ .PATH }}"

cfg, err := config.Load(config.FromYAML("interpolating.yaml"))

// добавляем данные для интерполяции из переменных окружения
interpolatedCfg, err := cfg.Interpolate(config.FromENV("APP_"))

Доступны все функции шаблона Go text/template. Дополнительно доступна функция default:

url: "{{ .PROTOCOL | default "http" }}://{{ .HOST }}/api"

Если .PROTOCOL не определен или пуст, используется значение по умолчанию http.

Подконфигурации

Sub

Функция Sub возвращает новую конфигурацию для вложенного ключа:

sub := cfg.Sub("server")
host, _ := config.Get[string](sub, "host")

Если ключ не существует или не является картой, возвращается nil.

Проверка и доступ

Has

Проверяет наличие ключа:

exists := config.Has(cfg, "server.host")
All

Возвращает полную копию всех значений конфигурации:

all := config.All(cfg)
Keys

Возвращает все ключи в точечной нотации, отсортированные по алфавиту:

keys := config.Keys(cfg)
// ["database.host", "database.port", "server.host", "server.port"]

Преобразование типов

Пакет автоматически преобразует типы при получении значений.

Числа

Строки автоматически преобразуются в числа:

// YAML: port: "8080"
port, _ := config.Get[int](cfg, "port") // 8080

Поддерживаются: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64

Время (Duration)

Строки автоматически преобразуются в time.Duration:

// YAML: timeout: "30s"
timeout, _ := config.Get[time.Duration](cfg, "timeout") // 30s

Также поддерживаются числовые значения в наносекундах:

// YAML: timeout: 30000 (30000 наносекунд = 30 микросекунд)
Срезы (Slice)

Значения массивов преобразуются в срезы:

// YAML: ports: [8080, 8081, 8082]
ports, _ := config.Get[[]int](cfg, "ports")
Структуры

Значения-мапы автоматически преобразуются в структуры (см. Unmarshal).

Обработка ошибок

KeyNotFoundError

Возвращается, когда ключ не существует в конфигурации:

_, err := config.Get[string](cfg, "missing.key")
var keyErr *config.KeyNotFoundError
if errors.As(err, &keyErr) {
    // ключ не найден
}
ConversionError

Возвращается, когда значение невозможно преобразовать в запрошенный тип:

_, err := config.Get[int](cfg, "string.value")
// ошибка, если значение = "abc"
InterpolationError

Возвращается при ошибке интерполяции шаблона:

_, err := cfg.Interpolate(loader)
if err != nil {
    var interpErr *config.InterpolationError
    if errors.As(err, &interpErr) {
        log.Printf("Ошибка в ключе %s: %v", interpErr.Key, interpErr.Cause)
    }
}

Потокобезопасность

Структура Config безопасна для параллельных чтений. Все методы доступа используют мьютексы для защиты данных.

Функции Get, MustGet, Unmarshal, Has, All, Keys не изменяют исходный Config — они потокобезопасны.

Метод Interpolate возвращает новый Config вместо изменения оригинального.

Поведение при nil конфигурации

Если cfg равен nil, поведение функций следующее:

  • Get — возвращает zero-значение типа и ошибку config is nil
  • MustGet — вызывает панику
  • Unmarshal — возвращает zero-значение типа и ошибку config is nil
  • Has — возвращает false
  • All — возвращает nil
  • Keys — возвращает nil
  • Sub — возвращает nil

Полный пример

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/shuldan/config"
)

type ServerConfig struct {
    Host         string        `config:"host"`
    Port         int           `config:"port"`
    ReadTimeout  time.Duration `config:"read_timeout"`
    WriteTimeout time.Duration `config:"write_timeout"`
}

type DatabaseConfig struct {
    Host     string `config:"host"`
    Port     int    `config:"port"`
    Database string `config:"database"`
}

type AppConfig struct {
    Server  ServerConfig  `config:"server"`
    Database DatabaseConfig `config:"database"`
}

func main() {
    // создаем загрузчики
    cfg, err := config.Load(
        config.FromYAML("config.yaml"),
        config.FromENV("APP_"),
    )
    if err != nil {
        log.Fatal(err)
    }

    // интерполяция шаблонов
    cfg, err = cfg.Interpolate(config.FromENV("APP_"))
    if err != nil {
        log.Fatal(err)
    }

    // получаем конкретные значения
    host, err := config.Get[string](cfg, "server.host", "localhost")
    if err != nil {
        log.Fatal(err)
    }
    port, err := config.Get[int](cfg, "server.port", 8080)
    if err != nil {
        log.Fatal(err)
    }

    // или маппим в структуру
    appConfig, err := config.Unmarshal[AppConfig](cfg, "")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Server: %s:%d\n", host, port)
    fmt.Printf("Database: %s:%d/%s\n",
        appConfig.Database.Host,
        appConfig.Database.Port,
        appConfig.Database.Database,
    )
}

Конфигурация может выглядеть так:

server:
  host: "{{ .SERVER_HOST }}"
  port: "{{ .SERVER_PORT }}"
  read_timeout: 30s
  write_timeout: 30s

database:
  host: localhost
  port: 5432
  database: myapp

Лицензия

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func All

func All(cfg *Config) map[string]any

func Get

func Get[T any](cfg *Config, key string, defaultVal ...T) (T, error)

func Has

func Has(cfg *Config, key string) bool

func Keys

func Keys(cfg *Config) []string

func MustGet

func MustGet[T any](cfg *Config, key string) T

func Unmarshal

func Unmarshal[T any](cfg *Config, key string) (T, error)

Types

type Config

type Config struct {
	// contains filtered or unexported fields
}

func Load

func Load(loaders ...Loader) (*Config, error)

func (*Config) Interpolate

func (c *Config) Interpolate(loader Loader) (*Config, error)

func (*Config) Sub

func (c *Config) Sub(key string) *Config

type ConversionError

type ConversionError struct {
	Key    string
	Value  any
	ToType reflect.Type
	Cause  error
}

func (*ConversionError) Error

func (e *ConversionError) Error() string

func (*ConversionError) Unwrap

func (e *ConversionError) Unwrap() error

type ENVLoader

type ENVLoader struct {
	// contains filtered or unexported fields
}

func (*ENVLoader) Load

func (l *ENVLoader) Load() (map[string]any, error)

type InterpolationError

type InterpolationError struct {
	Key   string
	Cause error
}

func (*InterpolationError) Error

func (e *InterpolationError) Error() string

func (*InterpolationError) Unwrap

func (e *InterpolationError) Unwrap() error

type JSONLoader

type JSONLoader struct {
	// contains filtered or unexported fields
}

func (*JSONLoader) Load

func (l *JSONLoader) Load() (map[string]any, error)

type KeyNotFoundError

type KeyNotFoundError struct {
	Key string
}

func (*KeyNotFoundError) Error

func (e *KeyNotFoundError) Error() string

type Loader

type Loader interface {
	Load() (map[string]any, error)
}

func FromENV

func FromENV(prefix string) Loader

func FromENVWithSeparator

func FromENVWithSeparator(prefix, separator string) Loader

func FromJSON

func FromJSON(path string) Loader

func FromMap

func FromMap(data map[string]any) Loader

func FromYAML

func FromYAML(path string) Loader

type MapLoader

type MapLoader struct {
	// contains filtered or unexported fields
}

func (*MapLoader) Load

func (l *MapLoader) Load() (map[string]any, error)

type YAMLLoader

type YAMLLoader struct {
	// contains filtered or unexported fields
}

func (*YAMLLoader) Load

func (l *YAMLLoader) Load() (map[string]any, error)

Jump to

Keyboard shortcuts

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