Skip to content

Commit

Permalink
feat: allow finding origin of value
Browse files Browse the repository at this point in the history
  • Loading branch information
phm07 committed Apr 24, 2024
1 parent d539b7a commit 73ebad0
Showing 1 changed file with 122 additions and 0 deletions.
122 changes: 122 additions & 0 deletions viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,128 @@ func (v *Viper) MustBindEnv(input ...string) {
}
}

type ValueOrigin int

const (
ValueOriginOverride ValueOrigin = iota
ValueOriginFlag
ValueOriginEnv
ValueOriginConfig
ValueOriginKVStore
ValueOriginDefault
ValueOriginNone
)

// Origin finds the origin of a given key's value.
// It returns a [ValueOrigin] value in the following order:
// 1. ValueOriginOverride - Set() override
// 2. ValueOriginFlag - PFlag override
// 3. ValueOriginEnv - Env override
// 4. ValueOriginConfig - Config file
// 5. ValueOriginKVStore - K/V store
// 6. ValueOriginDefault - Default value
// 7. ValueOriginNone - No value found
func Origin(key string) ValueOrigin { return v.Origin(key) }

func (v *Viper) Origin(key string) ValueOrigin {
lcaseKey := strings.ToLower(key)

var (
val any
exists bool
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1
)

// compute the path through the nested maps to the nested value
if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {
return ValueOriginNone
}

// if the requested key is an alias, then return the proper key
lcaseKey = v.realKey(lcaseKey)
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1

// Set() override first
val = v.searchMap(v.override, path)
if val != nil {
return ValueOriginOverride
}
if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {
return ValueOriginNone
}

// PFlag override next
flag, exists := v.pflags[lcaseKey]
if exists && flag.HasChanged() {
return ValueOriginFlag
}
if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {
return ValueOriginNone
}

// Env override next
if v.automaticEnvApplied {
envKey := strings.Join(append(v.parents, lcaseKey), ".")
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
if _, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {
return ValueOriginEnv
}
if nested && v.isPathShadowedInAutoEnv(path) != "" {
return ValueOriginNone
}
}
envkeys, exists := v.env[lcaseKey]
if exists {
for _, envkey := range envkeys {
if _, ok := v.getEnv(envkey); ok {
return ValueOriginEnv
}
}
}
if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {
return ValueOriginNone
}

// Config file next
val = v.searchIndexableWithPathPrefixes(v.config, path)
if val != nil {
return ValueOriginConfig
}
if nested && v.isPathShadowedInDeepMap(path, v.config) != "" {
return ValueOriginNone
}

// K/V store next
val = v.searchMap(v.kvstore, path)
if val != nil {
return ValueOriginKVStore
}
if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" {
return ValueOriginNone
}

// Default next
val = v.searchMap(v.defaults, path)
if val != nil {
return ValueOriginDefault
}
if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" {
return ValueOriginNone
}

// last chance: if no value is found and a flag does exist for the key,
// get the flag's default value even if the flag's value has not been set.
if _, exists := v.pflags[lcaseKey]; exists {
return ValueOriginDefault
}
// last item, no need to check shadowing

return ValueOriginNone
}

// Given a key, find the value.
//
// Viper will check to see if an alias exists first.
Expand Down

0 comments on commit 73ebad0

Please sign in to comment.