appconfig

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 9 Imported by: 0

README

go-config-manager

Go Reference

Per-app configuration stored as JSON in the user config directory. Zero dependencies.


For Users

Install

go get github.com/mesopix/go-config-manager
import "github.com/mesopix/go-config-manager"

Usage

Each Manager holds one config. Create a manager, assemble it once at startup, then use the *Config returned by Load:

//go:embed default_config.json
var defaultConfigJSON []byte

m := appconfig.NewManager()

// Optional: assemble the storage path. Empty arguments fall back to defaults
// (<user config dir> / executable name / config.json).
if err := m.Init("", "myapp", ""); err != nil {
    log.Fatal(err)
}

// Optional: register the first-run template (validated immediately,
// must be a JSON object).
if err := m.RegisterDefaults(defaultConfigJSON); err != nil {
    log.Fatal(err)
}

c, err := m.Load() // idempotent; later calls return the same *Config
if err != nil {
    // 配置文件损坏时返回 *CorruptConfigError,不提供默认值降级;
    // 调用方应打印错误并退出,或引导用户修复。
    var corruptErr *appconfig.CorruptConfigError
    if errors.As(err, &corruptErr) {
        fmt.Fprintf(os.Stderr, "config file %s is corrupt: %v\n", corruptErr.Path, corruptErr.Err)
        os.Exit(1)
    }
    log.Fatal(err)
}

port, _ := c.Get("port")
c.Set("port", 9090)
if err := c.Save(); err != nil { ... }

With no Init call the path defaults to <user config dir>/<executable name>/config.json; on first run the file is created from the registered template. c.Path() returns the absolute path (useful for editor integrations or error messages).

Path assembly rules (Init):

argument meaning default
firstDir level-1 directory, absolute path os.UserConfigDir()
secondDir level-2 name, may contain separators to nest executable name without extension
fileName config file name config.json

firstDir must be an absolute path; secondDir must be relative and must not climb out of firstDir (.. is rejected); fileName must be a plain file name. Init and RegisterDefaults may each succeed only once per manager — repeats return an error (failed attempts do not consume the slot).

If the config file does not exist and no template was registered, Load returns an error instead of silently creating an empty config.

Note: JSON numbers come back as float64 from Get.

Struct binding

For typed access, use DecodeFields / SetFieldsFrom with a struct whose fields carry json tags:

type Settings struct {
    Host string  `json:"host"`
    Port float64 `json:"port"`
}

var s Settings
if err := c.DecodeFields(&s); err != nil { ... }
s.Port = 9090
if err := c.SetFieldsFrom(s); err != nil { ... }
if err := c.Save(); err != nil { ... }

Missing keys leave target fields at their zero value; pointer fields stay nil when absent and become non-nil when explicitly set (even to the zero value), preserving the "unset vs explicit zero" distinction.

Schema validation
schema := appconfig.Schema{
    "host": {Type: appconfig.TypeString, Required: true},
    "port": {Type: appconfig.TypeFloat, Default: float64(3000)},
}

switch c.Check(schema) {
case appconfig.Valid:
    // nothing to do
case appconfig.MissingDefaults, appconfig.ExtraFields, appconfig.MissingAndExtra:
    if err := c.Normalize(schema); err != nil { ... }
    if err := c.Save(); err != nil { ... }
case appconfig.Invalid:
    log.Fatal("required field missing or type mismatch")
}

Normalize fills missing defaults and removes extra fields; it is a no-op when already Valid.

Versioning

Configs carry a version in meta.version. Declare the version your program ships with and register per-version hooks; Load then validates or upgrades the disk file automatically:

if err := m.SetCurrentVersion("2"); err != nil { ... } // this program uses v2
if err := m.RegisterValidator("2", func(fields map[string]any) error {
    if port, ok := fields["port"].(float64); !ok || port > 65535 {
        return errors.New("port out of range")
    }
    return nil
}); err != nil { ... }
if err := m.RegisterUpgrader("1", "2", func(fields map[string]any) (map[string]any, error) {
    fields["theme"] = "dark" // new in v2; existing fields are preserved
    return fields, nil
}); err != nil { ... }

c, err := m.Load()
var upErr *appconfig.UpgradeFailedError
if errors.As(err, &upErr) {
    // Upgrade dry-run failed; the disk file is untouched. Ask the user,
    // then optionally reset to the registered template (user config is lost):
    if err := m.Reset(); err != nil { ... }
    c, err = m.Load()
}
if err != nil { ... }

On Load, a disk file already at the current version is checked with that version's validator (failure → *CorruptConfigError). An older version reachable through the registered upgraders is upgraded in memory first (each step keeps existing fields), validated, and only then written back atomically; any failure returns *UpgradeFailedError and leaves the disk untouched. A version that cannot reach the current one — including a newer disk version — is an error; there is no downgrade. The registered template's meta.version must equal the current version, so first runs and Reset always write the right shape. UnknownVersion ("UNKNOWN") can be used as an upgrader's from to migrate versionless legacy files. Without SetCurrentVersion none of this runs and Load behaves as before.

CLI integration

HandleCLI lets your binary handle a config subcommand. Call it with os.Args[1:] before your normal flow, after RegisterDefaults (the CLI reads the same Manager instance; calling it before Load means this run sees any --edit repairs); when the first argument is config, the library takes over that argument and everything after it:

if shouldClose, err := m.HandleCLI(os.Args[1:]); shouldClose {
    if err != nil {
        fmt.Fprintln(os.Stderr, err) // usage is embedded in the error
        os.Exit(1)
    }
    return // config 已处理,输出已由库打印;跳过正常业务流程

}

Supported subcommands:

  • config — opens the interactive panel registered with SetPanel. The panel is entirely client-defined: it receives the loaded *Config (first-run creation and version upgrades have already run) and handles its own UI and saving. Without a registered panel this is a usage error.
  • config --edit — opens the config file in the user's editor and blocks until it exits, then re-reads the file and validates that it is still a JSON object. Invalid edits are reported with the parse error and the bad content is left untouched for another fix attempt. A missing config file is created from the template first; a corrupt file is opened as-is so the user can fix it by hand.

Editor selection: $VISUAL$EDITOR (may include arguments, e.g. code -w), falling back to the platform default (notepad on Windows, open -W on macOS, xdg-open elsewhere). GUI fallbacks may return before the editor actually closes; set $EDITOR for reliable blocking and post-edit validation.

Anything else after config (including a bare config) returns shouldClose = true with an error whose message contains a short usage text.

Repair (not yet implemented)

Manager.Repair() is reserved for future versions. It currently returns an error indicating the feature is not implemented.

API Reference

Full API documentation is available on pkg.go.dev.


For Developers

Project Structure

.
├── config.go           # Manager: NewManager, Init/RegisterDefaults/Load/Repair;
│                       # Config, Get/Set/Save,
│                       # DecodeFields/SetFieldsFrom, Check/Normalize, Path,
│                       # CorruptConfigError (Repair is a stub)
├── schema.go           # Schema types, Check, Normalize (standalone)
├── version.go          # Versioning: SetCurrentVersion, RegisterValidator,
│                       # RegisterUpgrader, SetPanel, Reset,
│                       # UpgradeFailedError, applyVersioning (dry-run core)
├── cli.go              # CLI takeover: HandleCLI dispatch, config (panel),
│                       # config --edit (editor launch + post-edit validation)
├── config_test.go      # Internal tests (load, save)
├── version_test.go     # Internal tests (validation, upgrade chain, Reset)
├── cli_test.go         # Internal tests (CLI, fake editor/panel injection)
├── api_test.go         # External black-box tests
└── examples/
    └── demo/
        ├── default_config.json  # Embedded default config template (v2)
        ├── schema.json          # Embedded schema definition
        └── main.go              # Runnable demo (versioning + panel + fallback)

Single package at module root, zero external dependencies.

Development Guide

Run tests and static analysis:

go test -v ./...
go vet ./...

Tests isolate themselves with a fresh NewManager() + Init pointed at t.TempDir(), so they never touch real user config; only the lazy-assembly tests override AppData / XDG_CONFIG_HOME / HOME via t.Setenv.

Design Decisions

  • One manager, one config: NewManager() returns an independent Manager holding its own path assembly, registered template, and loaded *Config; Load() is idempotent per manager. Managers pointing at the same path share no file lock — last Save wins — and a manager embeds a mutex, so use it by pointer and never copy it.
  • Executable name is the default subdirectory: With no Init, the config lives under <user config dir>/<executable name>/. Renaming the binary therefore changes the default path — the old config "disappears" under the old name, and test binaries get their own namespace automatically. Pass an explicit secondDir via Init when a stable path matters. (This deliberately overturned the library's earlier "never auto-detect" contract.)
  • Re-read after first save: On first run, Load writes the registered template to disk then reads it back. This ensures numeric types are always float64 (matching subsequent runs), avoiding subtle type mismatches between first and later launches.
  • Corrupt files fail loudly: When an existing config file cannot be read or parsed, Load returns nil and a *CorruptConfigError (carrying the file path and original error). It does NOT fall back to defaults — callers must surface the error to the user rather than silently continuing with stale defaults. The bad file is never overwritten; Repair() is reserved for future repair workflows.
  • Versioned configs upgrade in memory first: With SetCurrentVersion declared, Load validates or upgrades the disk config. Upgrades run as an in-memory dry-run along the registered chain (each from has at most one step; loops are detected); only after the final validator passes is the new version written back atomically. A failed dry-run returns *UpgradeFailedError and never touches the disk — the reset-to-template fallback (Reset) is the caller's decision to make with the user. The template's meta.version must match the current version so first runs and Reset always write the right shape. Programs that never call SetCurrentVersion keep the original behavior untouched.
  • Assembly is guarded, instances are not: Manager assembly methods (Init/RegisterDefaults/Load/HandleCLI) serialize on the manager's own mutex; the *Config methods (Get/Set/Save/...) are not synchronized — callers sharing a config across goroutines must lock themselves.
  • Atomic save: Save() writes to a temp file in the same directory, calls Sync() to flush to disk, then Renames over the target. A crash during save never leaves a half-written config.
  • CLI takeover is opt-in per invocation: HandleCLI only acts when args[0] is exactly config; otherwise the client's normal flow is untouched. --edit reuses the first-run creation flow for missing files and opens an existing corrupt file as-is — manual repair is that command's purpose. After the editor exits, the file must still be a JSON object or the command fails without touching the content.
  • Zero dependencies: Only stdlib (encoding/json, os, os/exec, path/filepath, runtime, sync). Keeps the dependency tree minimal for a utility library.

Release Process

  1. Ensure all tests pass: go test -v ./... && go vet ./...

  2. Commit all changes to main.

  3. Create and push a semantic version tag:

    git tag v0.x.y
    git push origin main --tags
    
  4. pkg.go.dev will automatically pick up the new version within minutes.

⚠️ Published tags are immutable — never delete or move them. Use a new version number for any change.

Documentation

Overview

包 appconfig 是一个基于 JSON 文件的轻量级配置存储库。

实例用法:NewManager 创建一个 Manager,经 Init(可选,装配存储路径)与 RegisterDefaults(可选,注册首运模板)完成装配,Load 返回加载好的配置 对象,后续所有读写都作用在这个对象上:

m := appconfig.NewManager()
m.Init("", "myapp", "config.json") // 可选;空参数用缺省值
m.RegisterDefaults(defaultJSON)    // 可选;//go:embed 的模板
c, err := m.Load()
c.Set("port", 9090)
c.Save()

版本机制:客户端按版本注册校验函数与升级函数,声明程序内置的当前版本, Load 时由库按需校验或逐级升级磁盘配置;升级失败的兜底是 Reset 模板覆盖。

Index

Constants

View Source
const UnknownVersion = "UNKNOWN"

UnknownVersion 表示无法识别的数据版本号。

Variables

This section is empty.

Functions

This section is empty.

Types

type CheckResult added in v0.3.0

type CheckResult int

CheckResult 表示 data 相对于 schema 的校验状态。

const (
	Valid           CheckResult = iota // 严格符合 schema
	MissingDefaults                    // 仅缺少带默认值的非必填字段
	ExtraFields                        // 仅有 schema 未定义的多余字段
	MissingAndExtra                    // 既缺带默认值的字段,又有多余字段
	Invalid                            // 必填缺失或类型不匹配
)

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config 持有以磁盘 JSON 文件为后端的配置值。 data 存储完整的 {meta, fields} 两层结构。 实例方法未做并发同步:多 goroutine 共享时由调用方自行加锁。

func (*Config) Check added in v0.5.0

func (c *Config) Check(schema Schema) CheckResult

Check 用 schema 校验当前 fields 层,返回校验状态。 等价于在 fields() 上调用 Schema.Check,但无需手动提取 map。

func (*Config) DeclaredVersion added in v0.4.0

func (c *Config) DeclaredVersion() string

DeclaredVersion 返回 config.json 中声明的数据版本号。 无法识别时返回 UnknownVersion。

func (*Config) DecodeFields added in v0.5.0

func (c *Config) DecodeFields(target any) error

DecodeFields 将 fields 层按 JSON tag 解码到 target(必须为指针)。 经 JSON 往返实现:fields 中缺失的键不会改动 target 的对应字段, 因此指针字段可区分"未设置"(nil)与"显式零值"(指向零值的指针)。

func (*Config) Get

func (c *Config) Get(key string) (any, bool)

Get 返回 fields 层中 key 下存储的值以及它是否存在。 JSON 数值会被反序列化为 float64。

func (*Config) Meta added in v0.4.0

func (c *Config) Meta() map[string]any

Meta 返回 data 中 "meta" 层的 map(只读访问)。

func (*Config) Normalize added in v0.5.0

func (c *Config) Normalize(schema Schema) error

Normalize 按 schema 规范化当前 fields 层:补全缺失的默认值、删除多余字段。 Valid 状态下为 no-op(直接返回 nil);MissingDefaults / ExtraFields / MissingAndExtra 状态下执行规范化并写回;Invalid 状态返回错误。

func (*Config) Path added in v0.5.0

func (c *Config) Path() string

Path 返回配置文件的绝对路径。

func (*Config) ResolveVersion added in v0.4.0

func (c *Config) ResolveVersion(schemaVersion string)

ResolveVersion 在 schema 校验通过后调用,将 resolvedVersion 设为 schema 的 meta.version。若 schemaVersion 为空则设为 UnknownVersion。

func (*Config) ResolvedVersion added in v0.4.0

func (c *Config) ResolvedVersion() string

ResolvedVersion 返回经 schema 校验后确定的实际数据版本号。 未经校验或无法识别时返回 UnknownVersion。

func (*Config) Save

func (c *Config) Save() error

Save 以原子方式把当前值写回 JSON 文件。 先写入临时文件并同步到磁盘,再重命名覆盖目标文件, 这样即使中途崩溃也不会留下写了一半的配置。

func (*Config) Set

func (c *Config) Set(key string, value any)

Set 将 value 存储到 fields 层的 key 下。

func (*Config) SetFieldsFrom added in v0.5.0

func (c *Config) SetFieldsFrom(source any) error

SetFieldsFrom 将 source 按 JSON tag 编码后整体替换 fields 层,meta 层不受影响。 source 为 nil 或无法编码为 JSON 对象时返回错误。

type CorruptConfigError added in v0.5.0

type CorruptConfigError struct {
	Path string // 配置文件的绝对路径
	Err  error  // 原始读取或解析错误
}

CorruptConfigError 表示已存在的配置文件无法读取或解析。 调用方应用 errors.As 识别本错误,向用户报错并退出,不得静默改用默认值。

func (*CorruptConfigError) Error added in v0.5.0

func (e *CorruptConfigError) Error() string

Error 实现 error 接口,信息携带配置文件路径与原始错误。

func (*CorruptConfigError) Unwrap added in v0.5.0

func (e *CorruptConfigError) Unwrap() error

Unwrap 返回原始错误,支持 errors.Is/As 链式判断。

type FieldDef added in v0.3.0

type FieldDef struct {
	Type     FieldType
	Required bool
	Default  any // nil 表示无默认值;仅当 Required 为 false 时有意义
}

FieldDef 描述一个配置键:期望类型、是否必填,以及可选的默认值(仅在非必填时生效)。

type FieldType added in v0.3.0

type FieldType int

FieldType 表示配置字段期望的 JSON 类型。

const (
	TypeString FieldType = iota // JSON 字符串
	TypeFloat                   // JSON 数值(float64)
	TypeBool                    // JSON 布尔值
	TypeArray                   // JSON 数组
	TypeObject                  // JSON 对象
)

type Manager added in v0.6.0

type Manager struct {
	// contains filtered or unexported fields
}

Manager 装配并持有一份配置:存储路径、首运模板与已加载的配置对象。 装配方法(Init/RegisterDefaults/Load/HandleCLI)在实例互斥锁上串行化, 未导出辅助方法由调用方持锁。内含 sync.Mutex:勿按值复制,始终通过指针使用。 指向同一路径的多个 Manager 之间没有文件锁,交叉写回时最后 Save 者胜出。

func NewManager added in v0.6.0

func NewManager() *Manager

NewManager 创建一个未装配的 Manager。

func (*Manager) HandleCLI added in v0.6.0

func (m *Manager) HandleCLI(args []string) (shouldClose bool, err error)

HandleCLI 接管客户端命令行中的 config 子命令。 args 应传入 os.Args[1:]:当 args[0] 恰为 "config" 时,接管该参数及其后的 所有参数并返回 shouldClose = true,客户端应跳过正常流程——出错时打印 err 并以非零码退出,否则直接退出(--edit 的用户反馈由库自己输出)。 配置文件位置与首运模板来自该 Manager 实例的装配(Init/RegisterDefaults), 未装配时使用缺省值。不是以 config 开头时返回 shouldClose = false,不做任何事。 推荐在 Load 之前调用:--edit 的修复结果可被本次运行的 Load 直接读到。

func (*Manager) Init added in v0.6.0

func (m *Manager) Init(firstDir, secondDir, fileName string) error

Init 装配该 Manager 的存储路径,仅能在成功 Load 之前调用一次。 三个参数传空字符串时使用缺省值:

  • firstDir:配置文件一级目录(完整绝对路径),缺省为 os.UserConfigDir();
  • secondDir:二级目录名,可含路径分隔符实现嵌套,缺省为可执行文件名 (不含扩展名)。注意 exe 改名会改变缺省路径,需要稳定路径时显式传入;
  • fileName:配置文件名,缺省为 "config.json"。

firstDir 必须是绝对路径;secondDir 不得为绝对路径或包含 ".." 上跳成分; fileName 必须是纯文件名。重复调用或成功加载后调用返回错误; 懒装配 Load 失败(cfg 仍为 nil)后仍可 Init。

func (*Manager) Load added in v0.6.0

func (m *Manager) Load() (*Config, error)

Load 返回该 Manager 的配置对象(幂等,后续调用返回同一对象)。 首次调用完成路径装配并加载:文件不存在且已注册模板时按首运流程创建, 并从磁盘重读(保证数值类型与后续运行一致);文件存在但无法读取或解析时 返回 nil 和 *CorruptConfigError,不提供默认值降级,也不覆盖磁盘上的坏文件; 文件不存在且未注册模板时返回错误。 已声明当前版本(SetCurrentVersion)时,读入后按需校验或逐级升级磁盘配置, 模板版本必须与当前版本一致。 未调用 Init 时按全缺省值装配(用户配置目录 + 可执行文件名 + config.json)。

func (*Manager) RegisterDefaults added in v0.6.0

func (m *Manager) RegisterDefaults(defaultJSON []byte) error

RegisterDefaults 注册首运创建配置文件所用的默认模板,仅能注册一次。 defaultJSON 必须是合法的 JSON 对象,注册时立即校验,非法即报错; 校验失败不消耗"仅一次"名额,可修正后重试。

func (*Manager) RegisterUpgrader added in v0.6.0

func (m *Manager) RegisterUpgrader(from, to string, fn UpgraderFunc) error

RegisterUpgrader 注册从 from 版本到 to 版本的升级函数。 同一 from 仅能注册一条路径(升级链不得分叉),from == to 拒绝。 客户端可用 UnknownVersion 作为 from,实现"无版本号老文件"的迁移。 必须在 Load 成功之前调用。

func (*Manager) RegisterValidator added in v0.6.0

func (m *Manager) RegisterValidator(version string, fn ValidatorFunc) error

RegisterValidator 为指定版本注册校验函数,同版本仅能注册一个。 Load 校验与升级的最终关口都会调用它。必须在 Load 成功之前调用。

func (*Manager) Repair added in v0.6.0

func (m *Manager) Repair() error

Repair 修复该 Manager 对应的损坏配置文件。预留接口,尚未实现; 未来版本将基于已注册模板重建配置文件或引导用户修复。

func (*Manager) Reset added in v0.6.0

func (m *Manager) Reset() error

Reset 丢弃磁盘上的自定义配置,用注册模板重新创建配置文件并落盘 (版本号即模板版本,Manager 缓存的配置对象同时被替换)。 典型用途:Load 返回 *UpgradeFailedError 后,经用户确认调用本方法兜底。

func (*Manager) SetCurrentVersion added in v0.6.0

func (m *Manager) SetCurrentVersion(version string) error

SetCurrentVersion 声明本程序内置(期望)的配置版本号,仅能成功调用一次, 且必须在 Load 成功之前调用。启用后 Load 会校验磁盘版本: 一致则调用该版本的校验函数,落后则按注册的升级链逐级升级。 未调用时 Load 不做任何版本处理(保持原有行为)。

func (*Manager) SetPanel added in v0.6.0

func (m *Manager) SetPanel(fn func(*Config) error)

SetPanel 注册 exe config 子命令打开的交互式配置面板,完全由客户端实现: 面板拿到已加载的 *Config 自行交互、修改并保存。未注册时裸 config 子命令 返回用法错误。传 nil 清除已注册的面板。

type Schema added in v0.3.0

type Schema map[string]FieldDef

Schema 是以配置键名为索引的字段定义集合。 它有意独立于 Config,使调用方自行掌控 schema 与校验生命周期。

func ParseSchema added in v0.4.0

func ParseSchema(data []byte) (Schema, error)

ParseSchema 从 JSON 字节中解析出 Schema(仅提取 fields 部分)。 客户端可通过返回的 SchemaFile 访问 meta 信息。

func (Schema) Check added in v0.3.0

func (s Schema) Check(data map[string]any) CheckResult

Check 只读检查 data 相对于 schema 的状态,不修改 data。

func (Schema) Normalize added in v0.3.0

func (s Schema) Normalize(data map[string]any) (map[string]any, error)

Normalize 返回 data 的规范化副本:补全缺失的默认值,删除多余字段。 原 data 不会被修改。仅当 Check 结果为 MissingDefaults / ExtraFields / MissingAndExtra 时才能成功转换;其他状态返回 error。

type SchemaFile added in v0.4.0

type SchemaFile struct {
	Meta   SchemaMeta          `json:"meta"`
	Fields map[string]FieldDef `json:"fields"`
}

SchemaFile 是 schema.json 的顶层结构,将元数据与字段定义分组。

func ParseSchemaFile added in v0.4.0

func ParseSchemaFile(data []byte) (*SchemaFile, error)

ParseSchemaFile 从 JSON 字节中解析出完整的 SchemaFile(含 meta)。

type SchemaMeta added in v0.4.0

type SchemaMeta struct {
	Version string `json:"version"`
}

SchemaMeta 是 schema 文件的元数据部分,由客户端自行填充。

type UpgradeFailedError added in v0.6.0

type UpgradeFailedError struct {
	From string // 失败环节的起始版本(最终校验失败时为整条链的起点)
	To   string // 失败环节的目标版本
	Err  error  // 升级函数或校验函数返回的原始错误
}

UpgradeFailedError 表示版本升级在内存 dry-run 阶段失败:升级函数出错、 返回 nil,或升级结果未通过目标版本的校验函数。磁盘上的配置文件未被改动。 典型处理:向用户报告错误,经确认后调用 Manager.Reset 用注册模板覆盖配置 文件兜底(用户自定义配置会丢失)。

func (*UpgradeFailedError) Error added in v0.6.0

func (e *UpgradeFailedError) Error() string

Error 实现 error 接口,信息携带升级区间与原始错误。

func (*UpgradeFailedError) Unwrap added in v0.6.0

func (e *UpgradeFailedError) Unwrap() error

Unwrap 返回原始错误,支持 errors.Is/As 链式判断。

type UpgraderFunc added in v0.6.0

type UpgraderFunc func(fields map[string]any) (map[string]any, error)

UpgraderFunc 是客户端注册的版本间升级函数:输入起始版本的 fields, 迁移并返回目标版本的 fields(尽可能保留旧字段);返回 nil 视为失败。

type ValidatorFunc added in v0.6.0

type ValidatorFunc func(fields map[string]any) error

ValidatorFunc 是客户端注册的单版本校验函数:入参为 fields 层, 返回 nil 表示通过。未注册校验函数的版本视为免检。

Directories

Path Synopsis
examples
demo command
命令 demo 演示其他项目如何使用 appconfig。
命令 demo 演示其他项目如何使用 appconfig。

Jump to

Keyboard shortcuts

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