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