功能说明
YAML 可读性好,但宽松的解析规则也意味着拼错的字段会被静默忽略,导致生产环境用了默认值却毫无察觉。这个加载器开启 KnownFields,把未在结构体中声明的键视为错误;解码后再做业务校验,确保监听地址非空、超时能解析成合法时长、请求体上限为正数。
示例文档包含服务配置、功能开关列表和调试标志,解码结果可以直接以百分号加 v 的形式打印检查。错误信息带上前缀,便于定位是哪一段配置不合法。
关键代码
yaml.NewDecoder 包装字节流,KnownFields 必须在 Decode 之前调用。结构体字段通过 yaml 标签映射到下划线命名,ReadTimeout 先声明为字符串再交给 time.ParseDuration,因为 YAML 库不会自动把 5s 解析成时长。校验按重要性排序:地址、超时、体积,每个错误都用 fmt.Errorf 包装原始错误。main 还构造了一份带未知 tls 字段的文档,验证拒绝逻辑。
使用注意
配置来源应优先使用只读挂载或密钥管理,解码后不要打印包含口令的完整结构。YAML 支持锚点和合并键,滥用会让配置难以追踪。时长为零可能代表不限制,需要与业务确认,不能简单当作非法。多文档流需要使用 Decode 循环。修改结构体字段名时要同步更新标签,否则旧配置会被拒绝,发布前应做兼容性检查。
验证方式
合法文档解码后输出地址 :8080、超时 5s、体积 1048576、功能列表包含 search 与 preview,err 为 nil。带 tls 未知字段的文档返回包含 field tls not found 的错误,输出提示为 true。可以再把 read_timeout 改成数字,确认时长解析失败并给出明确错误。
代码示例
package main
import (
"bytes"
"fmt"
"time"
"gopkg.in/yaml.v3"
)
type ServerConfig struct {
Addr string `yaml:"addr"`
ReadTimeout string `yaml:"read_timeout"`
MaxBodyBytes int64 `yaml:"max_body_bytes"`
}
type Config struct {
Server ServerConfig `yaml:"server"`
Features []string `yaml:"features"`
Debug bool `yaml:"debug"`
}
func LoadConfig(data []byte) (Config, error) {
var cfg Config
decoder := yaml.NewDecoder(bytes.NewReader(data))
decoder.KnownFields(true)
if err := decoder.Decode(&cfg); err != nil {
return Config{}, fmt.Errorf("decode config: %w", err)
}
if cfg.Server.Addr == "" {
return Config{}, fmt.Errorf("server.addr is required")
}
if _, err := time.ParseDuration(cfg.Server.ReadTimeout); err != nil {
return Config{}, fmt.Errorf("server.read_timeout: %w", err)
}
if cfg.Server.MaxBodyBytes <= 0 {
return Config{}, fmt.Errorf("server.max_body_bytes must be positive")
}
return cfg, nil
}
func main() {
document := []byte(`
server:
addr: ":8080"
read_timeout: 5s
max_body_bytes: 1048576
features:
- search
- preview
debug: false
`)
cfg, err := LoadConfig(document)
fmt.Printf("config: %+v err=%v\n", cfg, err)
unknown := []byte("server:\n addr: \":8080\"\n read_timeout: 5s\n max_body_bytes: 10\n tls: true\n")
_, err = LoadConfig(unknown)
fmt.Println("unknown field rejected:", err != nil)
}