← 中文资源

简体中文版本

YAML 配置解码

把 YAML 解码到强类型结构,拒绝未知字段,并校验必填地址、时长格式与体积上限。

阅读英文原文

功能说明

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)
}