internal/config: загрузка YAML+env (подстановка ${VAR:-default}, defaults, env override, Validate)
All checks were successful
build-test / build (push) Successful in 1m23s
All checks were successful
build-test / build (push) Successful in 1m23s
This commit is contained in:
89
internal/config/types.go
Normal file
89
internal/config/types.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Package config — загрузка конфигурации Ratatoskr-go.
|
||||
//
|
||||
// Источники (каждый следующий имеет приоритет над предыдущим):
|
||||
// 1. Файл config.yaml (с подстановкой ${VAR:-default})
|
||||
// 2. Переменные окружения (env-теги в структуре)
|
||||
//
|
||||
// Порядок: Load("config.yaml") → YAML → os.Expand → env override → Validate.
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration — time.Duration с YAML-сериализацией (строка "5s", "10m").
|
||||
type Duration time.Duration
|
||||
|
||||
func (d Duration) Duration() time.Duration { return time.Duration(d) }
|
||||
func (d Duration) String() string { return time.Duration(d).String() }
|
||||
|
||||
func (d *Duration) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var s string
|
||||
if err := unmarshal(&s); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidFormat, err)
|
||||
}
|
||||
td, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: parse duration %q: %v", ErrInvalidFormat, s, err)
|
||||
}
|
||||
*d = Duration(td)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config — полная конфигурация бинаря.
|
||||
type Config struct {
|
||||
Telegram TelegramCfg `yaml:"telegram"`
|
||||
Gitea GiteaCfg `yaml:"gitea"`
|
||||
OpenCode OpenCodeCfg `yaml:"opencode"`
|
||||
Chat ChatCfg `yaml:"chat"`
|
||||
Paths PathsCfg `yaml:"paths"`
|
||||
}
|
||||
|
||||
type TelegramCfg struct {
|
||||
Token string `yaml:"token" env:"TG_TOKEN"`
|
||||
ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"`
|
||||
}
|
||||
|
||||
type GiteaCfg struct {
|
||||
URL string `yaml:"url" env:"GITEA_URL"`
|
||||
Token string `yaml:"token" env:"GITEA_TOKEN"`
|
||||
User string `yaml:"user" env:"GITEA_USER"`
|
||||
}
|
||||
|
||||
type OpenCodeCfg struct {
|
||||
Bin string `yaml:"bin" default:"opencode"`
|
||||
DBPath string `yaml:"db_path" default:""`
|
||||
Config string `yaml:"config" env:"OPENCODE_CONFIG"`
|
||||
HardTimeout Duration `yaml:"hard_timeout" default:"20m"`
|
||||
IdleTimeout Duration `yaml:"idle_timeout" default:"2m"`
|
||||
PollMs Duration `yaml:"poll_ms" default:"2s"`
|
||||
}
|
||||
|
||||
type ChatCfg struct {
|
||||
PollInterval Duration `yaml:"poll_interval" default:"30s"`
|
||||
}
|
||||
|
||||
type PathsCfg struct {
|
||||
Worktree string `yaml:"worktree" default:"/opt/data/ratatoskr/worktrees"`
|
||||
}
|
||||
|
||||
// Validate проверяет обязательные поля. Возвращает C1 MissingField
|
||||
// (склеенную, все ошибки сразу) или C2 InvalidFormat.
|
||||
func (c *Config) Validate() error {
|
||||
var errs []error
|
||||
if c.Telegram.Token == "" {
|
||||
errs = append(errs, fmt.Errorf("%w: telegram.token", ErrMissingField))
|
||||
}
|
||||
if c.Telegram.ChatID == "" {
|
||||
errs = append(errs, fmt.Errorf("%w: telegram.chat_id", ErrMissingField))
|
||||
}
|
||||
if c.Gitea.URL == "" {
|
||||
errs = append(errs, fmt.Errorf("%w: gitea.url", ErrMissingField))
|
||||
}
|
||||
if c.Gitea.Token == "" {
|
||||
errs = append(errs, fmt.Errorf("%w: gitea.token", ErrMissingField))
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
Reference in New Issue
Block a user