configmanager

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 5 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

Create a default_config.json template file and embed it:

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

c, err := configmanager.LoadAppConfig("myapp", defaultConfigJSON)
if err != nil {
    // 配置文件损坏时返回 *CorruptConfigError,不提供默认值降级;
    // 调用方应打印错误并退出,或引导用户修复。
    var corruptErr *configmanager.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 { ... }

The file lives at <user config dir>/myapp/config.json; on first run it is created from the embedded JSON template. c.Path() returns the absolute path (useful for editor integrations or error messages).

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 := configmanager.Schema{
    "host": {Type: configmanager.TypeString, Required: true},
    "port": {Type: configmanager.TypeFloat, Default: float64(3000)},
}

switch c.Check(schema) {
case configmanager.Valid:
    // nothing to do
case configmanager.MissingDefaults, configmanager.ExtraFields, configmanager.MissingAndExtra:
    if err := c.Normalize(schema); err != nil { ... }
    if err := c.Save(); err != nil { ... }
case configmanager.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.

Repair (not yet implemented)

RepairAppConfig(appName, defaultJSON) 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           # Core library: LoadAppConfig, Config, Get/Set/Save,
│                       # DecodeFields/SetFieldsFrom, Check/Normalize, Path,
│                       # CorruptConfigError, RepairAppConfig (stub)
├── schema.go           # Schema types, Check, Normalize (standalone)
├── config_test.go      # Internal tests
├── api_test.go         # External black-box tests
└── examples/
    └── demo/
        ├── default_config.json  # Embedded default config template
        └── main.go              # Runnable demo

Single package at module root, zero external dependencies.

Development Guide

Run tests and static analysis:

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

Tests use t.TempDir() and override AppData / XDG_CONFIG_HOME / HOME via t.Setenv, so they never touch real user config.

Design Decisions

  • No auto-detection of executable name: Renaming the binary would silently create a new config file, losing previous settings. Test binaries would also use different config paths. Multiple binaries in the same project often share one config. The caller explicitly provides appName.
  • Re-read after first save: On first run, LoadAppConfig writes the embedded JSON defaults 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, LoadAppConfig 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; RepairAppConfig is reserved for future repair workflows.
  • 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.
  • Zero dependencies: Only stdlib (encoding/json, os, path/filepath). 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

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

Index

Constants

View Source
const UnknownVersion = "UNKNOWN"

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

Variables

This section is empty.

Functions

func RepairAppConfig added in v0.5.0

func RepairAppConfig(appName string, defaultJSON []byte) error

RepairAppConfig 修复 appName 对应的损坏配置文件。预留接口,尚未实现; 未来版本将基于 defaultJSON 重建配置文件或引导用户修复。

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} 两层结构。

func LoadAppConfig

func LoadAppConfig(appName string, defaultJSON []byte) (*Config, error)

LoadAppConfig 加载 appName 对应的配置。首次运行时从 defaultJSON 创建配置文件, 因此除非无法获取用户配置目录或 defaultJSON 本身非法,否则总会返回一个配置对象。 已存在的配置文件无法读取或解析时,返回 nil 和 *CorruptConfigError, 不提供默认值降级,也不覆盖磁盘上的坏文件。 defaultJSON 应为合法的 JSON 对象(如通过 //go:embed 嵌入的模板文件)。 数值会从磁盘读回,所以类型统一为 float64。

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 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 文件的元数据部分,由客户端自行填充。

Directories

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

Jump to

Keyboard shortcuts

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