configx

package
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 18 Imported by: 0

README

configx

configx 是 Aisphere Kernel 的统一配置模块。它负责把 file、env、remote source 加载成一棵可查询、可 Scan、可 Watch 的配置树,是 Kernel 中唯一的运行时配置读取入口。

新手上路:只看本文件即可上手。需要深度细节时再翻其他文档(见末尾"文档地图")。


1. 为什么需要 configx

在 configx 之前,业务系统通常同时使用本地 YAML/JSON、环境变量、Kubernetes ConfigMap、Nacos、Apollo、etcd、Consul 等配置来源。如果每个模块直接读取 os.Getenv、直接解析 YAML,最终会出现三类问题:启动顺序不一致、默认值规则不一致、热更新行为不一致。

configx 把这些差异收敛成一套 Source / Watcher / Value 契约。业务代码只依赖 configx.Config,不关心配置来自文件、环境变量还是远程配置中心。这样后续迁移配置来源时,只需要替换 Source,不需要改业务模块。

configx 的核心契约:配置来源可以有很多个,但业务读取配置只能通过 Config / Value / Scan 这一套稳定接口。

configx Source(file/env/remote)
  ↓ Load() []*KeyValue
configx Reader merge + resolve
  ↓ stable tree
Config.Value / Config.Scan / Config.Watch
  ↓ logx/dbx/httpx/grpcx/authx/cachex 等模块消费结构化配置

configx 本身不负责校验所有业务字段、不启动服务、不记录审计、不绑定具体配置中心 SDK。它只负责加载、合并、解析占位符、类型读取、热更新通知。


2. 30 秒上手

package bootstrap

import (
    "github.com/aisphereio/kernel/configx"
    "github.com/aisphereio/kernel/configx/file"
)

type ServerConfig struct {
    HTTP struct {
        Addr string `json:"addr"`
        Port int    `json:"port"`
    } `json:"http"`
}

func LoadServerConfig(path string) (ServerConfig, error) {
    cfg := configx.New(configx.WithSource(file.NewSource(path)))
    defer cfg.Close()

    if err := cfg.Load(); err != nil {
        return ServerConfig{}, err
    }

    var out ServerConfig
    if err := cfg.Value("server").Scan(&out); err != nil {
        return ServerConfig{}, err
    }
    return out, nil
}

配置示例:

{
  "server": {
    "http": { "addr": "0.0.0.0", "port": 8000 }
  }
}

日常开发只需要记住:New 创建配置、Load 加载、Value 读取单项、Scan 映射结构体、Watch 监听变化。


3. 构造器与 helper 速查

场景 API 说明 Example
创建配置实例 configx.New(opts...) 创建 Config,默认启用 JSON/YAML/XML/Proto codec ExampleNew
读取强类型值 configx.Get[T](cfg, key) 读取并转换成 T ExampleGet
启动期必须存在 configx.MustGet[T](cfg, key) 不存在或类型错误时 panic ExampleMustGet
有默认值读取 configx.GetOrDefault[T](cfg, key, fallback) 缺失或转换失败时返回 fallback ExampleGetOrDefault
cfg := configx.New(configx.WithSource(file.NewSource("configs/app.yaml")))
_ = cfg.Load()
addr := configx.MustGet[string](cfg, "server.http.addr")
port := configx.GetOrDefault[int](cfg, "server.http.port", 8000)

Get[T] 原生支持 bool / int / int64 / float64 / string 与任意带 json tag 的 struct。其他类型请用 Value(key).Scan(&v)


4. Option 列表

构造时按需附加:

configx.WithSource(src...)                 // 配置来源:file/env/remote source
configx.WithDecoder(decoder)               // 自定义 KeyValue 解码逻辑
configx.WithResolver(resolver)             // 自定义占位符解析逻辑
configx.WithResolveActualTypes(true)       // ${PORT} 解析为 int/bool/float 等实际类型
configx.WithMergeFunc(merge)               // 自定义多 source 合并策略

推荐顺序:基础文件 source 放前面,覆盖层 source 放后面。

cfg := configx.New(configx.WithSource(
    file.NewSource("configs/common.yaml"),
    file.NewSource("configs/dev.yaml"),
    env.NewSource("KERNEL_"),
))

后面的 source 会覆盖前面的叶子值,但嵌套 map 会递归合并。


5. Source / Watcher 契约

Source 是配置输入适配器:

type Source interface {
    Load() ([]*KeyValue, error)
    Watch() (Watcher, error)
}

Watcher 是热更新适配器:

type Watcher interface {
    Next() ([]*KeyValue, error)
    Stop() error
}

内置 source:

Source 场景
文件 configx/file 本地 JSON/YAML/XML/Proto 配置
环境变量 configx/env 容器部署、CI/CD 注入
Apollo contrib/config/apollo Apollo 配置中心
Nacos contrib/config/nacos Nacos 配置中心
etcd contrib/config/etcd etcd KV 配置
Consul contrib/config/consul Consul KV 配置
Kubernetes contrib/config/kubernetes ConfigMap / Secret
Polaris contrib/config/polaris Polaris 配置中心

每个 contrib source 都返回 configx.Source,所以可以无缝替换 file/env。


6. 合并规则

默认 merge 是"嵌套 map 递归合并,叶子值后者覆盖前者":

// common.json
{ "server": { "addr": "0.0.0.0", "port": 8000 } }

// prod.json
{ "server": { "port": 9000 } }

最终结果:

{ "server": { "addr": "0.0.0.0", "port": 9000 } }

数组不是递归合并,而是整体覆盖。这样可以避免数组项按下标合并导致不可预测结果。

可以用 WithMergeFunc 替换默认合并策略,例如实现"深合并并保留数组顺序"或"按 key 合并数组"。


7. 占位符解析

默认占位符格式:

${KEY}
${KEY:default}
${server.http.addr}

例子:

{
  "HOST": "127.0.0.1",
  "PORT": "8000",
  "server": {
    "addr": "${HOST}:${PORT}",
    "mode": "${MODE:dev}"
  }
}

解析结果:

{
  "server": {
    "addr": "127.0.0.1:8000",
    "mode": "dev"
  }
}

如果启用 WithResolveActualTypes(true),当整个字段就是一个占位符时,"${PORT}" 会转换为 int,"${ENABLED}" 会转换为 bool,"${RATIO}" 会转换为 float。


8. Value 读取规则

Value 提供统一类型转换:

v := cfg.Value("server.port")
port, err := v.Int()
addr, err := cfg.Value("server.addr").String()
enabled, err := cfg.Value("feature.enabled").Bool()
timeout, err := cfg.Value("upstream.timeout_ns").Duration()
方法 支持输入 输出
Bool() bool/string/number bool
Int() int/uint/float/string int64
Float() int/uint/float/string float64
String() string/number/bool/[]byte/Stringer string
Duration() int/string time.Duration
Slice() []any []Value
Map() map[string]any map[string]Value
Scan() any JSON/proto-compatible value struct/proto

Value 实现了原子 Load/Store,所以同一 Value 对象在 Watch 回调刷新后,引用它的代码会自动看到新值(不重新调用 cfg.Value(key))。


9. Scan 结构体

推荐用 Scan 接收模块配置:

type LogConfig struct {
    Level  string `json:"level"`
    Format string `json:"format"`
}

var logCfg LogConfig
if err := cfg.Value("log").Scan(&logCfg); err != nil {
    return err
}

大型模块建议每个模块定义自己的 Config 结构体,例如 logx.Configdbx.Confighttpx.Config,启动时由 boot 层统一 Scan。

Config.Scan(v) 会把整棵配置树 marshal 成 JSON 再 unmarshal 进 v,所以 struct 必须有 json tag。


10. Watch 热更新

Watch 用于监听某个 key 的变化:

_ = cfg.Watch("log.level", func(key string, value configx.Value) {
    level, _ := value.String()
    logger.SetLevel(level)
})

本版本优化了旧实现的两个问题:

  1. 同一个 key 支持多个 observer。
  2. Reload 后会刷新已缓存的 Value,即使新旧值类型发生变化也会更新。

注意:Watch 是配置变化通知,不是业务事件总线。回调里不要做耗时操作,不要访问网络,不要阻塞。

Watch 要求 key 在注册时已存在;否则返回 ErrNotFound。这样能避免"启动期配置缺失但被静默忽略"的隐患。


11. 错误处理

常见错误:

错误 含义 处理方式
configx.ErrNotFound key 不存在 启动必选项直接失败,可选项用默认值
configx.ErrClosed Config 已关闭 不要复用已 Close 的 Config
configx.ErrInvalidObserver Watch 传入 nil observer 修复调用方
configx.ErrNilConfig Get/MustGet 传入 nil Config 修复启动注入

业务层不要把配置错误包装成业务 errorx。配置错误一般是启动期错误,应在 boot 层直接返回并终止进程。请求路径上的配置缺失通常意味着代码 bug,应直接 panic 或 fail-fast,不应让 errorx 处理。


12. 禁止模式

// ❌ 禁止:业务代码直接读环境变量
os.Getenv("DATABASE_DSN")

// ✅ 推荐:boot 层统一加载,业务拿结构体
cfg.Value("database.dsn").String()

// ❌ 禁止:每个模块自己解析 YAML
os.ReadFile("config.yaml")
yaml.Unmarshal(data, &cfg)

// ✅ 推荐:统一 Source + Scan
configx.New(configx.WithSource(file.NewSource("config.yaml")))

// ❌ 禁止:在 Watch 回调里做慢操作
cfg.Watch("x", func(string, configx.Value) { callRemoteAPI() })

// ✅ 推荐:回调只更新本地原子配置或发送轻量信号

// ❌ 禁止:把启动期 Load 错误吞掉
if err := cfg.Load(); err != nil {
    log.Printf("load failed: %v", err)  // 启动失败被吞
}

// ✅ 推荐:直接返回 / panic,让进程退出
if err := cfg.Load(); err != nil {
    return err
}

13. 测试建议

每个使用 configx 的模块至少要覆盖三类测试:

  1. 默认配置可以 Scan 到模块 Config。
  2. 缺失必选项能返回明确错误。
  3. 覆盖层配置能覆盖默认值。

如果模块支持热更新,再增加 Watch 测试。

测试代码可以使用 configx.New(configx.WithSource(exampleSource{...})) 直接构造内存 source,不写临时文件。参考 configx/example_test.go 顶部的 exampleSource 实现。


14. 本次工程化优化点

本轮从 config/ 迁移到 configx/,并做了这些工程化优化:

优化 旧问题 新行为
包本地化 目录仍叫 config,和 Go 常见变量名冲突 统一为 configx
缓存刷新 Load() 后已缓存 Value 可能还是旧值 Load() / Watch 后刷新缓存
类型变化 Watch 更新时新旧类型不同会跳过 Value 可跨类型 Store
observer 同一个 key 只能一个 observer 支持多个 observer
Close 重复 Close 可能返回底层错误 Close 幂等
clone reader clone 依赖 gob 使用递归 clone,减少类型脆弱性
helper 只有 Get[T] 新增 MustGet[T]GetOrDefault[T]
文档 README 太薄 按 skill 建立 README/doc/AI/examples/contracts
测试 只覆盖基本路径 新增 contract / integration / coverage_edge / benchmark / fuzz

15. 迁移指南:config → configx

旧代码(已删除):

import "github.com/aisphereio/kernel/config"
import "github.com/aisphereio/kernel/config/file"

cfg := config.New(config.WithSource(file.NewSource("app.yaml")))

新代码:

import "github.com/aisphereio/kernel/configx"
import "github.com/aisphereio/kernel/configx/file"

cfg := configx.New(configx.WithSource(file.NewSource("app.yaml")))

如果是 contrib source,也全部返回 configx.Source

src := nacos.NewConfigSource(client, nacos.WithDataID("app.yaml"))
cfg := configx.New(configx.WithSource(src))

迁移检查清单:

  • 全仓库搜索 kernel/config"kernel/config/ 已无业务代码引用
  • contrib/config/* 子模块的 import 路径全部更新
  • os.Getenv 在 business 代码中已替换为 cfg.Value
  • 启动期 Load 错误直接 fail-fast
  • defer cfg.Close() 已添加

16. 文档地图

configx 的文档分为四类,按需查阅:

快速上手
├── 本文件 (configx/README.md)              ← 单一入口
├── configx/doc.go                          ← go doc 输出源
└── configx/example_test.go                 ← Go 标准示例(go test -v 可看输出)

深度规范(架构师/PR review 时看)
├── docs/design/configx.md                  ← 设计规范
└── docs/contracts/configx.md               ← 不可破坏契约

AI 编码指南(AI 写业务代码时看)
├── docs/ai/configx.md                      ← 合并版 AI 指南
└── AGENTS.md                               ← 项目级 AI 规则

验收与运维(CI/CD 时看)
└── docs/process/configx-acceptance-checklist.md

可运行示例
├── examples/configx-basic/                 ← 最小示例:file + Scan
├── examples/configx-env/                   ← env + file 覆盖示例
└── examples/configx-watch/                 ← Watch 热更新示例

优先级:日常开发只看本 README + docs/ai/configx.md 即可。其他文档按场景查阅,无需通读。


17. Examples 索引(按场景查找)

构造器与 helper 示例
API Example 函数 何时用
New ExampleNew 创建配置实例
Get ExampleGet 读取强类型值
MustGet ExampleMustGet 启动期必需配置
GetOrDefault ExampleGetOrDefault 可选配置默认值
Option 示例
API Example 函数 何时用
WithSource ExampleWithSource 组合 file/env/remote source
WithDecoder ExampleWithDecoder 自定义格式解析
WithResolver ExampleWithResolver 自定义占位符解析
WithResolveActualTypes ExampleWithResolveActualTypes 占位符转换为实际类型
WithMergeFunc ExampleWithMergeFunc 自定义覆盖策略
Config / Value 示例
场景 Example 函数
读取单项 ExampleConfig_Value
Scan 结构体 ExampleConfig_Scan
Watch 热更新 ExampleConfig_Watch
Bool 转换 ExampleValue_Bool
Int 转换 ExampleValue_Int
Float 转换 ExampleValue_Float
String 转换 ExampleValue_String
Slice 读取 ExampleValue_Slice
Map 读取 ExampleValue_Map
局部 Scan ExampleValue_Scan
业务场景示例(10 个)
场景 Example 函数
启动 HTTP 服务 Example_businessBootstrapHTTPServer
文件 + 环境覆盖 Example_businessLayeredFileAndEnv
数据库配置 Scan Example_businessScanDatabaseConfig
Feature flag Example_businessFeatureFlag
上游 timeout Example_businessUpstreamTimeoutConfig
占位符解析 Example_businessResolvePlaceholder
typed placeholder Example_businessActualTypedPlaceholder
runtime change Example_businessObserveRuntimeChange
必需配置校验 Example_businessValidateRequiredConfig
自定义 dotenv decoder Example_businessCustomDecoderForDotEnv

18. 发版前检查

# 单元测试
go test ./configx ./configx/env ./configx/file

# 标准示例
go test ./configx -run=Example -v

# 模块文档完整性检查
./scripts/check-module-docs.sh configx

# 禁止模式扫描
./scripts/check-configx-usage.sh

# 种子模糊测试
go test ./configx -run=^$ -fuzz=FuzzConfigLoad -fuzztime=30s

Windows PowerShell 如果执行脚本被拦截,使用仓库里的 .cmd 包装脚本,或者用 powershell -ExecutionPolicy Bypass -File ... 临时绕过。


19. 设计哲学一句话

配置必须有 source。 配置必须可合并。 配置必须可观测。 配置必须可热更新。 configx 只定义加载与读取语义,不负责校验、不负责服务、不负责审计。

Documentation

Overview

Package configx provides the unified configuration API for Aisphere Kernel.

configx is the ONLY runtime configuration reader in Kernel. It replaces the old Kratos-derived config package and ad-hoc os.Getenv / yaml.Unmarshal patterns. Business code should depend on Config or a module-specific struct produced by Config.Scan; it should not call os.Getenv or parse YAML/JSON directly.

Design principle

configx only LOADS, MERGES, and RESOLVES configuration. It does NOT validate business fields, start services, record audit, or bind to a specific config center SDK. Other Kernel modules (logx, dbx, httpx, grpcx, authx) consume configx through the stable Config / Value / Scan APIs.

configx depends only on the Go standard library plus the encodingx and logx packages; it does not import third-party config libraries.

30-second quickstart

The common startup flow is:

cfg := configx.New(configx.WithSource(file.NewSource("configs/app.yaml")))
defer cfg.Close()

if err := cfg.Load(); err != nil {
    return err
}

addr := configx.MustGet[string](cfg, "server.http.addr")
port := configx.GetOrDefault[int](cfg, "server.http.port", 8000)

Use Value when a single key is needed:

enabled, err := cfg.Value("features.new_home").Bool()
timeout, err := cfg.Value("upstream.timeout_ns").Duration()

Use Scan when a module owns a structured configuration section:

type HTTPConfig struct {
    Addr string `json:"addr"`
    Port int    `json:"port"`
}

var httpCfg HTTPConfig
if err := cfg.Value("server.http").Scan(&httpCfg); err != nil {
    return err
}

Sources

A Source turns a backend into a slice of KeyValue values:

type Source interface {
    Load() ([]*KeyValue, error)
    Watch() (Watcher, error)
}

Built-in local sources live in subpackages:

file.NewSource("configs/app.yaml")
env.NewSource("KERNEL_")

Remote and platform sources live under contrib/config:

contrib/config/apollo
contrib/config/nacos
contrib/config/etcd
contrib/config/consul
contrib/config/kubernetes
contrib/config/polaris

Each contrib source returns configx.Source so it can be dropped into WithSource without adapting business code.

Merge order

Sources are loaded in the order passed to WithSource. Later sources override earlier leaf values while nested maps are merged recursively. Arrays are replaced as a whole. The recommended order is defaults first, environment or deployment-specific overrides last:

cfg := configx.New(configx.WithSource(
    file.NewSource("configs/common.yaml"),
    file.NewSource("configs/prod.yaml"),
    env.NewSource("KERNEL_"),
))

Placeholder resolution

The default resolver expands placeholders in string values:

${KEY}
${KEY:default}
${server.http.addr}

WithResolveActualTypes(true) converts a whole-value placeholder into bool, int64, or float64 when possible. Mixed strings remain strings:

"${PORT}"          -> 8080
"http://:${PORT}"  -> "http://:8080"

Placeholders are resolved after merge, so a placeholder in source A can reference a value defined in source B.

Value API

Value provides typed conversions. Each method returns (T, error); none panic on type mismatch:

Bool()      bool-like values (bool / "true" / "false" / numbers)
Int()       integer-like values (int / uint / float / numeric string)
Float()     floating-point values (int / uint / float / numeric string)
String()    string representation (any convertible type)
Duration()  time.Duration from integer nanoseconds
Slice()     []Value from []any
Map()       map[string]Value from map[string]any
Scan(any)   JSON/proto-compatible struct scan

Value is backed by an atomic store, so a cached Value reference will reflect updates after Watch or Load without needing to re-call Config.Value.

Helper API

Get[T](cfg, key)              returns a typed value and an error
MustGet[T](cfg, key)          panics on error; use only at startup
GetOrDefault[T](cfg, key, v)  returns fallback on missing/invalid values

Get supports bool / int / int64 / float64 / string natively. Other types fall through to Value.Scan.

Watch

Watch registers one or more observers for a key. Observers are called when a loaded or watched config update changes that key. Observer callbacks should be short and non-blocking; use them to update local atomic settings or send a lightweight signal, not to perform network calls.

cfg.Watch("log.level", func(_ string, v Value) {
    level, _ := v.String()
    logLevel.Store(level)
})

Watch requires the key to exist at registration time; this fails fast on startup misconfiguration rather than silently never firing.

Errors

ErrNotFound         key is absent
ErrClosed           Config was closed
ErrInvalidObserver  Watch was called with nil observer
ErrNilConfig        helper received a nil Config

These errors are not business errors. Do not wrap them in errorx; surface them at startup as fail-fast failures.

Forbidden patterns

Do not read os.Getenv in business code. Do not parse YAML/JSON inside every module. Do not hide config errors by silently using zero values for required fields. Do not do expensive work in Watch callbacks.

Test code is exempt: it may use os.Setenv to construct env source fixtures.

Migration from old config package

Replace:

import "github.com/aisphereio/kernel/config"
cfg := config.New(config.WithSource(file.NewSource("app.yaml")))

with:

import "github.com/aisphereio/kernel/configx"
cfg := configx.New(configx.WithSource(file.NewSource("app.yaml")))

The old config/ package has been removed; new code must use configx/ and its subpackages configx/file and configx/env.

Documentation

See configx/README.md for the single-source-of-truth user guide, and docs/ai/configx.md for the AI coding recipe. docs/contracts/configx.md lists behaviors that cannot change without a major version bump.

Example (BusinessActualTypedPlaceholder)
cfg := New(
	WithSource(exampleSource{format: "json", data: `{"PORT":"9000","server":{"port":"${PORT}"}}`}),
	WithResolveActualTypes(true),
)
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[int](cfg, "server.port") + 1)
Output:
9001
Example (BusinessBootstrapHTTPServer)
cfg := New(WithSource(exampleSource{format: "json", data: `{"server":{"http":{"addr":":8000"}}}`}))
defer cfg.Close()
_ = cfg.Load()

addr := MustGet[string](cfg, "server.http.addr")
fmt.Println("listen", addr)
Output:
listen :8000
Example (BusinessCustomDecoderForDotEnv)
decoder := func(kv *KeyValue, target map[string]any) error {
	for _, line := range strings.Split(string(kv.Value), "\n") {
		key, value, ok := strings.Cut(line, "=")
		if ok {
			target[key] = value
		}
	}
	return nil
}
cfg := New(
	WithSource(exampleSource{key: ".env", data: "APP_NAME=kernel\nAPP_ENV=dev"}),
	WithDecoder(decoder),
)
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "APP_NAME"), MustGet[string](cfg, "APP_ENV"))
Output:
kernel dev
Example (BusinessFeatureFlag)
cfg := New(WithSource(exampleSource{format: "json", data: `{"features":{"new_home":"true"}}`}))
defer cfg.Close()
_ = cfg.Load()

enabled, _ := cfg.Value("features.new_home").Bool()
fmt.Println("new_home", enabled)
Output:
new_home true
Example (BusinessLayeredFileAndEnv)
cfg := New(WithSource(
	exampleSource{format: "json", data: `{"app":{"name":"kernel","env":"dev"}}`},
	exampleSource{key: "app.env", data: "prod"},
))
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "app.name"), MustGet[string](cfg, "app.env"))
Output:
kernel prod
Example (BusinessObserveRuntimeChange)
cfg := New(WithSource(exampleSource{format: "json", data: `{"log":{"level":"info"}}`}))
defer cfg.Close()
_ = cfg.Load()

_ = cfg.Watch("log.level", func(_ string, value Value) {
	level, _ := value.String()
	fmt.Println("reload", level)
})
cfg.(*config).Value("log.level").Store("debug")
cfg.(*config).notify("log.level", cfg.Value("log.level"))
Output:
reload debug
Example (BusinessResolvePlaceholder)
cfg := New(WithSource(exampleSource{format: "json", data: `{"HOST":"127.0.0.1","server":{"addr":"${HOST}:8000"}}`}))
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "server.addr"))
Output:
127.0.0.1:8000
Example (BusinessScanDatabaseConfig)
cfg := New(WithSource(exampleSource{format: "json", data: `{"database":{"driver":"postgres","max_idle":8}}`}))
defer cfg.Close()
_ = cfg.Load()

var db struct {
	Driver  string `json:"driver"`
	MaxIdle int    `json:"max_idle"`
}
_ = cfg.Value("database").Scan(&db)
fmt.Println(db.Driver, db.MaxIdle)
Output:
postgres 8
Example (BusinessUpstreamTimeoutConfig)
cfg := New(WithSource(exampleSource{format: "json", data: `{"upstream":{"timeout_ns":5000000000}}`}))
defer cfg.Close()
_ = cfg.Load()

timeout, _ := cfg.Value("upstream.timeout_ns").Duration()
fmt.Println(timeout.String())
Output:
5s
Example (BusinessValidateRequiredConfig)
cfg := New(WithSource(exampleSource{format: "json", data: `{}`}))
defer cfg.Close()
_ = cfg.Load()

_, err := Get[string](cfg, "jwt.secret")
fmt.Println(err == ErrNotFound)
Output:
true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned when a key does not exist in the loaded config tree.
	ErrNotFound = errors.New("configx: key not found")
	// ErrClosed is returned when a closed Config is used for a new load/watch operation.
	ErrClosed = errors.New("configx: config is closed")
	// ErrInvalidObserver is returned when Watch is called with a nil observer.
	ErrInvalidObserver = errors.New("configx: observer is nil")
	// ErrNilConfig is returned by helper functions when the Config argument is nil.
	ErrNilConfig = errors.New("configx: config is nil")
)

Functions

func Get

func Get[T any](c Config, key string) (T, error)

Get retrieves a config value by key and scans it into the target type.

Example
cfg := New(WithSource(exampleSource{
	format: "json",
	data:   `{"server":{"port":8080}}`,
}))
defer cfg.Close()
_ = cfg.Load()

port, _ := Get[int](cfg, "server.port")
fmt.Println(port)
Output:
8080

func GetOrDefault

func GetOrDefault[T any](c Config, key string, fallback T) T

GetOrDefault returns a typed value if present; otherwise it returns fallback.

Example
cfg := New(WithSource(exampleSource{
	format: "json",
	data:   `{"server":{"addr":":8000"}}`,
}))
defer cfg.Close()
_ = cfg.Load()

fmt.Println(GetOrDefault[int](cfg, "server.port", 8000))
Output:
8000

func MustGet

func MustGet[T any](c Config, key string) T

MustGet returns a typed value or panics. Use it only during process startup.

Example
cfg := New(WithSource(exampleSource{
	format: "json",
	data:   `{"service":{"name":"aihub"}}`,
}))
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "service.name"))
Output:
aihub

Types

type Config

type Config interface {
	Load() error
	Scan(v any) error
	Value(key string) Value
	Watch(key string, o Observer) error
	Close() error
}

Config is the runtime configuration interface used by kernel modules and apps.

func New

func New(opts ...Option) Config

New creates a Config from one or more sources.

Example
cfg := New(WithSource(exampleSource{
	format: "json",
	data:   `{"app":{"name":"hub"}}`,
}))
defer cfg.Close()

_ = cfg.Load()
name, _ := Get[string](cfg, "app.name")
fmt.Println(name)
Output:
hub

type Decoder

type Decoder func(*KeyValue, map[string]any) error

Decoder is config decoder.

type KeyValue

type KeyValue struct {
	Key    string
	Value  []byte
	Format string
}

KeyValue is config key value.

type Merge

type Merge func(dst, src any) error

Merge is config merge func.

type Observer

type Observer func(string, Value)

Observer is invoked when a watched config key changes.

type Option

type Option func(*options)

Option is config option.

func WithDecoder

func WithDecoder(d Decoder) Option

WithDecoder with config decoder. DefaultDecoder behavior: If KeyValue.Format is non-empty, then KeyValue.Value will be deserialized into map[string]interface{} and stored in the config cache(map[string]interface{}) if KeyValue.Format is empty,{KeyValue.Key : KeyValue.Value} will be stored in config cache(map[string]interface{})

Example
decoder := func(kv *KeyValue, target map[string]any) error {
	parts := strings.SplitN(string(kv.Value), "=", 2)
	if len(parts) == 2 {
		target[parts[0]] = parts[1]
	}
	return nil
}
cfg := New(
	WithSource(exampleSource{key: "app.name", data: "name=kernel"}),
	WithDecoder(decoder),
)
defer cfg.Close()
_ = cfg.Load()

name, _ := Get[string](cfg, "name")
fmt.Println(name)
Output:
kernel

func WithMergeFunc

func WithMergeFunc(m Merge) Option

WithMergeFunc with config merge func.

Example
replaceMerge := func(dst, src any) error {
	d := dst.(*map[string]any)
	*d = src.(map[string]any)
	return nil
}
cfg := New(
	WithSource(
		exampleSource{format: "json", data: `{"name":"first"}`},
		exampleSource{format: "json", data: `{"name":"second"}`},
	),
	WithMergeFunc(replaceMerge),
)
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "name"))
Output:
second

func WithResolveActualTypes

func WithResolveActualTypes(enableConvertToType bool) Option

WithResolveActualTypes with config resolver. bool input will enable conversion of config to data types

Example
cfg := New(
	WithSource(exampleSource{
		format: "json",
		data:   `{"PORT":"8080","server":{"port":"${PORT}"}}`,
	}),
	WithResolveActualTypes(true),
)
defer cfg.Close()
_ = cfg.Load()

port, _ := Get[int](cfg, "server.port")
fmt.Printf("%T %d\n", port, port)
Output:
int 8080

func WithResolver

func WithResolver(r Resolver) Option

WithResolver with config resolver.

Example
resolver := func(values map[string]any) error {
	values["service"] = map[string]any{"name": "resolved"}
	return nil
}
cfg := New(
	WithSource(exampleSource{format: "json", data: `{}`}),
	WithResolver(resolver),
)
defer cfg.Close()
_ = cfg.Load()

fmt.Println(MustGet[string](cfg, "service.name"))
Output:
resolved

func WithSource

func WithSource(s ...Source) Option

WithSource with config source.

Example
cfg := New(WithSource(
	exampleSource{format: "json", data: `{"app":{"name":"kernel"}}`},
	exampleSource{format: "json", data: `{"app":{"env":"dev"}}`},
))
defer cfg.Close()
_ = cfg.Load()

name, _ := Get[string](cfg, "app.name")
env, _ := Get[string](cfg, "app.env")
fmt.Println(name, env)
Output:
kernel dev

type Reader

type Reader interface {
	Merge(...*KeyValue) error
	Value(string) (Value, bool)
	Source() ([]byte, error)
	Resolve() error
}

Reader is the internal merged config tree reader.

type Resolver

type Resolver func(map[string]any) error

Resolver resolve placeholder in config.

type Source

type Source interface {
	Load() ([]*KeyValue, error)
	Watch() (Watcher, error)
}

Source is config source.

type Value

type Value interface {
	Bool() (bool, error)
	Int() (int64, error)
	Float() (float64, error)
	String() (string, error)
	Duration() (time.Duration, error)
	Slice() ([]Value, error)
	Map() (map[string]Value, error)
	Scan(any) error
	Load() any
	Store(any)
}

Value is a typed view over one config value.

type Watcher

type Watcher interface {
	Next() ([]*KeyValue, error)
	Stop() error
}

Watcher watches a source for changes.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL