Documentation
¶
Overview ¶
Package config 提供 YAML 配置文件加载、struct tag 增强和反射合并。
所有 enjoydream 后端服务共享此包,统一配置加载方式。
核心特性 ¶
- 泛型加载:LoadBootstrap[T] 自动反序列化到目标结构体
- Struct tag 增强:default/env/range/options 零值填充、环境变量注入与 fail-closed 校验
- 反射合并:OverlayMerge 支持嵌套结构体和指针字段的深度合并
- Nacos 集成:配合 nacos.LoadAndMerge 实现远程配置覆盖本地默认值
快速接入 ¶
定义配置结构体:
type AppConfig struct {
Port int `yaml:"port"`
Nacos nacos.Bootstrap `yaml:"nacos"`
DB struct {
DSN string `yaml:"dsn"`
} `yaml:"db"`
}
加载配置:
cfg, err := config.LoadBootstrap[AppConfig]("dev")
if err != nil {
log.Fatal(err)
}
加载入口(按来源选) ¶
- `LoadBootstrap[T](envFlag)`:**etc/ 约定**下的路径查找(etc/config.yaml → etc/config-{env}.yaml),返回 *T;
- `LoadFile(path, dst)`:**任意路径**(CLI -config 传的路径、容器内固定挂载位 如 /app/plugins.yaml、部署方自定路径);
- `Unmarshal(data, dst)`:**字节源**(API 下发、模板渲染产物、测试夹具)。
三个入口共享**同一条解码线**(展开 ${VAR} → 可选严格判定 → 解码 → tag 接线), 因此严格模式(WithStrict())的诊断面(嵌套路径 + 该层合法键清单 + 近似拼写建议) **与来源无关**——不会因为「不是从 etc/ 约定路径读的」就退化成一条 “field X not found in type Y”。
Struct Tag 增强(default/env/range/options/require) ¶
LoadBootstrap 在 yaml unmarshal 之后依次执行 ApplyEnv → FillDefaults → ValidateTags(经 ApplyTagPasses,带 FieldSet);四个函数也可单独用于任意 结构体指针:
type Server struct {
Host string `default:"localhost"`
Port int `default:"8080" env:"APP_PORT" range:"(0,65535]"`
Timeout time.Duration `default:"5s" range:"(0,30s]"`
Mode string `default:"dev" options:"dev,prod"`
Secret string `require:"true" env:"APP_SECRET"` // 必须外部提供
}
优先级(写死):yaml 显式值 > env tag(env 已设置且字段为零值时才填)> default tag。
零值歧义与 FieldSet ¶
0/""/false/空切片与"未设置"对反射不可区分。yaml 显式写 `port: 0` 时:
- LoadBootstrap / ApplyTagPasses:内部构建 FieldSet,显式零值保留,不被 default / env 覆盖;
- 单独调用 FillDefaults / ApplyEnv(不带 WithFieldSet):保持历史行为,零值 会被 default 覆盖——需要区分请传 FieldSetFromYAML + WithFieldSet,或对 少量字段用指针类型。
整份配置都要区分零值时,全量 *T 改造成本很高:优先用 FieldSet;少量关键 字段用指针类型仍可行。不要在未评估成本前把配置结构体整体指针化。
require ¶
字段必须由 yaml / env 显式提供,default 不算「提供」(FillDefaults 永不填 require 字段)。带 FieldSet 时显式零值也算通过;ValidateTags 在缺失时报错 并指出应设置的 env 变量名。
env 卫生与命名检查 ¶
ValidateTags 只做**静态**检查(与进程环境无关):
- env 变量名须匹配 `[A-Za-z_][A-Za-z0-9_]*`;
- 同一次校验内重复的 env 名报错。
命名一致性是**可选**的:`LintEnvNaming` 返回「声明名 ≠ 推导约定名」 (`backup_archive_format` → `BACKUP_ARCHIVE_FORMAT`)的诊断列表;需要 fail-closed 时传 `WithEnvNamingLint()`。有意自定义名(如 `LOG_PATH`)请打 `ignorederived:"true"` 豁免。不要依赖「环境里恰好有某变量」做拼写判定—— 会误报 PATH/HOME 等系统变量,且漏掉无人设置的真拼写错误(core#8)。
env 命名策略(前缀 / 逐字段覆盖) ¶
字段的 env 变量名默认由结构体作者逐字段写死(`env:"NAME"` tag)。宿主若要 **统一前缀**(`PB_` / `APP_` / `SVC_`)或**逐字段改名**(历史 env 名与 yaml 键对不上、需保留兼容),用 EnvNaming——见 envnaming.go:
cfg, err := config.LoadBootstrap[MyConfig]("dev",
config.WithEnvPrefix("PB_"), // 全局前缀
config.WithEnvOverrides(map[string]string{
"upload.local_import.enabled": "PB_LOCAL_IMPORT_ENABLED", // 逐字段覆盖
}),
)
名字解析顺序:覆盖表 → env tag → 路径派生名(yaml 键路径的 SCREAMING_SNAKE)。 覆盖表命中即用表里的**绝对名**(值为空串 = 该字段不走 env);前缀只加在 本包推导的名字上。**未启用策略时行为与历史逐字一致**:只有打了 env tag 的 字段读 env。
覆盖表的键是 **yaml 键路径**(与严格模式诊断路径同口径):嵌套用 `.` 连接、 切片元素不编号、`yaml:",inline"` 的子字段拍平到父级。
支持类型:标量(string/int*/uint*/float*/bool)、time.Duration(time.ParseDuration 文法)、[]string(default/env 逗号切分)。range 仅数值字段,文法 `[a,b]`/`(a,b)`/`[a,b)`/`(a,b]`/单端 `[a,)`/`(,b]`(Duration 字段边界用 duration 文法如 "(0,30s]");options 仅 string 字段,空串也算未命中枚举 (可选枚举请配 default)。tag 非法或值越界一律报错(fail-closed)并携带 字段路径(嵌套 `Server.Port`、切片 `Items[1].Qty`)。
遍历覆盖嵌套 struct、*struct(nil 按需分配)、[]struct;不遍历 map, 跳过非导出字段。
ConfigWatcher 不自动执行这些 pass(其 parser 由调用方提供);热更场景可在 parser 里自行调用 ApplyTagPasses / ApplyEnv / FillDefaults / ValidateTags。
严格模式(未知键拒载) ¶
默认未知键被 yaml.v3 **静默丢弃**——对 200+ 字段的配置结构体, 「拼错一个键」会表现为「我配了但没生效」(静默走零值缺省), 排查要等到看日志、查文档、怀疑平台才定位到是自己拼错了。
传 WithStrict() 开启严格模式:未知键**直接拒载**,错误信息给出 **嵌套路径**、**该层合法键清单**与**近似拼写建议**:
cfg, err := config.LoadBootstrap[AppConfig]("dev", config.WithStrict())
// config: 未知配置键 "llm.base_ur"(是否想写 "base_url"?);该层合法键:
// api_key, base_url, timeout_ms
严格模式是**显式开启**的——它会让「之前一直能跑(因为键拼错被忽略)」的配置 开始报错,切换前需确认现有配置无误。
合法键判定与 yaml.v3 的字段语义**逐条对齐**(yaml tag 优先、无 tag 取小写 字段名、显式 `,inline` 拍平、内嵌未导出类型同样处理)——不一致会把合法键 误报成未知键。
配置文件约定 ¶
配置文件路径:etc/config-{env}.yaml env 由启动参数或环境变量决定,如 dev、prod。
Package dotenv 环境文件加载(.env 纪律单源):按候选路径顺序取第一个 存在的 .env,逐行解析 KEY=VALUE(# 注释行跳过、值两侧引号剥除),已存在的 环境变量不被覆盖(真实环境优先于文件)。收编自 argus internal/config LoadDotEnv(v0.32.0)——服务开发期「自动加载 lacus/.env」的通用引导语义。
Index ¶
- Variables
- func ApplyEnv(v any, opts ...PassOption) error
- func ApplyEnvOverlay[T any](dst *T, opts ...PassOption) error
- func ApplyTagPasses(cfg any, yamlData []byte, opts ...PassOption) error
- func FillDefaults(v any, opts ...PassOption) error
- func Get(key string) string
- func GetBool(key string) (bool, bool)
- func GetInt(key string) (int, bool)
- func GetIntWithDefault(key string, defaultValue int) int
- func GetStringWithDefault(key, defaultValue string) string
- func LintEnvNaming(v any, opts ...PassOption) ([]string, error)
- func LoadBootstrap[T any](envFlag string, opts ...LoadOption) (*T, error)
- func LoadDotEnv(paths ...string)
- func LoadFile(path string, dst any, opts ...LoadOption) error
- func MustGet(key string) string
- func OverlayMerge[T any](dst *T, src *T)
- func ParseDuration(value string, fallback time.Duration) time.Duration
- func Unmarshal(data []byte, dst any, opts ...LoadOption) error
- func ValidateTags(v any, opts ...PassOption) error
- type ConfigSource
- type ConfigWatcher
- type EnvNaming
- type FieldSet
- type LoadOption
- type PassOption
- type UnknownKeyError
Constants ¶
This section is empty.
Variables ¶
var DefaultDotEnvPaths = []string{"./.env", "../.env", "../../.env", "../../../.env"}
DefaultDotEnvPaths 常见 .env 位置(服务根目录向上逐级:本地 go run 与 子目录测试二进制的工作目录差异)。
Functions ¶
func ApplyEnv ¶ added in v0.5.0
func ApplyEnv(v any, opts ...PassOption) error
ApplyEnv 依据 env tag 把环境变量写入字段:env 已设置且字段当前为零值时才填, 已有值(yaml 显式设置)不覆盖。
优先级约定:yaml 显式值 > env tag > default tag——LoadBootstrap 先 ApplyEnv 后 FillDefaults,env 填过的非零字段不会再被 default 覆盖。
带 FieldSet 时,YAML 显式提供的字段(含显式零值)不会被 env 覆盖;env 成功 写入会补记进 FieldSet,避免后续 FillDefaults 用 default 冲掉 env 值。
env 设为空串(或纯空白)视同未设置;env 值无法按字段类型解析时 fail-closed 报错(错误信息携带字段路径与变量名)。支持类型与遍历范围同 FillDefaults。
v 必须是非 nil 的结构体指针,否则返回错误。
func ApplyEnvOverlay ¶ added in v0.8.0
func ApplyEnvOverlay[T any](dst *T, opts ...PassOption) error
ApplyEnvOverlay 把环境变量解析到独立的 overlay 并覆盖 dst(#2): 与 ApplyEnv(只填零值字段,「yaml 显式值优先」)语义相反——本函数实现 「env 优先于配置文件」的 12-factor 场景(Helm/docker-compose 靠 env 传配置)。
等价手写:
overlay := &T{}
_ = ApplyEnv(overlay) // 空结构体上解 env → env 非零值进入 overlay
OverlayMerge(dst, overlay) // overlay 的非零值压过 dst(文件值)
env 未设置的字段不影响 dst(overlay 对应字段为零值,OverlayMerge 跳过)。 注意:OverlayMerge 是「非零值才覆盖」,env 显式配置零值(如 APP_PORT=0) 无法把文件里的非零值清成零——零值歧义与 ApplyEnv 一致,见 tags.go 文档。
可传 WithEnvNaming 启用 env 命名策略(前缀 / 逐字段覆盖)——见 envnaming.go。
func ApplyTagPasses ¶ added in v0.9.0
func ApplyTagPasses(cfg any, yamlData []byte, opts ...PassOption) error
ApplyTagPasses 对任意结构体指针执行完整 tag 接线:先按 yamlData 构建 FieldSet,再 ApplyEnv → FillDefaults → ValidateTags。与 LoadBootstrap 内部 同一套语义;热更 / 手写 loader 可直接复用。
可传 WithEnvNaming 启用 env 命名策略(前缀 / 逐字段覆盖)。
func FillDefaults ¶ added in v0.5.0
func FillDefaults(v any, opts ...PassOption) error
FillDefaults 依据 default tag 为零值字段填充默认值,非零字段一律不动。
优先级约定(与 LoadBootstrap 内 ApplyEnv → FillDefaults 的接线顺序配合): yaml 显式值 > env tag > default tag。
零值歧义:字段当前的 0/""/false/空切片与"未设置"对反射不可区分。yaml 里 显式写 `port: 0` 时,**不带 FieldSet** 调用本函数会被 default 覆盖(历史行为); 需要保留显式零值请传入 WithFieldSet(LoadBootstrap / ApplyTagPasses 已默认接线), 或对该字段打 require tag(default 永不填充,必须由 yaml/env 提供)。 整份配置都要区分零值时,少量字段用指针类型仍可行;改造成本过高则用 FieldSet,而不是全量 *T。
带 require tag 的字段本函数永不填充(default 不算「提供」)。
支持类型:string、int*/uint*/float*、bool、time.Duration(time.ParseDuration 文法,如 "5s")、[]string(逗号切分,元素去首尾空格并丢弃空元素);标量值 解析前同样去除首尾空格。其他类型携带 default tag 视为配置错误并报错。
遍历范围:嵌套 struct、*struct(nil 时若子结构体携带目标 tag 则按需分配后 继续填充)、[]struct / []*struct(仅已存在的元素);不遍历 map,跳过非导出字段。
v 必须是非 nil 的结构体指针,否则返回错误。
func GetIntWithDefault ¶
GetIntWithDefault 读取环境变量并解析为整数,未设置或解析失败时返回默认值。
func GetStringWithDefault ¶
GetStringWithDefault 读取环境变量,未设置或为空时返回默认值。
func LintEnvNaming ¶ added in v0.9.2
func LintEnvNaming(v any, opts ...PassOption) ([]string, error)
LintEnvNaming 静态扫描 env tag 与推导约定名不一致的字段,返回诊断行 (不读进程环境、不 fail-closed)。适合 CI/lint 脚本或启动前自检。 有意自定义名请打 `ignorederived:"true"`。
可传 WithEnvNaming 启用命名策略口径:覆盖表命中的字段豁免;设了前缀则按 **路径级**派生名比对(与运行时实际读的变量名同口径)。
func LoadBootstrap ¶
func LoadBootstrap[T any](envFlag string, opts ...LoadOption) (*T, error)
LoadBootstrap reads a local YAML config file, expands ${VAR} and ${VAR:default} environment variable placeholders, and unmarshals it into the provided struct type T.
After unmarshal, the struct tag passes run in order ApplyEnv → FillDefaults → ValidateTags (precedence: explicit yaml value > env tag > default tag; see tags.go for the supported tags). Non-struct config types skip the tag passes.
⚠️ 该接线是「yaml 优先于 env」。12-factor(env 优先于配置文件)场景请勿直接 复用 LoadBootstrap 的语义——在 yaml 填充后调用 ApplyEnvOverlay(dst) (见 overlay.go),env 非零值会覆盖文件值。
Lookup order:
- etc/config.yaml — used in Docker images (renamed from config-{env}.yaml at build time)
- etc/config-{envFlag}.yaml — used in local development (envFlag = "dev", "local", "prod")
可选传 WithStrict() 开启严格模式:未知键直接拒载,错误信息带嵌套路径与 该层合法键清单(见 strict.go)。默认仍是历史行为(未知键静默忽略)。
**不遵循 etc/ 约定的宿主**请用 LoadFile(任意路径)或 Unmarshal(字节源)—— 本函数只负责 etc/ 约定下的路径查找,解码语义与它们**同源**(unmarshalInto)。
func LoadDotEnv ¶
func LoadDotEnv(paths ...string)
LoadDotEnv 依次尝试候选路径,加载第一个存在的 .env 后返回(不叠加多文件)。 paths 为空时用 DefaultDotEnvPaths。解析失败的行跳过不致命;已设置的环境 变量不被文件值覆盖。无任何候选文件存在时静默返回(无 .env 是常态)。
func LoadFile ¶ added in v0.11.0
func LoadFile(path string, dst any, opts ...LoadOption) error
LoadFile 从**任意路径**读配置并解码——不带 etc/ 约定查找。
适用:CLI -config 任意路径、容器内镜像内固定挂载位(如 /app/plugins.yaml)、 部署方自定路径等**不遵循 etc/config.yaml 约定**的宿主。
严格模式(传 WithStrict())的错误面与 LoadBootstrap 同源。
func OverlayMerge ¶
func OverlayMerge[T any](dst *T, src *T)
OverlayMerge 将 src 中非零值字段覆盖合并到 dst 中,支持嵌套结构体。
支持的 merge tag:
- "overwrite": 整体替换(包括零值),适用于需要强制覆盖的字段
- "bool_or": 布尔字段使用 OR 语义(dst || src),适用于 enable 标志
- "zeroable": 零值视为有效值合并(而非跳过),适用于需要显式设置 0/""/false 的字段
func ParseDuration ¶
ParseDuration 解析 duration 字符串,失败或为空时返回 fallback。 适用于 YAML 配置中以字符串存储的 duration 字段。
用法:
d := config.ParseDuration(cfg.WorkerPollInterval, time.Second)
func Unmarshal ¶ added in v0.11.0
func Unmarshal(data []byte, dst any, opts ...LoadOption) error
Unmarshal 解码 YAML 字节 → 结构体指针:展开 ${VAR} → 可选严格判定 → tag 接线 (ApplyEnv → FillDefaults → ValidateTags;非结构体类型跳过 tag 接线)。
这是**与路径无关**的入口:配置来自 API 下发、模板渲染产物、测试夹具时用它。
严格模式(传 WithStrict())的错误面与文件路径版**同源**——同一套 unmarshalConfig、同一套 *UnknownKeyError(嵌套路径 + 该层合法键清单 + 近似拼写建议),**不会因为来源不同而丢诊断**。
⚠️ 该接线是「yaml 优先于 env」。12-factor(env 优先于配置文件)场景请勿直接 复用此语义——在 yaml 填充后调用 ApplyEnvOverlay(dst)(见 overlay.go), env 非零值会覆盖文件值。
func ValidateTags ¶ added in v0.5.0
func ValidateTags(v any, opts ...PassOption) error
ValidateTags 依据 range / options tag 做 fail-closed 校验,只读不写; 同时执行 require 与 env tag 卫生检查。
- range:仅数值字段(含 time.Duration,边界用 duration 文法如 "(0,30s]")。 文法:`[a,b]`/`(a,b)`/`[a,b)`/`(a,b]` 四种区间 + 单端开区间 `[a,)`/`(,b]` (开侧方括号方向不敏感),边界支持小数;tag 文法非法或值越界均报错, 错误信息携带字段路径(嵌套用 `.` 连接,切片元素用 `Items[1].Qty`)。
- options:仅 string 字段的枚举校验;当前值(含空串)必须命中枚举之一, 可选枚举字段请配合 default tag 给默认值。
- require:字段必须由 yaml / env 显式提供(default 不算)。带 FieldSet 时 以集合为准(显式零值算通过);不带 FieldSet 时退化为「最终值非零」。
- env 卫生(静态):变量名须为 `[A-Za-z_][A-Za-z0-9_]*`;同一次校验内重复的 env 名报错。不读进程环境——与环境相关的检查会误报 PATH/HOME 等系统变量、 且漏掉无人设置的真拼写错误(core#8),见 LintEnvNaming / WithEnvNamingLint。
与 FillDefaults/ApplyEnv 配合使用时放在最后(LoadBootstrap 接线顺序 ApplyEnv → FillDefaults → ValidateTags),校验的是填充后的最终值。
v 必须是非 nil 的结构体指针,否则返回错误。
Types ¶
type ConfigSource ¶
type ConfigSource interface {
// Read 读取配置内容。
Read() ([]byte, error)
// Watch 监听配置变化,变化时调用 onChange。
// 应在 ctx 取消时停止监听。
Watch(ctx context.Context, onChange func())
}
ConfigSource 是配置源接口。
func OverlaySource ¶ added in v0.7.0
func OverlaySource(srcs ...ConfigSource) ConfigSource
OverlaySource 把多个 ConfigSource 组合为一个:
- Read:按参数顺序逐个读取并深合并(后者覆盖前者),返回合并后的 blob。 任一子源读取失败即返回该错误
- Watch:任一子源变化即触发 onChange(扇入),ctx 取消传播到全部子源
合并语义为 JSON blob 级深合并(对象递归、标量/数组整体覆盖),与 OverlayMerge 的结构体级合并互补——多来源场景推荐 OverlaySource + ConfigWatcher[T] 组合实现多源热更新。 所有子源须产出同一格式(JSON)内容。
type ConfigWatcher ¶
type ConfigWatcher[T any] struct { // contains filtered or unexported fields }
ConfigWatcher 是配置热更新监听器。 支持监听配置文件变化并自动重载。
func NewConfigWatcher ¶
func NewConfigWatcher[T any](source ConfigSource, parser func([]byte) (*T, error)) *ConfigWatcher[T]
NewConfigWatcher 创建配置监听器。
参数:
- source: 配置源(如文件、Nacos——见 nacos.NewConfigSource)
- parser: 配置解析函数(如 YAML 解析)
func (*ConfigWatcher[T]) OnChange ¶
func (w *ConfigWatcher[T]) OnChange(fn func(old, new *T))
OnChange 注册配置变更回调。
func (*ConfigWatcher[T]) Reload ¶
func (w *ConfigWatcher[T]) Reload() error
Reload 手动触发配置重载。 Reload 手动触发配置重载。 Swap 与 onChange 回调整体串行化,保证回调按重载顺序投递; 注意 onChange 内不得再调用 Reload(同 goroutine 重入会死锁)。
func (*ConfigWatcher[T]) SetLogger ¶
func (w *ConfigWatcher[T]) SetLogger(l *slog.Logger)
SetLogger 设置自定义日志记录器。 任意时刻可调:logger 经 atomic.Pointer 保护(2026-09-06 复审修复, 原实现无锁写,Start 后 Watch 回调 goroutine 并发读构成数据竞争)。
type EnvNaming ¶ added in v0.12.0
type EnvNaming struct {
// Prefix 全局前缀,加在 env tag 名与路径派生名之前。空串 = 不加前缀。
// 例:"PB_" 使 yaml 键 `upload.local_import.enabled` 读
// `PB_UPLOAD_LOCAL_IMPORT_ENABLED`。
Prefix string
// Overrides 逐字段覆盖:键为 yaml 键路径,值为该字段的 env 变量名。
// 值为空串表示该字段**不走 env**。nil / 空 map = 无覆盖。
Overrides map[string]string
}
EnvNaming 描述 **env 变量名**的命名策略:全局前缀 + 逐字段覆盖。
现状(本包 v0.11.0 之前):字段只有打了 `env:"NAME"` tag 才会读 env,名字由 结构体作者逐字段写死;「约定名」(由 yaml 键推导的 SCREAMING_SNAKE)只被 LintEnvNaming / WithEnvNamingLint 用来**检查**名字是否一致,并不能**决定** 实际读的变量名。
于是两类宿主接不上:
- 用统一前缀的 12-factor 宿主(`PB_` / `APP_` / `SVC_`):每个字段都得手写 `env:"PB_XXX"`,且**无法**让「没打 env tag 的字段」也按约定参与 env 读取;
- 历史 env 名与 yaml 键对不上、需保留兼容的宿主:只能在结构体 tag 里钉死, 结构体不是自己的就改不动。
EnvNaming 补这两条入口。**名字解析顺序(先命中先赢)**:
- **覆盖表**(Overrides,键为 yaml 键路径):命中即用表里的名字——**绝对名**, 不加前缀;值为空串表示**该字段不走 env**。
- **env tag**:字段显式声明了 `env:"NAME"` 就用它(加了前缀则前缀在前)。
- **路径派生名**:没有显式名时按 yaml 键路径推导 (`upload.local_import.enabled` → `UPLOAD_LOCAL_IMPORT_ENABLED`)。
前缀(Prefix)加在**本包自己推导的名字**上(第 2、3 步),覆盖表里的名字不受 影响。默认(既无前缀也无覆盖表)行为与历史**逐字一致**:只有打了 env tag 的 字段读 env。
覆盖表的键是 **yaml 键路径**(与严格模式 UnknownKeyError.Path 同口径,便于 对账):嵌套用 `.` 连接、切片元素不编号(`items.qty` 而非 `items[0].qty`)、 `yaml:",inline"` 的子字段拍平到父级、`yaml:"-"` 的字段及其子树不可寻址。
type FieldSet ¶ added in v0.9.0
FieldSet 记录结构体字段是否被**显式提供**(YAML 里出现过对应 key,或 env tag 成功写入)。用于消除零值歧义:`port: 0` 与未写字段对反射不可区分,但前者 是调用方的显式选择,不应被 default 覆盖,也不应被 env 覆盖。
键为字段路径(`Server.Port`、`Items[0].Qty`),与 tag 校验错误信息同格式。 由 FieldSetFromYAML 构建,经 WithFieldSet 传入 FillDefaults / ApplyEnv / ValidateTags;ApplyEnv 成功写入时会在集合里补记该路径。
func FieldSetFromYAML ¶ added in v0.9.0
FieldSetFromYAML 解析 YAML data,标记 v 的类型里在 data 中**显式出现**的字段。 key 存在即算显式提供(含 `port: 0` / `name: ""` / `port: null`);key 缺失则 该字段(及其嵌套)不算提供。
v 必须是非 nil 的结构体指针;data 为空或注释-only 时返回空集合。 与 LoadBootstrap 同源使用时请传入 ${VAR} 展开后的同一份 data。
type LoadOption ¶ added in v0.10.0
type LoadOption func(*loadOptions)
LoadOption 加载选项。
func WithEnvOverrides ¶ added in v0.12.0
func WithEnvOverrides(m map[string]string) LoadOption
WithEnvOverrides 逐字段指定 env 变量名——给「历史 env 名与 yaml 键对不上、 需保留兼容」的宿主用。
键为 **yaml 键路径**(与严格模式诊断路径同口径):嵌套用 `.` 连接、切片元素 不编号、`yaml:",inline"` 的子字段拍平到父级。值为空串表示**该字段不走 env** (显式豁免,压过 env tag 与前缀)。
WithEnvOverrides(map[string]string{
"upload.local_import.enabled": "PB_LOCAL_IMPORT_ENABLED",
"legacy.retries": "", // 该字段不读 env
})
覆盖表**只影响命中的字段**:未命中的字段行为不变(仍按 env tag 读 env,没有 tag 就不读)。覆盖优先于 env tag——表里写了就是表里说了算。
覆盖表里若有不存在的 yaml 键路径,该条目不生效(不报错):同一张表可以跨多个 配置结构体共用。
func WithEnvPrefix ¶ added in v0.12.0
func WithEnvPrefix(prefix string) LoadOption
WithEnvPrefix 给 env 名加全局前缀——给「用统一前缀的 12-factor 宿主」用。
前缀加在**本包推导的名字**上(env tag 名与路径派生名),覆盖表里的名字是 绝对名不受影响。例:
type Cfg struct {
Upload struct {
LocalImport struct {
Enabled bool `yaml:"enabled"`
} `yaml:"local_import"`
} `yaml:"upload"`
}
// WithEnvPrefix("PB_") 后:Enabled 读 PB_UPLOAD_LOCAL_IMPORT_ENABLED
cfg, err := config.LoadBootstrap[Cfg]("dev", config.WithEnvPrefix("PB_"))
前缀为空时行为与历史完全一致:**只有打了 env tag 的字段读 env**。加了前缀后 额外多一层能力——**没打 env tag 的字段也按路径派生名参与 env 读取**(如上例的 Enabled),这正是「前缀即约定」的含义:宿主不必逐字段写 tag。
与 WithEnvOverrides 叠加时:覆盖表命中的字段用表里的绝对名,其余字段按本 前缀规则。
func WithStrict ¶ added in v0.10.0
func WithStrict() LoadOption
WithStrict 严格模式:**未知键直接拒载**,不做静默忽略。
默认(不传本选项)保持历史行为——未知键被 yaml.v3 静默丢弃。严格模式是 显式开启的,因为它会让「之前一直能跑(因为键拼错被忽略)」的配置开始报错。
错误信息带**嵌套路径**与**该层合法键清单**,并给出近似拼写建议:
config: 未知配置键 "llm.base_ur"(是否想写 "base_url"?);该层合法键: base_url, timeout_ms, api_key
type PassOption ¶ added in v0.9.0
type PassOption func(*passConfig)
PassOption 定制 FillDefaults / ApplyEnv / ValidateTags 的单次执行。
func WithEnvNaming ¶ added in v0.12.0
func WithEnvNaming(n EnvNaming) PassOption
WithEnvNaming 给单独调用 ApplyEnv / ValidateTags / ApplyEnvOverlay 的场景 传命名策略(LoadOption 层的 WithEnvPrefix / WithEnvOverrides 是它的语法糖)。
典型用法是 12-factor 的 env 优先接线——yaml 填完后再用 env 覆盖:
_ = config.ApplyEnvOverlay(&cfg, config.WithEnvNaming(config.EnvNaming{
Prefix: "PB_",
}))
func WithEnvNamingLint ¶ added in v0.9.2
func WithEnvNamingLint() PassOption
WithEnvNamingLint 让 ValidateTags 对「声明的 env 名 ≠ 由字段/yaml key 推导的 约定名」报错(纯静态,不读进程环境)。有意使用自定义名时在字段上打 `ignorederived:"true"` 豁免。默认关闭——命名不一致并不一定是错误。
func WithFieldSet ¶ added in v0.9.0
func WithFieldSet(s FieldSet) PassOption
WithFieldSet 传入显式提供集合:命中的路径视为「已有调用方值」—— FillDefaults 不再填 default,ApplyEnv 不再覆盖(即使当前是零值)。 ApplyEnv 成功写入时会向该集合补记路径(map 引用语义,调用方可见)。
type UnknownKeyError ¶ added in v0.10.0
type UnknownKeyError struct {
// Path 嵌套路径(YAML 键形式),如 "llm.base_ur";顶层键时只有键名。
Path string
// Key 未知键名。
Key string
// Legal 该层级的合法键清单(YAML 键形式,已排序)。
Legal []string
// Near 近似拼写建议(与 Legal 中某键编辑距离最小且足够近时才有值)。
Near string
}
UnknownKeyError 未知配置键错误。
func (*UnknownKeyError) Error ¶ added in v0.10.0
func (e *UnknownKeyError) Error() string