golang-spf13-viper

安装量: 803
排名: #4911

安装

npx skills add https://github.com/samber/cc-skills-golang --skill golang-spf13-viper

Persona: You are a Go engineer who treats configuration as a layered system. Flag beats env beats file beats default — and you bind every key so all four layers stay reachable through one API. Using spf13/viper for layered configuration in Go Viper resolves configuration values from multiple sources in a fixed precedence order. It has no user-facing surface — it doesn't define commands or flags. Its job is to answer "what is the value of key X right now?" by walking its source layers from highest to lowest priority. Official Resources: pkg.go.dev/github.com/spf13/viper github.com/spf13/viper This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform. go get github.com/spf13/viper@latest Viper vs. cobra Cobra owns the command tree — subcommands, flags, arg validation, completions. Viper owns configuration resolution — it answers "what is the value of key X?" by walking its source layers. Viper has no user-facing surface; it is purely a key-value resolver. Use cobra alone for flag-only CLIs; viper alone for config-file daemons; both when you need both, binding flags at PersistentPreRunE via BindPFlag . → See samber/cc-skills-golang@golang-spf13-cobra for the cobra side of this integration. The precedence pipeline Viper resolves a key by walking sources in this order (first set value wins): 1. explicit Set() — viper.Set("key", val) highest priority 2. flag — bound pflag.Flag 3. env var — BindEnv / AutomaticEnv 4. config file — ReadInConfig / MergeInConfig 5. KV remote — etcd / Consul 6. default — viper.SetDefault("key", val) lowest priority This pipeline is fixed and cannot be reordered. Understanding it prevents most viper bugs: a key that "should" come from a config file may be shadowed by an env var or a flag with a default value. Sources and config files viper . SetConfigName ( "config" ) viper . AddConfigPath ( "$HOME/.myapp" ) if err := viper . ReadInConfig ( ) ; err != nil { var notFound * viper . ConfigFileNotFoundError if ! errors . As ( err , & notFound ) { return fmt . Errorf ( "reading config: %w" , err ) // propagate real errors only } } ConfigFileNotFoundError must be handled gracefully — config files are usually optional. An unhandled error from a missing file crashes programs that are perfectly valid when run with only flags or env vars. For supported formats (JSON, TOML, YAML, HCL, INI, properties), MergeInConfig , and remote KV, see sources-and-formats.md . Env binding and key replacers This is the highest-bug-density area in viper. All three settings must be wired together — missing any one breaks nested key resolution: // ✓ Good — all three wired together at startup viper . SetEnvPrefix ( "MYAPP" ) // prevent collisions: PORT → MYAPP_PORT viper . SetEnvKeyReplacer ( strings . NewReplacer ( "." , "" ) ) // database.host → MYAPP_DATABASE_HOST viper . AutomaticEnv ( ) // ✗ Bad — without SetEnvKeyReplacer, viper looks for MYAPP_DATABASE.HOST (dot preserved) For BindEnv , AllowEmptyEnv , and env-vs-default interaction, see binding-and-env.md . Flag binding (the cobra seam) Bind cobra flags to viper in init() or PersistentPreRunE — never in RunE (too late; cobra parses flags before RunE runs): func init ( ) { rootCmd . PersistentFlags ( ) . Int ( "port" , 8080 , "listen port" ) viper . BindPFlag ( "port" , rootCmd . PersistentFlags ( ) . Lookup ( "port" ) ) // viper.BindPFlags(cmd.Flags()) — bind an entire FlagSet at once } For AllowEmptyEnv and flag/env interaction details, see binding-and-env.md . Unmarshaling into structs viper.Unmarshal maps the resolved configuration into a struct using mapstructure : type Config struct { Port int mapstructure:"port" Database struct { MaxConn int mapstructure:"max_conn" // explicit tag: mapstructure won't convert underscore→camelCase } mapstructure:"database" } var cfg Config viper . Unmarshal ( & cfg ) Always use mapstructure tags — implicit mapping is fragile for nested structs and underscore-named fields. Prefer UnmarshalKey("database", &dbCfg) over Sub("database").Unmarshal — it avoids the nil-check Sub requires when the key is missing. For time.Duration / net.IP / slice decoders and custom DecodeHook registration, see unmarshal.md . Sub-trees viper.Sub("database") returns a new viper.Viper scoped to the prefix, or nil if the key does not exist — always nil-check before calling methods on the result. Prefer UnmarshalKey("database", &dbCfg) which avoids the nil risk entirely. Hot reload viper . WatchConfig ( ) viper . OnConfigChange ( func ( e fsnotify . Event ) { / re-apply changed values */ } ) WatchConfig uses fsnotify and watches inodes. Editors that write atomically via rename (vim, neovim) replace the inode — the callback may not fire. Test hot-reload with echo >> config.yaml , not editor saves. For race-safe reload patterns, see watch-and-reload.md . Test isolation Never use the global viper in tests — state leaks across test cases. Use viper.New() per test so each instance is isolated: v := viper . New ( ) v . SetConfigFile ( "testdata/config.yaml" ) require . NoError ( t , v . ReadInConfig ( ) ) For t.Setenv interactions and Reset() limitations, see testing-and-isolation.md . Best Practices Set prefix + key replacer + AutomaticEnv together — missing any one causes nested env keys to silently not resolve ( database.host → DATABASE.HOST instead of DATABASE_HOST ). Handle ConfigFileNotFoundError gracefully — a missing config file should not crash a service that runs with only flags and env vars. Always use mapstructure tags on config structs — implicit mapping silently misses nested and underscore-named fields. Use viper.New() in tests, never the global — the global accumulates state across test runs; per-test instances are isolated. Bind flags before Execute() — binding in RunE is too late; cobra parses flags before RunE runs. Common Mistakes Mistake Why it fails Fix AutomaticEnv without SetEnvKeyReplacer database.host looks for MYAPP_DATABASE.HOST (dot preserved) — never matches Add SetEnvKeyReplacer(strings.NewReplacer(".", "")) before AutomaticEnv No mapstructure tags on struct fields Silently misses nested and underscore-named fields Add mapstructure:"key_name" to every field Using global viper in tests State from one test contaminates the next, causing flaky ordering Create viper.New() per test Missing ConfigFileNotFoundError check Missing config file crashes a service that should run on flags/env alone errors.As(err, &notFound) — only propagate non-not-found errors Further Reading sources-and-formats.md — supported file formats, multi-path search, MergeInConfig, remote KV (etcd/Consul) binding-and-env.md — BindEnv, AutomaticEnv, SetEnvPrefix, SetEnvKeyReplacer, AllowEmptyEnv, timing rules unmarshal.md — Unmarshal, UnmarshalKey, mapstructure tags, custom DecodeHooks (Duration, IP, slice) watch-and-reload.md — WatchConfig, OnConfigChange, fsnotify caveats, atomic-rename trap, race-safe patterns testing-and-isolation.md — viper.New() per test, t.Setenv interactions, Reset() limitations, snapshot/restore Cross-References → See samber/cc-skills-golang@golang-cli skill for general CLI architecture — project layout, exit codes, signal handling, cobra+viper integration → See samber/cc-skills-golang@golang-spf13-cobra skill for the cobra side of this integration (flag definition and binding) → See samber/cc-skills-golang@golang-testing skill for general Go testing patterns If you encounter a bug or unexpected behavior in spf13/viper, open an issue at https://github.com/spf13/viper/issues .

返回排行榜