Path: blob/main/vendor/github.com/spf13/viper/viper.go
2875 views
// Copyright © 2014 Steve Francia <[email protected]>.1//2// Use of this source code is governed by an MIT-style3// license that can be found in the LICENSE file.45// Viper is an application configuration system.6// It believes that applications can be configured a variety of ways7// via flags, ENVIRONMENT variables, configuration files retrieved8// from the file system, or a remote key/value store.910// Each item takes precedence over the item below it:1112// overrides13// flag14// env15// config16// key/value store17// default1819package viper2021import (22"bytes"23"encoding/csv"24"errors"25"fmt"26"io"27"log/slog"28"os"29"path/filepath"30"reflect"31"slices"32"strconv"33"strings"34"sync"35"time"3637"github.com/fsnotify/fsnotify"38"github.com/go-viper/mapstructure/v2"39"github.com/spf13/afero"40"github.com/spf13/cast"41"github.com/spf13/pflag"4243"github.com/spf13/viper/internal/features"44)4546// ConfigMarshalError happens when failing to marshal the configuration.47type ConfigMarshalError struct {48err error49}5051// Error returns the formatted configuration error.52func (e ConfigMarshalError) Error() string {53return fmt.Sprintf("While marshaling config: %s", e.err.Error())54}5556var v *Viper5758func init() {59v = New()60}6162// UnsupportedConfigError denotes encountering an unsupported63// configuration filetype.64type UnsupportedConfigError string6566// Error returns the formatted configuration error.67func (str UnsupportedConfigError) Error() string {68return fmt.Sprintf("Unsupported Config Type %q", string(str))69}7071// ConfigFileNotFoundError denotes failing to find configuration file.72type ConfigFileNotFoundError struct {73name, locations string74}7576// Error returns the formatted configuration error.77func (fnfe ConfigFileNotFoundError) Error() string {78return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)79}8081// ConfigFileAlreadyExistsError denotes failure to write new configuration file.82type ConfigFileAlreadyExistsError string8384// Error returns the formatted error when configuration already exists.85func (faee ConfigFileAlreadyExistsError) Error() string {86return fmt.Sprintf("Config File %q Already Exists", string(faee))87}8889// A DecoderConfigOption can be passed to viper.Unmarshal to configure90// mapstructure.DecoderConfig options.91type DecoderConfigOption func(*mapstructure.DecoderConfig)9293// DecodeHook returns a DecoderConfigOption which overrides the default94// DecoderConfig.DecodeHook value, the default is:95//96// mapstructure.ComposeDecodeHookFunc(97// mapstructure.StringToTimeDurationHookFunc(),98// mapstructure.StringToSliceHookFunc(","),99// )100func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {101return func(c *mapstructure.DecoderConfig) {102c.DecodeHook = hook103}104}105106// Viper is a prioritized configuration registry. It107// maintains a set of configuration sources, fetches108// values to populate those, and provides them according109// to the source's priority.110// The priority of the sources is the following:111// 1. overrides112// 2. flags113// 3. env. variables114// 4. config file115// 5. key/value store116// 6. defaults117//118// For example, if values from the following sources were loaded:119//120// Defaults : {121// "secret": "",122// "user": "default",123// "endpoint": "https://localhost"124// }125// Config : {126// "user": "root"127// "secret": "defaultsecret"128// }129// Env : {130// "secret": "somesecretkey"131// }132//133// The resulting config will have the following values:134//135// {136// "secret": "somesecretkey",137// "user": "root",138// "endpoint": "https://localhost"139// }140//141// Note: Vipers are not safe for concurrent Get() and Set() operations.142type Viper struct {143// Delimiter that separates a list of keys144// used to access a nested value in one go145keyDelim string146147// A set of paths to look for the config file in148configPaths []string149150// The filesystem to read config from.151fs afero.Fs152153finder Finder154155// A set of remote providers to search for the configuration156remoteProviders []*defaultRemoteProvider157158// Name of file to look for inside the path159configName string160configFile string161configType string162configPermissions os.FileMode163envPrefix string164165automaticEnvApplied bool166envKeyReplacer StringReplacer167allowEmptyEnv bool168169parents []string170config map[string]any171override map[string]any172defaults map[string]any173kvstore map[string]any174pflags map[string]FlagValue175env map[string][]string176aliases map[string]string177typeByDefValue bool178179onConfigChange func(fsnotify.Event)180181logger *slog.Logger182183encoderRegistry EncoderRegistry184decoderRegistry DecoderRegistry185186decodeHook mapstructure.DecodeHookFunc187188experimentalFinder bool189experimentalBindStruct bool190}191192// New returns an initialized Viper instance.193func New() *Viper {194v := new(Viper)195v.keyDelim = "."196v.configName = "config"197v.configPermissions = os.FileMode(0o644)198v.fs = afero.NewOsFs()199v.config = make(map[string]any)200v.parents = []string{}201v.override = make(map[string]any)202v.defaults = make(map[string]any)203v.kvstore = make(map[string]any)204v.pflags = make(map[string]FlagValue)205v.env = make(map[string][]string)206v.aliases = make(map[string]string)207v.typeByDefValue = false208v.logger = slog.New(&discardHandler{})209210codecRegistry := NewCodecRegistry()211212v.encoderRegistry = codecRegistry213v.decoderRegistry = codecRegistry214215v.experimentalFinder = features.Finder216v.experimentalBindStruct = features.BindStruct217218return v219}220221// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.222// If you're unfamiliar with this style,223// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and224// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis.225type Option interface {226apply(v *Viper)227}228229type optionFunc func(v *Viper)230231func (fn optionFunc) apply(v *Viper) {232fn(v)233}234235// KeyDelimiter sets the delimiter used for determining key parts.236// By default it's value is ".".237func KeyDelimiter(d string) Option {238return optionFunc(func(v *Viper) {239v.keyDelim = d240})241}242243// StringReplacer applies a set of replacements to a string.244type StringReplacer interface {245// Replace returns a copy of s with all replacements performed.246Replace(s string) string247}248249// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.250func EnvKeyReplacer(r StringReplacer) Option {251return optionFunc(func(v *Viper) {252if r == nil {253return254}255256v.envKeyReplacer = r257})258}259260// WithDecodeHook sets a default decode hook for mapstructure.261func WithDecodeHook(h mapstructure.DecodeHookFunc) Option {262return optionFunc(func(v *Viper) {263if h == nil {264return265}266267v.decodeHook = h268})269}270271// NewWithOptions creates a new Viper instance.272func NewWithOptions(opts ...Option) *Viper {273v := New()274275for _, opt := range opts {276opt.apply(v)277}278279return v280}281282// SetOptions sets the options on the global Viper instance.283//284// Be careful when using this function: subsequent calls may override options you set.285// It's always better to use a local Viper instance.286func SetOptions(opts ...Option) {287for _, opt := range opts {288opt.apply(v)289}290}291292// Reset is intended for testing, will reset all to default settings.293// In the public interface for the viper package so applications294// can use it in their testing as well.295func Reset() {296v = New()297SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}298299resetRemote()300}301302// SupportedExts are universally supported extensions.303var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}304305// OnConfigChange sets the event handler that is called when a config file changes.306func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }307308// OnConfigChange sets the event handler that is called when a config file changes.309func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {310v.onConfigChange = run311}312313// WatchConfig starts watching a config file for changes.314func WatchConfig() { v.WatchConfig() }315316// WatchConfig starts watching a config file for changes.317func (v *Viper) WatchConfig() {318initWG := sync.WaitGroup{}319initWG.Add(1)320go func() {321watcher, err := fsnotify.NewWatcher()322if err != nil {323v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err))324os.Exit(1)325}326defer watcher.Close()327// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way328filename, err := v.getConfigFile()329if err != nil {330v.logger.Error(fmt.Sprintf("get config file: %s", err))331initWG.Done()332return333}334335configFile := filepath.Clean(filename)336configDir, _ := filepath.Split(configFile)337realConfigFile, _ := filepath.EvalSymlinks(filename)338339eventsWG := sync.WaitGroup{}340eventsWG.Add(1)341go func() {342for {343select {344case event, ok := <-watcher.Events:345if !ok { // 'Events' channel is closed346eventsWG.Done()347return348}349currentConfigFile, _ := filepath.EvalSymlinks(filename)350// we only care about the config file with the following cases:351// 1 - if the config file was modified or created352// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)353if (filepath.Clean(event.Name) == configFile &&354(event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||355(currentConfigFile != "" && currentConfigFile != realConfigFile) {356realConfigFile = currentConfigFile357err := v.ReadInConfig()358if err != nil {359v.logger.Error(fmt.Sprintf("read config file: %s", err))360}361if v.onConfigChange != nil {362v.onConfigChange(event)363}364} else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {365eventsWG.Done()366return367}368369case err, ok := <-watcher.Errors:370if ok { // 'Errors' channel is not closed371v.logger.Error(fmt.Sprintf("watcher error: %s", err))372}373eventsWG.Done()374return375}376}377}()378err = watcher.Add(configDir)379if err != nil {380v.logger.Error(fmt.Sprintf("failed to add watcher: %s", err))381initWG.Done()382return383}384initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on...385eventsWG.Wait() // now, wait for event loop to end in this go-routine...386}()387initWG.Wait() // make sure that the go routine above fully ended before returning388}389390// SetConfigFile explicitly defines the path, name and extension of the config file.391// Viper will use this and not check any of the config paths.392func SetConfigFile(in string) { v.SetConfigFile(in) }393394func (v *Viper) SetConfigFile(in string) {395if in != "" {396v.configFile = in397}398}399400// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.401// E.g. if your prefix is "spf", the env registry will look for env402// variables that start with "SPF_".403func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }404405func (v *Viper) SetEnvPrefix(in string) {406if in != "" {407v.envPrefix = in408}409}410411func GetEnvPrefix() string { return v.GetEnvPrefix() }412413func (v *Viper) GetEnvPrefix() string {414return v.envPrefix415}416417func (v *Viper) mergeWithEnvPrefix(in string) string {418if v.envPrefix != "" {419return strings.ToUpper(v.envPrefix + "_" + in)420}421422return strings.ToUpper(in)423}424425// AllowEmptyEnv tells Viper to consider set,426// but empty environment variables as valid values instead of falling back.427// For backward compatibility reasons this is false by default.428func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }429430func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {431v.allowEmptyEnv = allowEmptyEnv432}433434// TODO: should getEnv logic be moved into find(). Can generalize the use of435// rewriting keys many things, Ex: Get('someKey') -> some_key436// (camel case to snake case for JSON keys perhaps)437438// getEnv is a wrapper around os.Getenv which replaces characters in the original439// key. This allows env vars which have different keys than the config object440// keys.441func (v *Viper) getEnv(key string) (string, bool) {442if v.envKeyReplacer != nil {443key = v.envKeyReplacer.Replace(key)444}445446val, ok := os.LookupEnv(key)447448return val, ok && (v.allowEmptyEnv || val != "")449}450451// ConfigFileUsed returns the file used to populate the config registry.452func ConfigFileUsed() string { return v.ConfigFileUsed() }453func (v *Viper) ConfigFileUsed() string { return v.configFile }454455// AddConfigPath adds a path for Viper to search for the config file in.456// Can be called multiple times to define multiple search paths.457func AddConfigPath(in string) { v.AddConfigPath(in) }458459func (v *Viper) AddConfigPath(in string) {460if v.finder != nil {461v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "AddConfigPath"))462}463464if in != "" {465absin := absPathify(v.logger, in)466467v.logger.Info("adding path to search paths", "path", absin)468if !slices.Contains(v.configPaths, absin) {469v.configPaths = append(v.configPaths, absin)470}471}472}473474// searchMap recursively searches for a value for path in source map.475// Returns nil if not found.476// Note: This assumes that the path entries and map keys are lower cased.477func (v *Viper) searchMap(source map[string]any, path []string) any {478if len(path) == 0 {479return source480}481482next, ok := source[path[0]]483if ok {484// Fast path485if len(path) == 1 {486return next487}488489// Nested case490switch next := next.(type) {491case map[any]any:492return v.searchMap(cast.ToStringMap(next), path[1:])493case map[string]any:494// Type assertion is safe here since it is only reached495// if the type of `next` is the same as the type being asserted496return v.searchMap(next, path[1:])497default:498// got a value but nested key expected, return "nil" for not found499return nil500}501}502return nil503}504505// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.506//507// While searchMap() considers each path element as a single map key or slice index, this508// function searches for, and prioritizes, merged path elements.509// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar"510// is also defined, this latter value is returned for path ["foo", "bar"].511//512// This should be useful only at config level (other maps may not contain dots513// in their keys).514//515// Note: This assumes that the path entries and map keys are lower cased.516func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any {517if len(path) == 0 {518return source519}520521// search for path prefixes, starting from the longest one522for i := len(path); i > 0; i-- {523prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))524525var val any526switch sourceIndexable := source.(type) {527case []any:528val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)529case map[string]any:530val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path)531}532if val != nil {533return val534}535}536537// not found538return nil539}540541// searchSliceWithPathPrefixes searches for a value for path in sourceSlice542//543// This function is part of the searchIndexableWithPathPrefixes recurring search and544// should not be called directly from functions other than searchIndexableWithPathPrefixes.545func (v *Viper) searchSliceWithPathPrefixes(546sourceSlice []any,547prefixKey string,548pathIndex int,549path []string,550) any {551// if the prefixKey is not a number or it is out of bounds of the slice552index, err := strconv.Atoi(prefixKey)553if err != nil || len(sourceSlice) <= index {554return nil555}556557next := sourceSlice[index]558559// Fast path560if pathIndex == len(path) {561return next562}563564switch n := next.(type) {565case map[any]any:566return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])567case map[string]any, []any:568return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])569default:570// got a value but nested key expected, do nothing and look for next prefix571}572573// not found574return nil575}576577// searchMapWithPathPrefixes searches for a value for path in sourceMap578//579// This function is part of the searchIndexableWithPathPrefixes recurring search and580// should not be called directly from functions other than searchIndexableWithPathPrefixes.581func (v *Viper) searchMapWithPathPrefixes(582sourceMap map[string]any,583prefixKey string,584pathIndex int,585path []string,586) any {587next, ok := sourceMap[prefixKey]588if !ok {589return nil590}591592// Fast path593if pathIndex == len(path) {594return next595}596597// Nested case598switch n := next.(type) {599case map[any]any:600return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])601case map[string]any, []any:602return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])603default:604// got a value but nested key expected, do nothing and look for next prefix605}606607// not found608return nil609}610611// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere612// on its path in the map.613// e.g., if "foo.bar" has a value in the given map, it “shadows”614//615// "foo.bar.baz" in a lower-priority map616func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string {617var parentVal any618for i := 1; i < len(path); i++ {619parentVal = v.searchMap(m, path[0:i])620if parentVal == nil {621// not found, no need to add more path elements622return ""623}624switch parentVal.(type) {625case map[any]any:626continue627case map[string]any:628continue629default:630// parentVal is a regular value which shadows "path"631return strings.Join(path[0:i], v.keyDelim)632}633}634return ""635}636637// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere638// in a sub-path of the map.639// e.g., if "foo.bar" has a value in the given map, it “shadows”640//641// "foo.bar.baz" in a lower-priority map642func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {643// unify input map644var m map[string]interface{}645switch miv := mi.(type) {646case map[string]string:647m = castMapStringToMapInterface(miv)648case map[string]FlagValue:649m = castMapFlagToMapInterface(miv)650default:651return ""652}653654// scan paths655var parentKey string656for i := 1; i < len(path); i++ {657parentKey = strings.Join(path[0:i], v.keyDelim)658if _, ok := m[parentKey]; ok {659return parentKey660}661}662return ""663}664665// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere666// in the environment, when automatic env is on.667// e.g., if "foo.bar" has a value in the environment, it “shadows”668//669// "foo.bar.baz" in a lower-priority map670func (v *Viper) isPathShadowedInAutoEnv(path []string) string {671var parentKey string672for i := 1; i < len(path); i++ {673parentKey = strings.Join(path[0:i], v.keyDelim)674if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {675return parentKey676}677}678return ""679}680681// SetTypeByDefaultValue enables or disables the inference of a key value's682// type when the Get function is used based upon a key's default value as683// opposed to the value returned based on the normal fetch logic.684//685// For example, if a key has a default value of []string{} and the same key686// is set via an environment variable to "a b c", a call to the Get function687// would return a string slice for the key if the key's type is inferred by688// the default value and the Get function would return:689//690// []string {"a", "b", "c"}691//692// Otherwise the Get function would return:693//694// "a b c"695func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }696697func (v *Viper) SetTypeByDefaultValue(enable bool) {698v.typeByDefValue = enable699}700701// GetViper gets the global Viper instance.702func GetViper() *Viper {703return v704}705706// Get can retrieve any value given the key to use.707// Get is case-insensitive for a key.708// Get has the behavior of returning the value associated with the first709// place from where it is set. Viper will check in the following order:710// override, flag, env, config file, key/value store, default711//712// Get returns an interface. For a specific value use one of the Get____ methods.713func Get(key string) any { return v.Get(key) }714715func (v *Viper) Get(key string) any {716lcaseKey := strings.ToLower(key)717val := v.find(lcaseKey, true)718if val == nil {719return nil720}721722if v.typeByDefValue {723// TODO(bep) this branch isn't covered by a single test.724valType := val725path := strings.Split(lcaseKey, v.keyDelim)726defVal := v.searchMap(v.defaults, path)727if defVal != nil {728valType = defVal729}730731switch valType.(type) {732case bool:733return cast.ToBool(val)734case string:735return cast.ToString(val)736case int32, int16, int8, int:737return cast.ToInt(val)738case uint:739return cast.ToUint(val)740case uint32:741return cast.ToUint32(val)742case uint64:743return cast.ToUint64(val)744case int64:745return cast.ToInt64(val)746case float64, float32:747return cast.ToFloat64(val)748case time.Time:749return cast.ToTime(val)750case time.Duration:751return cast.ToDuration(val)752case []string:753return cast.ToStringSlice(val)754case []int:755return cast.ToIntSlice(val)756case []time.Duration:757return cast.ToDurationSlice(val)758}759}760761return val762}763764// Sub returns new Viper instance representing a sub tree of this instance.765// Sub is case-insensitive for a key.766func Sub(key string) *Viper { return v.Sub(key) }767768func (v *Viper) Sub(key string) *Viper {769subv := New()770data := v.Get(key)771if data == nil {772return nil773}774775if reflect.TypeOf(data).Kind() == reflect.Map {776subv.parents = append([]string(nil), v.parents...)777subv.parents = append(subv.parents, strings.ToLower(key))778subv.automaticEnvApplied = v.automaticEnvApplied779subv.envPrefix = v.envPrefix780subv.envKeyReplacer = v.envKeyReplacer781subv.keyDelim = v.keyDelim782subv.config = cast.ToStringMap(data)783return subv784}785return nil786}787788// GetString returns the value associated with the key as a string.789func GetString(key string) string { return v.GetString(key) }790791func (v *Viper) GetString(key string) string {792return cast.ToString(v.Get(key))793}794795// GetBool returns the value associated with the key as a boolean.796func GetBool(key string) bool { return v.GetBool(key) }797798func (v *Viper) GetBool(key string) bool {799return cast.ToBool(v.Get(key))800}801802// GetInt returns the value associated with the key as an integer.803func GetInt(key string) int { return v.GetInt(key) }804805func (v *Viper) GetInt(key string) int {806return cast.ToInt(v.Get(key))807}808809// GetInt32 returns the value associated with the key as an integer.810func GetInt32(key string) int32 { return v.GetInt32(key) }811812func (v *Viper) GetInt32(key string) int32 {813return cast.ToInt32(v.Get(key))814}815816// GetInt64 returns the value associated with the key as an integer.817func GetInt64(key string) int64 { return v.GetInt64(key) }818819func (v *Viper) GetInt64(key string) int64 {820return cast.ToInt64(v.Get(key))821}822823// GetUint8 returns the value associated with the key as an unsigned integer.824func GetUint8(key string) uint8 { return v.GetUint8(key) }825826func (v *Viper) GetUint8(key string) uint8 {827return cast.ToUint8(v.Get(key))828}829830// GetUint returns the value associated with the key as an unsigned integer.831func GetUint(key string) uint { return v.GetUint(key) }832833func (v *Viper) GetUint(key string) uint {834return cast.ToUint(v.Get(key))835}836837// GetUint16 returns the value associated with the key as an unsigned integer.838func GetUint16(key string) uint16 { return v.GetUint16(key) }839840func (v *Viper) GetUint16(key string) uint16 {841return cast.ToUint16(v.Get(key))842}843844// GetUint32 returns the value associated with the key as an unsigned integer.845func GetUint32(key string) uint32 { return v.GetUint32(key) }846847func (v *Viper) GetUint32(key string) uint32 {848return cast.ToUint32(v.Get(key))849}850851// GetUint64 returns the value associated with the key as an unsigned integer.852func GetUint64(key string) uint64 { return v.GetUint64(key) }853854func (v *Viper) GetUint64(key string) uint64 {855return cast.ToUint64(v.Get(key))856}857858// GetFloat64 returns the value associated with the key as a float64.859func GetFloat64(key string) float64 { return v.GetFloat64(key) }860861func (v *Viper) GetFloat64(key string) float64 {862return cast.ToFloat64(v.Get(key))863}864865// GetTime returns the value associated with the key as time.866func GetTime(key string) time.Time { return v.GetTime(key) }867868func (v *Viper) GetTime(key string) time.Time {869return cast.ToTime(v.Get(key))870}871872// GetDuration returns the value associated with the key as a duration.873func GetDuration(key string) time.Duration { return v.GetDuration(key) }874875func (v *Viper) GetDuration(key string) time.Duration {876return cast.ToDuration(v.Get(key))877}878879// GetIntSlice returns the value associated with the key as a slice of int values.880func GetIntSlice(key string) []int { return v.GetIntSlice(key) }881882func (v *Viper) GetIntSlice(key string) []int {883return cast.ToIntSlice(v.Get(key))884}885886// GetStringSlice returns the value associated with the key as a slice of strings.887func GetStringSlice(key string) []string { return v.GetStringSlice(key) }888889func (v *Viper) GetStringSlice(key string) []string {890return cast.ToStringSlice(v.Get(key))891}892893// GetStringMap returns the value associated with the key as a map of interfaces.894func GetStringMap(key string) map[string]any { return v.GetStringMap(key) }895896func (v *Viper) GetStringMap(key string) map[string]any {897return cast.ToStringMap(v.Get(key))898}899900// GetStringMapString returns the value associated with the key as a map of strings.901func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) }902903func (v *Viper) GetStringMapString(key string) map[string]string {904return cast.ToStringMapString(v.Get(key))905}906907// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.908func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) }909910func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {911return cast.ToStringMapStringSlice(v.Get(key))912}913914// GetSizeInBytes returns the size of the value associated with the given key915// in bytes.916func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }917918func (v *Viper) GetSizeInBytes(key string) uint {919sizeStr := cast.ToString(v.Get(key))920return parseSizeInBytes(sizeStr)921}922923// UnmarshalKey takes a single key and unmarshals it into a Struct.924func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {925return v.UnmarshalKey(key, rawVal, opts...)926}927928func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {929return decode(v.Get(key), v.defaultDecoderConfig(rawVal, opts...))930}931932// Unmarshal unmarshals the config into a Struct. Make sure that the tags933// on the fields of the structure are properly set.934func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {935return v.Unmarshal(rawVal, opts...)936}937938func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {939keys := v.AllKeys()940941if v.experimentalBindStruct {942// TODO: make this optional?943structKeys, err := v.decodeStructKeys(rawVal, opts...)944if err != nil {945return err946}947948keys = append(keys, structKeys...)949}950951// TODO: struct keys should be enough?952return decode(v.getSettings(keys), v.defaultDecoderConfig(rawVal, opts...))953}954955func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {956var structKeyMap map[string]any957958err := decode(input, v.defaultDecoderConfig(&structKeyMap, opts...))959if err != nil {960return nil, err961}962963flattenedStructKeyMap := v.flattenAndMergeMap(map[string]bool{}, structKeyMap, "")964965r := make([]string, 0, len(flattenedStructKeyMap))966for v := range flattenedStructKeyMap {967r = append(r, v)968}969970return r, nil971}972973// defaultDecoderConfig returns default mapstructure.DecoderConfig with support974// of time.Duration values & string slices.975func (v *Viper) defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {976decodeHook := v.decodeHook977if decodeHook == nil {978decodeHook = mapstructure.ComposeDecodeHookFunc(979mapstructure.StringToTimeDurationHookFunc(),980// mapstructure.StringToSliceHookFunc(","),981stringToWeakSliceHookFunc(","),982)983}984985c := &mapstructure.DecoderConfig{986Metadata: nil,987WeaklyTypedInput: true,988DecodeHook: decodeHook,989}990991for _, opt := range opts {992opt(c)993}994995// Do not allow overwriting the output996c.Result = output997998return c999}10001001// As of mapstructure v2.0.0 StringToSliceHookFunc checks if the return type is a string slice.1002// This function removes that check.1003// TODO: implement a function that checks if the value can be converted to the return type and use it instead.1004func stringToWeakSliceHookFunc(sep string) mapstructure.DecodeHookFunc {1005return func(1006f reflect.Type,1007t reflect.Type,1008data interface{},1009) (interface{}, error) {1010if f.Kind() != reflect.String || t.Kind() != reflect.Slice {1011return data, nil1012}10131014raw := data.(string)1015if raw == "" {1016return []string{}, nil1017}10181019return strings.Split(raw, sep), nil1020}1021}10221023// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.1024func decode(input any, config *mapstructure.DecoderConfig) error {1025decoder, err := mapstructure.NewDecoder(config)1026if err != nil {1027return err1028}1029return decoder.Decode(input)1030}10311032// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent1033// in the destination struct.1034func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {1035return v.UnmarshalExact(rawVal, opts...)1036}10371038func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {1039config := v.defaultDecoderConfig(rawVal, opts...)1040config.ErrorUnused = true10411042keys := v.AllKeys()10431044if v.experimentalBindStruct {1045// TODO: make this optional?1046structKeys, err := v.decodeStructKeys(rawVal, opts...)1047if err != nil {1048return err1049}10501051keys = append(keys, structKeys...)1052}10531054// TODO: struct keys should be enough?1055return decode(v.getSettings(keys), config)1056}10571058// BindPFlags binds a full flag set to the configuration, using each flag's long1059// name as the config key.1060func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) }10611062func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {1063return v.BindFlagValues(pflagValueSet{flags})1064}10651066// BindPFlag binds a specific key to a pflag (as used by cobra).1067// Example (where serverCmd is a Cobra instance):1068//1069// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")1070// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))1071func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }10721073func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {1074if flag == nil {1075return fmt.Errorf("flag for %q is nil", key)1076}1077return v.BindFlagValue(key, pflagValue{flag})1078}10791080// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long1081// name as the config key.1082func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) }10831084func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {1085flags.VisitAll(func(flag FlagValue) {1086if err = v.BindFlagValue(flag.Name(), flag); err != nil {1087return1088}1089})1090return nil1091}10921093// BindFlagValue binds a specific key to a FlagValue.1094func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }10951096func (v *Viper) BindFlagValue(key string, flag FlagValue) error {1097if flag == nil {1098return fmt.Errorf("flag for %q is nil", key)1099}1100v.pflags[strings.ToLower(key)] = flag1101return nil1102}11031104// BindEnv binds a Viper key to a ENV variable.1105// ENV variables are case sensitive.1106// If only a key is provided, it will use the env key matching the key, uppercased.1107// If more arguments are provided, they will represent the env variable names that1108// should bind to this key and will be taken in the specified order.1109// EnvPrefix will be used when set when env name is not provided.1110func BindEnv(input ...string) error { return v.BindEnv(input...) }11111112func (v *Viper) BindEnv(input ...string) error {1113if len(input) == 0 {1114return fmt.Errorf("missing key to bind to")1115}11161117key := strings.ToLower(input[0])11181119if len(input) == 1 {1120v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))1121} else {1122v.env[key] = append(v.env[key], input[1:]...)1123}11241125return nil1126}11271128// MustBindEnv wraps BindEnv in a panic.1129// If there is an error binding an environment variable, MustBindEnv will1130// panic.1131func MustBindEnv(input ...string) { v.MustBindEnv(input...) }11321133func (v *Viper) MustBindEnv(input ...string) {1134if err := v.BindEnv(input...); err != nil {1135panic(fmt.Sprintf("error while binding environment variable: %v", err))1136}1137}11381139// Given a key, find the value.1140//1141// Viper will check to see if an alias exists first.1142// Viper will then check in the following order:1143// flag, env, config file, key/value store.1144// Lastly, if no value was found and flagDefault is true, and if the key1145// corresponds to a flag, the flag's default value is returned.1146//1147// Note: this assumes a lower-cased key given.1148func (v *Viper) find(lcaseKey string, flagDefault bool) any {1149var (1150val any1151exists bool1152path = strings.Split(lcaseKey, v.keyDelim)1153nested = len(path) > 11154)11551156// compute the path through the nested maps to the nested value1157if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {1158return nil1159}11601161// if the requested key is an alias, then return the proper key1162lcaseKey = v.realKey(lcaseKey)1163path = strings.Split(lcaseKey, v.keyDelim)1164nested = len(path) > 111651166// Set() override first1167val = v.searchMap(v.override, path)1168if val != nil {1169return val1170}1171if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {1172return nil1173}11741175// PFlag override next1176flag, exists := v.pflags[lcaseKey]1177if exists && flag.HasChanged() {1178switch flag.ValueType() {1179case "int", "int8", "int16", "int32", "int64":1180return cast.ToInt(flag.ValueString())1181case "bool":1182return cast.ToBool(flag.ValueString())1183case "stringSlice", "stringArray":1184s := strings.TrimPrefix(flag.ValueString(), "[")1185s = strings.TrimSuffix(s, "]")1186res, _ := readAsCSV(s)1187return res1188case "boolSlice":1189s := strings.TrimPrefix(flag.ValueString(), "[")1190s = strings.TrimSuffix(s, "]")1191res, _ := readAsCSV(s)1192return cast.ToBoolSlice(res)1193case "intSlice":1194s := strings.TrimPrefix(flag.ValueString(), "[")1195s = strings.TrimSuffix(s, "]")1196res, _ := readAsCSV(s)1197return cast.ToIntSlice(res)1198case "uintSlice":1199s := strings.TrimPrefix(flag.ValueString(), "[")1200s = strings.TrimSuffix(s, "]")1201res, _ := readAsCSV(s)1202return cast.ToUintSlice(res)1203case "float64Slice":1204s := strings.TrimPrefix(flag.ValueString(), "[")1205s = strings.TrimSuffix(s, "]")1206res, _ := readAsCSV(s)1207return cast.ToFloat64Slice(res)1208case "durationSlice":1209s := strings.TrimPrefix(flag.ValueString(), "[")1210s = strings.TrimSuffix(s, "]")1211slice := strings.Split(s, ",")1212return cast.ToDurationSlice(slice)1213case "stringToString":1214return stringToStringConv(flag.ValueString())1215case "stringToInt":1216return stringToIntConv(flag.ValueString())1217default:1218return flag.ValueString()1219}1220}1221if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {1222return nil1223}12241225// Env override next1226if v.automaticEnvApplied {1227envKey := strings.Join(append(v.parents, lcaseKey), ".")1228// even if it hasn't been registered, if automaticEnv is used,1229// check any Get request1230if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {1231return val1232}1233if nested && v.isPathShadowedInAutoEnv(path) != "" {1234return nil1235}1236}1237envkeys, exists := v.env[lcaseKey]1238if exists {1239for _, envkey := range envkeys {1240if val, ok := v.getEnv(envkey); ok {1241return val1242}1243}1244}1245if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {1246return nil1247}12481249// Config file next1250val = v.searchIndexableWithPathPrefixes(v.config, path)1251if val != nil {1252return val1253}1254if nested && v.isPathShadowedInDeepMap(path, v.config) != "" {1255return nil1256}12571258// K/V store next1259val = v.searchMap(v.kvstore, path)1260if val != nil {1261return val1262}1263if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" {1264return nil1265}12661267// Default next1268val = v.searchMap(v.defaults, path)1269if val != nil {1270return val1271}1272if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" {1273return nil1274}12751276if flagDefault {1277// last chance: if no value is found and a flag does exist for the key,1278// get the flag's default value even if the flag's value has not been set.1279if flag, exists := v.pflags[lcaseKey]; exists {1280switch flag.ValueType() {1281case "int", "int8", "int16", "int32", "int64":1282return cast.ToInt(flag.ValueString())1283case "bool":1284return cast.ToBool(flag.ValueString())1285case "stringSlice", "stringArray":1286s := strings.TrimPrefix(flag.ValueString(), "[")1287s = strings.TrimSuffix(s, "]")1288res, _ := readAsCSV(s)1289return res1290case "boolSlice":1291s := strings.TrimPrefix(flag.ValueString(), "[")1292s = strings.TrimSuffix(s, "]")1293res, _ := readAsCSV(s)1294return cast.ToBoolSlice(res)1295case "intSlice":1296s := strings.TrimPrefix(flag.ValueString(), "[")1297s = strings.TrimSuffix(s, "]")1298res, _ := readAsCSV(s)1299return cast.ToIntSlice(res)1300case "uintSlice":1301s := strings.TrimPrefix(flag.ValueString(), "[")1302s = strings.TrimSuffix(s, "]")1303res, _ := readAsCSV(s)1304return cast.ToUintSlice(res)1305case "float64Slice":1306s := strings.TrimPrefix(flag.ValueString(), "[")1307s = strings.TrimSuffix(s, "]")1308res, _ := readAsCSV(s)1309return cast.ToFloat64Slice(res)1310case "stringToString":1311return stringToStringConv(flag.ValueString())1312case "stringToInt":1313return stringToIntConv(flag.ValueString())1314case "durationSlice":1315s := strings.TrimPrefix(flag.ValueString(), "[")1316s = strings.TrimSuffix(s, "]")1317slice := strings.Split(s, ",")1318return cast.ToDurationSlice(slice)1319default:1320return flag.ValueString()1321}1322}1323// last item, no need to check shadowing1324}13251326return nil1327}13281329func readAsCSV(val string) ([]string, error) {1330if val == "" {1331return []string{}, nil1332}1333stringReader := strings.NewReader(val)1334csvReader := csv.NewReader(stringReader)1335return csvReader.Read()1336}13371338// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L791339// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.1340func stringToStringConv(val string) any {1341val = strings.Trim(val, "[]")1342// An empty string would cause an empty map1343if val == "" {1344return map[string]any{}1345}1346r := csv.NewReader(strings.NewReader(val))1347ss, err := r.Read()1348if err != nil {1349return nil1350}1351out := make(map[string]any, len(ss))1352for _, pair := range ss {1353k, vv, found := strings.Cut(pair, "=")1354if !found {1355return nil1356}1357out[k] = vv1358}1359return out1360}13611362// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/string_to_int.go#L681363// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.1364func stringToIntConv(val string) any {1365val = strings.Trim(val, "[]")1366// An empty string would cause an empty map1367if val == "" {1368return map[string]any{}1369}1370ss := strings.Split(val, ",")1371out := make(map[string]any, len(ss))1372for _, pair := range ss {1373k, vv, found := strings.Cut(pair, "=")1374if !found {1375return nil1376}1377var err error1378out[k], err = strconv.Atoi(vv)1379if err != nil {1380return nil1381}1382}1383return out1384}13851386// IsSet checks to see if the key has been set in any of the data locations.1387// IsSet is case-insensitive for a key.1388func IsSet(key string) bool { return v.IsSet(key) }13891390func (v *Viper) IsSet(key string) bool {1391lcaseKey := strings.ToLower(key)1392val := v.find(lcaseKey, false)1393return val != nil1394}13951396// AutomaticEnv makes Viper check if environment variables match any of the existing keys1397// (config, default or flags). If matching env vars are found, they are loaded into Viper.1398func AutomaticEnv() { v.AutomaticEnv() }13991400func (v *Viper) AutomaticEnv() {1401v.automaticEnvApplied = true1402}14031404// SetEnvKeyReplacer sets the strings.Replacer on the viper object1405// Useful for mapping an environmental variable to a key that does1406// not match it.1407func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }14081409func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {1410v.envKeyReplacer = r1411}14121413// RegisterAlias creates an alias that provides another accessor for the same key.1414// This enables one to change a name without breaking the application.1415func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) }14161417func (v *Viper) RegisterAlias(alias, key string) {1418v.registerAlias(alias, strings.ToLower(key))1419}14201421func (v *Viper) registerAlias(alias, key string) {1422alias = strings.ToLower(alias)1423if alias != key && alias != v.realKey(key) {1424_, exists := v.aliases[alias]14251426if !exists {1427// if we alias something that exists in one of the maps to another1428// name, we'll never be able to get that value using the original1429// name, so move the config value to the new realkey.1430if val, ok := v.config[alias]; ok {1431delete(v.config, alias)1432v.config[key] = val1433}1434if val, ok := v.kvstore[alias]; ok {1435delete(v.kvstore, alias)1436v.kvstore[key] = val1437}1438if val, ok := v.defaults[alias]; ok {1439delete(v.defaults, alias)1440v.defaults[key] = val1441}1442if val, ok := v.override[alias]; ok {1443delete(v.override, alias)1444v.override[key] = val1445}1446v.aliases[alias] = key1447}1448} else {1449v.logger.Warn("creating circular reference alias", "alias", alias, "key", key, "real_key", v.realKey(key))1450}1451}14521453func (v *Viper) realKey(key string) string {1454newkey, exists := v.aliases[key]1455if exists {1456v.logger.Debug("key is an alias", "alias", key, "to", newkey)14571458return v.realKey(newkey)1459}1460return key1461}14621463// InConfig checks to see if the given key (or an alias) is in the config file.1464func InConfig(key string) bool { return v.InConfig(key) }14651466func (v *Viper) InConfig(key string) bool {1467lcaseKey := strings.ToLower(key)14681469// if the requested key is an alias, then return the proper key1470lcaseKey = v.realKey(lcaseKey)1471path := strings.Split(lcaseKey, v.keyDelim)14721473return v.searchIndexableWithPathPrefixes(v.config, path) != nil1474}14751476// SetDefault sets the default value for this key.1477// SetDefault is case-insensitive for a key.1478// Default only used when no value is provided by the user via flag, config or ENV.1479func SetDefault(key string, value any) { v.SetDefault(key, value) }14801481func (v *Viper) SetDefault(key string, value any) {1482// If alias passed in, then set the proper default1483key = v.realKey(strings.ToLower(key))1484value = toCaseInsensitiveValue(value)14851486path := strings.Split(key, v.keyDelim)1487lastKey := strings.ToLower(path[len(path)-1])1488deepestMap := deepSearch(v.defaults, path[0:len(path)-1])14891490// set innermost value1491deepestMap[lastKey] = value1492}14931494// Set sets the value for the key in the override register.1495// Set is case-insensitive for a key.1496// Will be used instead of values obtained via1497// flags, config file, ENV, default, or key/value store.1498func Set(key string, value any) { v.Set(key, value) }14991500func (v *Viper) Set(key string, value any) {1501// If alias passed in, then set the proper override1502key = v.realKey(strings.ToLower(key))1503value = toCaseInsensitiveValue(value)15041505path := strings.Split(key, v.keyDelim)1506lastKey := strings.ToLower(path[len(path)-1])1507deepestMap := deepSearch(v.override, path[0:len(path)-1])15081509// set innermost value1510deepestMap[lastKey] = value1511}15121513// ReadInConfig will discover and load the configuration file from disk1514// and key/value stores, searching in one of the defined paths.1515func ReadInConfig() error { return v.ReadInConfig() }15161517func (v *Viper) ReadInConfig() error {1518v.logger.Info("attempting to read in config file")1519filename, err := v.getConfigFile()1520if err != nil {1521return err1522}15231524if !slices.Contains(SupportedExts, v.getConfigType()) {1525return UnsupportedConfigError(v.getConfigType())1526}15271528v.logger.Debug("reading file", "file", filename)1529file, err := afero.ReadFile(v.fs, filename)1530if err != nil {1531return err1532}15331534config := make(map[string]any)15351536err = v.unmarshalReader(bytes.NewReader(file), config)1537if err != nil {1538return err1539}15401541v.config = config1542return nil1543}15441545// MergeInConfig merges a new configuration with an existing config.1546func MergeInConfig() error { return v.MergeInConfig() }15471548func (v *Viper) MergeInConfig() error {1549v.logger.Info("attempting to merge in config file")1550filename, err := v.getConfigFile()1551if err != nil {1552return err1553}15541555if !slices.Contains(SupportedExts, v.getConfigType()) {1556return UnsupportedConfigError(v.getConfigType())1557}15581559file, err := afero.ReadFile(v.fs, filename)1560if err != nil {1561return err1562}15631564return v.MergeConfig(bytes.NewReader(file))1565}15661567// ReadConfig will read a configuration file, setting existing keys to nil if the1568// key does not exist in the file.1569func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }15701571func (v *Viper) ReadConfig(in io.Reader) error {1572config := make(map[string]any)15731574err := v.unmarshalReader(in, config)1575if err != nil {1576return err1577}15781579v.config = config15801581return nil1582}15831584// MergeConfig merges a new configuration with an existing config.1585func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }15861587func (v *Viper) MergeConfig(in io.Reader) error {1588config := make(map[string]any)15891590if err := v.unmarshalReader(in, config); err != nil {1591return err1592}15931594return v.MergeConfigMap(config)1595}15961597// MergeConfigMap merges the configuration from the map given with an existing config.1598// Note that the map given may be modified.1599func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(cfg) }16001601func (v *Viper) MergeConfigMap(cfg map[string]any) error {1602if v.config == nil {1603v.config = make(map[string]any)1604}1605insensitiviseMap(cfg)1606mergeMaps(cfg, v.config, nil)1607return nil1608}16091610// WriteConfig writes the current configuration to a file.1611func WriteConfig() error { return v.WriteConfig() }16121613func (v *Viper) WriteConfig() error {1614filename, err := v.getConfigFile()1615if err != nil {1616return err1617}1618return v.writeConfig(filename, true)1619}16201621// SafeWriteConfig writes current configuration to file only if the file does not exist.1622func SafeWriteConfig() error { return v.SafeWriteConfig() }16231624func (v *Viper) SafeWriteConfig() error {1625if len(v.configPaths) < 1 {1626return errors.New("missing configuration for 'configPath'")1627}1628return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType))1629}16301631// WriteConfigAs writes current configuration to a given filename.1632func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }16331634func (v *Viper) WriteConfigAs(filename string) error {1635return v.writeConfig(filename, true)1636}16371638// WriteConfigTo writes current configuration to an [io.Writer].1639func WriteConfigTo(w io.Writer) error { return v.WriteConfigTo(w) }16401641func (v *Viper) WriteConfigTo(w io.Writer) error {1642format := strings.ToLower(v.getConfigType())16431644if !slices.Contains(SupportedExts, format) {1645return UnsupportedConfigError(format)1646}16471648return v.marshalWriter(w, format)1649}16501651// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.1652func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }16531654func (v *Viper) SafeWriteConfigAs(filename string) error {1655alreadyExists, err := afero.Exists(v.fs, filename)1656if alreadyExists && err == nil {1657return ConfigFileAlreadyExistsError(filename)1658}1659return v.writeConfig(filename, false)1660}16611662func (v *Viper) writeConfig(filename string, force bool) error {1663v.logger.Info("attempting to write configuration to file")16641665var configType string16661667ext := filepath.Ext(filename)1668if ext != "" && ext != filepath.Base(filename) {1669configType = ext[1:]1670} else {1671configType = v.configType1672}1673if configType == "" {1674return fmt.Errorf("config type could not be determined for %s", filename)1675}16761677if !slices.Contains(SupportedExts, configType) {1678return UnsupportedConfigError(configType)1679}1680if v.config == nil {1681v.config = make(map[string]any)1682}1683flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY1684if !force {1685flags |= os.O_EXCL1686}1687f, err := v.fs.OpenFile(filename, flags, v.configPermissions)1688if err != nil {1689return err1690}1691defer f.Close()16921693if err := v.marshalWriter(f, configType); err != nil {1694return err1695}16961697return f.Sync()1698}16991700func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {1701format := strings.ToLower(v.getConfigType())1702if format == "" {1703return errors.New("cannot decode configuration: unable to determine config type")1704}17051706buf := new(bytes.Buffer)1707_, err := buf.ReadFrom(in)1708if err != nil {1709return fmt.Errorf("failed to read configuration from input: %w", err)1710}17111712// TODO: remove this once SupportedExts is deprecated/removed1713if !slices.Contains(SupportedExts, format) {1714return UnsupportedConfigError(format)1715}17161717// TODO: return [UnsupportedConfigError] if the registry does not contain the format1718// TODO: consider deprecating this error type1719decoder, err := v.decoderRegistry.Decoder(format)1720if err != nil {1721return ConfigParseError{err}1722}17231724err = decoder.Decode(buf.Bytes(), c)1725if err != nil {1726return ConfigParseError{err}1727}17281729insensitiviseMap(c)1730return nil1731}17321733// Marshal a map into Writer.1734func (v *Viper) marshalWriter(w io.Writer, configType string) error {1735c := v.AllSettings()17361737encoder, err := v.encoderRegistry.Encoder(configType)1738if err != nil {1739return ConfigMarshalError{err}1740}17411742b, err := encoder.Encode(c)1743if err != nil {1744return ConfigMarshalError{err}1745}17461747_, err = w.Write(b)1748if err != nil {1749return ConfigMarshalError{err}1750}17511752return nil1753}17541755func keyExists(k string, m map[string]any) string {1756lk := strings.ToLower(k)1757for mk := range m {1758lmk := strings.ToLower(mk)1759if lmk == lk {1760return mk1761}1762}1763return ""1764}17651766func castToMapStringInterface(1767src map[any]any,1768) map[string]any {1769tgt := map[string]any{}1770for k, v := range src {1771tgt[fmt.Sprintf("%v", k)] = v1772}1773return tgt1774}17751776func castMapStringSliceToMapInterface(src map[string][]string) map[string]any {1777tgt := map[string]any{}1778for k, v := range src {1779tgt[k] = v1780}1781return tgt1782}17831784func castMapStringToMapInterface(src map[string]string) map[string]any {1785tgt := map[string]any{}1786for k, v := range src {1787tgt[k] = v1788}1789return tgt1790}17911792func castMapFlagToMapInterface(src map[string]FlagValue) map[string]any {1793tgt := map[string]any{}1794for k, v := range src {1795tgt[k] = v1796}1797return tgt1798}17991800// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's1801// insistence on parsing nested structures as `map[any]any`1802// instead of using a `string` as the key for nest structures beyond one level1803// deep. Both map types are supported as there is a go-yaml fork that uses1804// `map[string]any` instead.1805func mergeMaps(src, tgt map[string]any, itgt map[any]any) {1806for sk, sv := range src {1807tk := keyExists(sk, tgt)1808if tk == "" {1809v.logger.Debug("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv)1810tgt[sk] = sv1811if itgt != nil {1812itgt[sk] = sv1813}1814continue1815}18161817tv, ok := tgt[tk]1818if !ok {1819v.logger.Debug("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv)1820tgt[sk] = sv1821if itgt != nil {1822itgt[sk] = sv1823}1824continue1825}18261827svType := reflect.TypeOf(sv)1828tvType := reflect.TypeOf(tv)18291830v.logger.Debug(1831"processing",1832"key", sk,1833"st", svType,1834"tt", tvType,1835"sv", sv,1836"tv", tv,1837)18381839switch ttv := tv.(type) {1840case map[any]any:1841v.logger.Debug("merging maps (must convert)")1842tsv, ok := sv.(map[any]any)1843if !ok {1844v.logger.Error(1845"Could not cast sv to map[any]any",1846"key", sk,1847"st", svType,1848"tt", tvType,1849"sv", sv,1850"tv", tv,1851)1852continue1853}18541855ssv := castToMapStringInterface(tsv)1856stv := castToMapStringInterface(ttv)1857mergeMaps(ssv, stv, ttv)1858case map[string]any:1859v.logger.Debug("merging maps")1860tsv, ok := sv.(map[string]any)1861if !ok {1862v.logger.Error(1863"Could not cast sv to map[string]any",1864"key", sk,1865"st", svType,1866"tt", tvType,1867"sv", sv,1868"tv", tv,1869)1870continue1871}1872mergeMaps(tsv, ttv, nil)1873default:1874v.logger.Debug("setting value")1875tgt[tk] = sv1876if itgt != nil {1877itgt[tk] = sv1878}1879}1880}1881}18821883// AllKeys returns all keys holding a value, regardless of where they are set.1884// Nested keys are returned with a v.keyDelim separator.1885func AllKeys() []string { return v.AllKeys() }18861887func (v *Viper) AllKeys() []string {1888m := map[string]bool{}1889// add all paths, by order of descending priority to ensure correct shadowing1890m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")1891m = v.flattenAndMergeMap(m, v.override, "")1892m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags))1893m = v.mergeFlatMap(m, castMapStringSliceToMapInterface(v.env))1894m = v.flattenAndMergeMap(m, v.config, "")1895m = v.flattenAndMergeMap(m, v.kvstore, "")1896m = v.flattenAndMergeMap(m, v.defaults, "")18971898// convert set of paths to list1899a := make([]string, 0, len(m))1900for x := range m {1901a = append(a, x)1902}1903return a1904}19051906// flattenAndMergeMap recursively flattens the given map into a map[string]bool1907// of key paths (used as a set, easier to manipulate than a []string):1908// - each path is merged into a single key string, delimited with v.keyDelim1909// - if a path is shadowed by an earlier value in the initial shadow map,1910// it is skipped.1911//1912// The resulting set of paths is merged to the given shadow set at the same time.1913func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]any, prefix string) map[string]bool {1914if shadow != nil && prefix != "" && shadow[prefix] {1915// prefix is shadowed => nothing more to flatten1916return shadow1917}1918if shadow == nil {1919shadow = make(map[string]bool)1920}19211922var m2 map[string]any1923if prefix != "" {1924prefix += v.keyDelim1925}1926for k, val := range m {1927fullKey := prefix + k1928switch val := val.(type) {1929case map[string]any:1930m2 = val1931case map[any]any:1932m2 = cast.ToStringMap(val)1933default:1934// immediate value1935shadow[strings.ToLower(fullKey)] = true1936continue1937}1938// recursively merge to shadow map1939shadow = v.flattenAndMergeMap(shadow, m2, fullKey)1940}1941return shadow1942}19431944// mergeFlatMap merges the given maps, excluding values of the second map1945// shadowed by values from the first map.1946func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]any) map[string]bool {1947// scan keys1948outer:1949for k := range m {1950path := strings.Split(k, v.keyDelim)1951// scan intermediate paths1952var parentKey string1953for i := 1; i < len(path); i++ {1954parentKey = strings.Join(path[0:i], v.keyDelim)1955if shadow[parentKey] {1956// path is shadowed, continue1957continue outer1958}1959}1960// add key1961shadow[strings.ToLower(k)] = true1962}1963return shadow1964}19651966// AllSettings merges all settings and returns them as a map[string]any.1967func AllSettings() map[string]any { return v.AllSettings() }19681969func (v *Viper) AllSettings() map[string]any {1970return v.getSettings(v.AllKeys())1971}19721973func (v *Viper) getSettings(keys []string) map[string]any {1974m := map[string]any{}1975// start from the list of keys, and construct the map one value at a time1976for _, k := range keys {1977value := v.Get(k)1978if value == nil {1979// should not happen, since AllKeys() returns only keys holding a value,1980// check just in case anything changes1981continue1982}1983path := strings.Split(k, v.keyDelim)1984lastKey := strings.ToLower(path[len(path)-1])1985deepestMap := deepSearch(m, path[0:len(path)-1])1986// set innermost value1987deepestMap[lastKey] = value1988}1989return m1990}19911992// SetFs sets the filesystem to use to read configuration.1993func SetFs(fs afero.Fs) { v.SetFs(fs) }19941995func (v *Viper) SetFs(fs afero.Fs) {1996v.fs = fs1997}19981999// SetConfigName sets name for the config file.2000// Does not include extension.2001func SetConfigName(in string) { v.SetConfigName(in) }20022003func (v *Viper) SetConfigName(in string) {2004if v.finder != nil {2005v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "SetConfigName"))2006}20072008if in != "" {2009v.configName = in2010v.configFile = ""2011}2012}20132014// SetConfigType sets the type of the configuration returned by the2015// remote source, e.g. "json".2016func SetConfigType(in string) { v.SetConfigType(in) }20172018func (v *Viper) SetConfigType(in string) {2019if in != "" {2020v.configType = in2021}2022}20232024// SetConfigPermissions sets the permissions for the config file.2025func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }20262027func (v *Viper) SetConfigPermissions(perm os.FileMode) {2028v.configPermissions = perm.Perm()2029}20302031func (v *Viper) getConfigType() string {2032if v.configType != "" {2033return v.configType2034}20352036cf, err := v.getConfigFile()2037if err != nil {2038return ""2039}20402041ext := filepath.Ext(cf)20422043if len(ext) > 1 {2044return ext[1:]2045}20462047return ""2048}20492050func (v *Viper) getConfigFile() (string, error) {2051if v.configFile == "" {2052cf, err := v.findConfigFile()2053if err != nil {2054return "", err2055}2056v.configFile = cf2057}2058return v.configFile, nil2059}20602061// Debug prints all configuration registries for debugging2062// purposes.2063func Debug() { v.Debug() }2064func DebugTo(w io.Writer) { v.DebugTo(w) }20652066func (v *Viper) Debug() { v.DebugTo(os.Stdout) }20672068func (v *Viper) DebugTo(w io.Writer) {2069fmt.Fprintf(w, "Aliases:\n%#v\n", v.aliases)2070fmt.Fprintf(w, "Override:\n%#v\n", v.override)2071fmt.Fprintf(w, "PFlags:\n%#v\n", v.pflags)2072fmt.Fprintf(w, "Env:\n%#v\n", v.env)2073fmt.Fprintf(w, "Key/Value Store:\n%#v\n", v.kvstore)2074fmt.Fprintf(w, "Config:\n%#v\n", v.config)2075fmt.Fprintf(w, "Defaults:\n%#v\n", v.defaults)2076}207720782079