Compare commits

...
35 Commits
Author SHA1 Message Date
Mark Sagi-Kazar 528f7416c4 allow octoslash on PRs
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-15 23:20:01 +02:00
Matthew Coleman bdc880d2f5 chore: turn up the number of stale issue ops from 30 to 500 2025-10-15 23:17:53 +02:00
ccoVeille 937ae2d3e6 ci: enable revive rules
These are the exact default rules, only packages-comment rule was disabled.
But we had to add them all to disable one rule.

For records, revive owns a lot of rules that could be enabled.
2025-10-14 14:31:00 +02:00
ccoVeille 2959599d7d doc: add documentation to all exported methods 2025-10-14 14:31:00 +02:00
Mark Sagi-Kazar 15fb405454 ci: update octoslash permissions
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-09 15:15:17 +02:00
Matthew Coleman c0965a201e docs: clenups, remove HCL support 2025-10-08 12:05:32 +02:00
Matthew Coleman cd5c582cff docs: readme refresh 2025-10-08 12:05:32 +02:00
Márk Sági-Kazár c2b66799cc docs: update comment on deprecated error 2025-10-08 12:00:40 +02:00
Matthew Coleman 6f44d6965b refactor: clean-ups 2025-10-08 12:00:40 +02:00
Mark Sagi-Kazar 196113234f refactor: apply review comments
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-08 12:00:40 +02:00
Matthew Coleman b3621ce3dd style: better naming 2025-10-08 12:00:40 +02:00
Matthew Coleman da507cf456 style: clean-up 2025-10-08 12:00:40 +02:00
Matthew Coleman 3504ce391d fix: wrap file not found error 2025-10-08 12:00:40 +02:00
Matthew Coleman 3c673554b5 style: variable naming, readme conforming 2025-10-08 12:00:40 +02:00
Matthew Coleman 315c2137bb refactor: redundant fields for file search error, interface, syscall opt 2025-10-08 12:00:40 +02:00
Matthew ColemanandMárk Sági-Kazár 7312e0f009 style: apply suggestions from code review
Co-authored-by: Márk Sági-Kazár <mark.sagikazar@gmail.com>
2025-10-08 12:00:40 +02:00
Matthew Coleman 00a9d5df73 docs: better file not found example, remove file not found interface 2025-10-08 12:00:40 +02:00
Matthew Coleman 29eb8d3fc7 style: better test name 2025-10-08 12:00:40 +02:00
Matthew Coleman e8ab22ef25 style: clean-up 2025-10-08 12:00:40 +02:00
Matthew Coleman 5ae04a8bbf docs: add explanation on read in config errors 2025-10-08 12:00:40 +02:00
Matthew Coleman b9dfcc5b9e refactor: better naming, return original error for finder 2025-10-08 12:00:40 +02:00
Matthew ColemanandMárk Sági-Kazár 2d525ac880 refactor: apply suggestions from code review
Co-authored-by: Márk Sági-Kazár <mark.sagikazar@gmail.com>
2025-10-08 12:00:40 +02:00
Matthew Coleman ec92e64332 style: clean-up 2025-10-08 12:00:40 +02:00
Matthew Coleman d8387f662f refactor: segregate errors, config file not found interface 2025-10-08 12:00:40 +02:00
Matthew Coleman 93dc9f6a62 style: gofumpt 2025-10-08 12:00:40 +02:00
Matthew Coleman 0afb0456be fix: return not found error if setting config 2025-10-08 12:00:40 +02:00
Mark Sagi-Kazar 2da50fe98c chore: add slash commands
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-08 03:06:30 +02:00
Mark Sagi-Kazar 0525738e5e ci: restrict permissions to the required ones
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-02 22:28:27 +02:00
Mark Sagi-Kazar b5b5970f98 ci: remove unused codeql workflow
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-10-02 22:17:50 +02:00
dependabot[bot] 2220648d15 build(deps): bump github.com/sagikazarmark/locafero
Bumps [github.com/sagikazarmark/locafero](https://github.com/sagikazarmark/locafero) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/sagikazarmark/locafero/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: github.com/sagikazarmark/locafero
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-26 22:02:03 +02:00
Mark Sagi-Kazar e66ee1a10e remove issue comment test
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-09-18 21:49:29 +02:00
Mark Sagi-Kazar b127084a3e collect issue comment events
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-09-18 21:43:10 +02:00
Matthew Coleman eadfaeec3f feat: allow for issue exemption 2025-09-17 17:31:07 +02:00
Matthew Coleman c4eab501d4 feat: turn stale issue management on 2025-09-17 17:31:07 +02:00
Mark Sagi-Kazar 7a5ad96afc fix stale issue params
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2025-09-12 19:23:59 +02:00
26 changed files with 746 additions and 611 deletions
@@ -0,0 +1,5 @@
permit(
principal in Role::"Collaborator",
action,
resource
);
+5
View File
@@ -0,0 +1,5 @@
permit(
principal in Role::"Triager",
action in [Action::"close", Action::"add-label", Action::"remove-label", Action::"self-assign", Action::"self-unassign"],
resource
);
+12
View File
@@ -0,0 +1,12 @@
[
{
"uid": { "type": "User", "id": "1226384" },
"attrs": { "login": "sagikazarmark" },
"parents": [{ "type": "Role", "id": "Collaborator" }]
},
{
"uid": { "type": "User", "id": "805695" },
"attrs": { "login": "spacez320" },
"parents": [{ "type": "Role", "id": "Triager" }]
}
]
+3
View File
@@ -4,6 +4,9 @@ on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
permissions:
pull-requests: read
jobs:
release-label:
name: Release note label
+3
View File
@@ -5,6 +5,9 @@ on:
branches: [master]
pull_request:
permissions:
contents: read
jobs:
build:
name: Build
-72
View File
@@ -1,72 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '22 16 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9
+18
View File
@@ -0,0 +1,18 @@
name: Octoslash
on: issue_comment
permissions:
issues: write
pull-requests: write
jobs:
run:
name: Run
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Octoslash
uses: sagikazarmark/octoslash-action@v0
+10 -2
View File
@@ -4,6 +4,12 @@ on:
schedule:
- cron: "0 3 * * *"
permissions:
actions: write
issues: write
# contents: write # only for delete-branch option
# pull-requests: write
jobs:
stale:
name: Stale
@@ -17,5 +23,7 @@ jobs:
days-before-pr-stale: -1 # Ignore all PRs
stale-issue-message: "Issues with no activity for 30 days are marked stale and subject to being closed."
close-issue-message: "This issue has been closed for inactivity. Please re-open if this was a mistake."
stole-issue-label: "lifecycle/stale"
debug-only: true # TODO for testing
exempt-issue-labels: "lifecycle/keep"
stale-issue-label: "lifecycle/stale"
operations-per-run: 500 # Must respect GitHub API limits
debug-only: false # Set this to 'true' to disable stale issue management
+32 -1
View File
@@ -82,10 +82,41 @@ linters:
- noctx
path: _test.go
presets:
- comments
- std-error-handling
settings:
revive:
rules:
- name: blank-imports
- name: context-as-argument
arguments:
- allowTypesBefore: "*testing.T"
- name: context-keys-type
- name: dot-imports
- name: empty-block
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
arguments:
- "sayRepetitiveInsteadOfStutters" # make error messages clearer
- name: increment-decrement
- name: indent-error-flow
- name: package-comments
disabled: true # disable package comments rule for now
- name: range
- name: redefines-builtin-id
- name: superfluous-else
- name: time-naming
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: var-naming
# TODO: add more revive rules
misspell:
locale: US
nolintlint:
+351 -483
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -49,7 +49,7 @@ type DecoderRegistry interface {
Decoder(format string) (Decoder, error)
}
// [CodecRegistry] combines [EncoderRegistry] and [DecoderRegistry] interfaces.
// CodecRegistry combines [EncoderRegistry] and [DecoderRegistry] interfaces.
type CodecRegistry interface {
EncoderRegistry
DecoderRegistry
+93
View File
@@ -0,0 +1,93 @@
package viper
import (
"fmt"
)
// FileLookupError is returned when Viper cannot resolve a configuration file.
//
// This is meant to be a common interface for all file look-up errors, occurring either because a
// file does not exist or because it cannot find any file matching finder criteria.
type FileLookupError interface {
error
fileLookup()
}
// ConfigFileNotFoundError denotes failing to find a configuration file from a search.
//
// Deprecated: This is error wraps [FileNotFoundFromSearchError], which should be used instead.
type ConfigFileNotFoundError struct {
locations []string
name string
}
// Error returns the formatted error.
func (e ConfigFileNotFoundError) Error() string {
return e.Unwrap().Error()
}
// Unwraps to FileNotFoundFromSearchError.
func (e ConfigFileNotFoundError) Unwrap() error {
return FileNotFoundFromSearchError(e)
}
// FileNotFoundFromSearchError denotes failing to find a configuration file from a search.
// Wraps ConfigFileNotFoundError.
type FileNotFoundFromSearchError struct {
locations []string
name string
}
func (e FileNotFoundFromSearchError) fileLookup() {}
// Error returns the formatted error.
func (e FileNotFoundFromSearchError) Error() string {
message := fmt.Sprintf("File %q not found", e.name)
if len(e.locations) > 0 {
message += fmt.Sprintf(" in %v", e.locations)
}
return message
}
// FileNotFoundError denotes failing to find a specific configuration file.
type FileNotFoundError struct {
err error
path string
}
func (e FileNotFoundError) fileLookup() {}
// Error returns the formatted error.
func (e FileNotFoundError) Error() string {
return fmt.Sprintf("file not found: %s", e.path)
}
// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
type ConfigFileAlreadyExistsError string
// Error returns the formatted error when configuration already exists.
func (e ConfigFileAlreadyExistsError) Error() string {
return fmt.Sprintf("Config File %q Already Exists", string(e))
}
// ConfigMarshalError happens when failing to marshal the configuration.
type ConfigMarshalError struct {
err error
}
// Error returns the formatted configuration error.
func (e ConfigMarshalError) Error() string {
return fmt.Sprintf("While marshaling config: %s", e.err.Error())
}
// UnsupportedConfigError denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string
// Error returns the formatted configuration error.
func (str UnsupportedConfigError) Error() string {
return fmt.Sprintf("Unsupported Config Type %q", string(str))
}
+2 -3
View File
@@ -1,7 +1,6 @@
package viper
import (
"fmt"
"os"
"path/filepath"
@@ -50,7 +49,7 @@ func (v *Viper) findConfigFileWithFinder(finder Finder) (string, error) {
}
if len(results) == 0 {
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
return "", ConfigFileNotFoundError{name: v.configName, locations: v.configPaths}
}
// We call clean on the final result to ensure that the path is in its canonical form.
@@ -69,7 +68,7 @@ func (v *Viper) findConfigFileOld() (string, error) {
return file, nil
}
}
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
return "", ConfigFileNotFoundError{name: v.configName, locations: v.configPaths}
}
func (v *Viper) searchInPath(in string) (filename string) {
+2 -2
View File
@@ -6,7 +6,7 @@ require (
github.com/fsnotify/fsnotify v1.9.0
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/sagikazarmark/locafero v0.11.0
github.com/sagikazarmark/locafero v0.12.0
github.com/spf13/afero v1.15.0
github.com/spf13/cast v1.10.0
github.com/spf13/pflag v1.0.10
@@ -19,8 +19,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+2 -4
View File
@@ -18,10 +18,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+2
View File
@@ -15,6 +15,7 @@ const keyDelimiter = "_"
// (commonly called as dotenv format).
type Codec struct{}
// Encode encodes a map[string]any into a dotenv byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
flattened := map[string]any{}
@@ -40,6 +41,7 @@ func (Codec) Encode(v map[string]any) ([]byte, error) {
return buf.Bytes(), nil
}
// Decode decodes a dotenv byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
var buf bytes.Buffer
+2
View File
@@ -7,11 +7,13 @@ import (
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
type Codec struct{}
// Encode encodes a map[string]any into a JSON byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
// TODO: expose prefix and indent in the Codec as setting?
return json.MarshalIndent(v, "", " ")
}
// Decode decodes a JSON byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return json.Unmarshal(b, &v)
}
+2
View File
@@ -7,10 +7,12 @@ import (
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
type Codec struct{}
// Encode encodes a map[string]any into a TOML byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
return toml.Marshal(v)
}
// Decode decodes a TOML byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return toml.Unmarshal(b, &v)
}
+2
View File
@@ -5,10 +5,12 @@ import "go.yaml.in/yaml/v3"
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
type Codec struct{}
// Encode encodes a map[string]any into a YAML byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
return yaml.Marshal(v)
}
// Decode decodes a YAML byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return yaml.Unmarshal(b, &v)
}
+1
View File
@@ -2,4 +2,5 @@
package features
// BindStruct is a feature flag for enabling/disabling the config binding to structs.
const BindStruct = true
+1
View File
@@ -2,4 +2,5 @@
package features
// BindStruct is a feature flag for enabling/disabling the config binding to structs.
const BindStruct = false
+1
View File
@@ -2,4 +2,5 @@
package features
// Finder is a feature flag for enabling/disabling the config finder.
const Finder = true
+1
View File
@@ -2,4 +2,5 @@
package features
// Finder is a feature flag for enabling/disabling the config finder.
const Finder = false
+25
View File
@@ -21,6 +21,7 @@ type remoteConfigFactory interface {
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
}
// RemoteResponse represents a response from a remote configuration provider.
type RemoteResponse struct {
Value []byte
Error error
@@ -93,6 +94,14 @@ func AddRemoteProvider(provider, endpoint, path string) error {
return v.AddRemoteProvider(provider, endpoint, path)
}
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://ip:port
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
if !slices.Contains(SupportedRemoteProviders, provider) {
return UnsupportedRemoteProviderError(provider)
@@ -126,6 +135,16 @@ func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) err
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
}
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
// Secure Remote Providers are implemented with github.com/sagikazarmark/crypt.
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
if !slices.Contains(SupportedRemoteProviders, provider) {
return UnsupportedRemoteProviderError(provider)
@@ -159,15 +178,21 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
// and read it in the remote configuration registry.
func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
// ReadRemoteConfig attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func (v *Viper) ReadRemoteConfig() error {
return v.getKeyValueConfig()
}
// WatchRemoteConfig updates configuration from available remote providers.
func WatchRemoteConfig() error { return v.WatchRemoteConfig() }
// WatchRemoteConfig updates configuration from available remote providers.
func (v *Viper) WatchRemoteConfig() error {
return v.watchKeyValueConfig()
}
// WatchRemoteConfigOnChannel updates configuration from available remote providers.
func (v *Viper) WatchRemoteConfigOnChannel() error {
return v.watchKeyValueConfigOnChannel()
}
+133 -40
View File
@@ -25,6 +25,7 @@ import (
"errors"
"fmt"
"io"
fs "io/fs"
"log/slog"
"os"
"path/filepath"
@@ -44,49 +45,12 @@ import (
"github.com/spf13/viper/internal/features"
)
// ConfigMarshalError happens when failing to marshal the configuration.
type ConfigMarshalError struct {
err error
}
// Error returns the formatted configuration error.
func (e ConfigMarshalError) Error() string {
return fmt.Sprintf("While marshaling config: %s", e.err.Error())
}
var v *Viper
func init() {
v = New()
}
// UnsupportedConfigError denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string
// Error returns the formatted configuration error.
func (str UnsupportedConfigError) Error() string {
return fmt.Sprintf("Unsupported Config Type %q", string(str))
}
// ConfigFileNotFoundError denotes failing to find configuration file.
type ConfigFileNotFoundError struct {
name, locations string
}
// Error returns the formatted configuration error.
func (fnfe ConfigFileNotFoundError) Error() string {
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
}
// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
type ConfigFileAlreadyExistsError string
// Error returns the formatted error when configuration already exists.
func (faee ConfigFileAlreadyExistsError) Error() string {
return fmt.Sprintf("Config File %q Already Exists", string(faee))
}
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
// mapstructure.DecoderConfig options.
type DecoderConfigOption func(*mapstructure.DecoderConfig)
@@ -392,6 +356,8 @@ func (v *Viper) WatchConfig() {
// Viper will use this and not check any of the config paths.
func SetConfigFile(in string) { v.SetConfigFile(in) }
// SetConfigFile explicitly defines the path, name and extension of the config file.
// Viper will use this and not check any of the config paths.
func (v *Viper) SetConfigFile(in string) {
if in != "" {
v.configFile = in
@@ -403,14 +369,19 @@ func (v *Viper) SetConfigFile(in string) {
// variables that start with "SPF_".
func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }
// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
func (v *Viper) SetEnvPrefix(in string) {
if in != "" {
v.envPrefix = in
}
}
// GetEnvPrefix returns the environment variable prefix.
func GetEnvPrefix() string { return v.GetEnvPrefix() }
// GetEnvPrefix returns the environment variable prefix.
func (v *Viper) GetEnvPrefix() string {
return v.envPrefix
}
@@ -428,6 +399,9 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
// For backward compatibility reasons this is false by default.
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
// AllowEmptyEnv tells Viper to consider set,
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
v.allowEmptyEnv = allowEmptyEnv
}
@@ -450,13 +424,17 @@ func (v *Viper) getEnv(key string) (string, bool) {
}
// ConfigFileUsed returns the file used to populate the config registry.
func ConfigFileUsed() string { return v.ConfigFileUsed() }
func ConfigFileUsed() string { return v.ConfigFileUsed() }
// ConfigFileUsed returns the file used to populate the config registry.
func (v *Viper) ConfigFileUsed() string { return v.configFile }
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func AddConfigPath(in string) { v.AddConfigPath(in) }
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func (v *Viper) AddConfigPath(in string) {
if v.finder != nil {
v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "AddConfigPath"))
@@ -695,6 +673,20 @@ func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
// "a b c"
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
// SetTypeByDefaultValue enables or disables the inference of a key value's
// type when the Get function is used based upon a key's default value as
// opposed to the value returned based on the normal fetch logic.
//
// For example, if a key has a default value of []string{} and the same key
// is set via an environment variable to "a b c", a call to the Get function
// would return a string slice for the key if the key's type is inferred by
// the default value and the Get function would return:
//
// []string {"a", "b", "c"}
//
// Otherwise the Get function would return:
//
// "a b c"
func (v *Viper) SetTypeByDefaultValue(enable bool) {
v.typeByDefValue = enable
}
@@ -713,6 +705,13 @@ func GetViper() *Viper {
// Get returns an interface. For a specific value use one of the Get____ methods.
func Get(key string) any { return v.Get(key) }
// Get retrieves the value associated with the key.
// Get is case-insensitive for a key.
// Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func (v *Viper) Get(key string) any {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, true)
@@ -766,6 +765,8 @@ func (v *Viper) Get(key string) any {
// Sub is case-insensitive for a key.
func Sub(key string) *Viper { return v.Sub(key) }
// Sub returns a new Viper instance representing a sub tree of this instance.
// Sub is case-insensitive for a key.
func (v *Viper) Sub(key string) *Viper {
subv := New()
data := v.Get(key)
@@ -789,6 +790,7 @@ func (v *Viper) Sub(key string) *Viper {
// GetString returns the value associated with the key as a string.
func GetString(key string) string { return v.GetString(key) }
// GetString returns the value associated with the key as a string.
func (v *Viper) GetString(key string) string {
return cast.ToString(v.Get(key))
}
@@ -796,6 +798,7 @@ func (v *Viper) GetString(key string) string {
// GetBool returns the value associated with the key as a boolean.
func GetBool(key string) bool { return v.GetBool(key) }
// GetBool returns the value associated with the key as a boolean.
func (v *Viper) GetBool(key string) bool {
return cast.ToBool(v.Get(key))
}
@@ -803,6 +806,7 @@ func (v *Viper) GetBool(key string) bool {
// GetInt returns the value associated with the key as an integer.
func GetInt(key string) int { return v.GetInt(key) }
// GetInt returns the value associated with the key as an integer.
func (v *Viper) GetInt(key string) int {
return cast.ToInt(v.Get(key))
}
@@ -810,6 +814,7 @@ func (v *Viper) GetInt(key string) int {
// GetInt32 returns the value associated with the key as an integer.
func GetInt32(key string) int32 { return v.GetInt32(key) }
// GetInt32 returns the value associated with the key as an integer.
func (v *Viper) GetInt32(key string) int32 {
return cast.ToInt32(v.Get(key))
}
@@ -817,6 +822,7 @@ func (v *Viper) GetInt32(key string) int32 {
// GetInt64 returns the value associated with the key as an integer.
func GetInt64(key string) int64 { return v.GetInt64(key) }
// GetInt64 returns the value associated with the key as an integer.
func (v *Viper) GetInt64(key string) int64 {
return cast.ToInt64(v.Get(key))
}
@@ -824,6 +830,7 @@ func (v *Viper) GetInt64(key string) int64 {
// GetUint8 returns the value associated with the key as an unsigned integer.
func GetUint8(key string) uint8 { return v.GetUint8(key) }
// GetUint8 returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint8(key string) uint8 {
return cast.ToUint8(v.Get(key))
}
@@ -831,6 +838,7 @@ func (v *Viper) GetUint8(key string) uint8 {
// GetUint returns the value associated with the key as an unsigned integer.
func GetUint(key string) uint { return v.GetUint(key) }
// GetUint returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint(key string) uint {
return cast.ToUint(v.Get(key))
}
@@ -838,6 +846,7 @@ func (v *Viper) GetUint(key string) uint {
// GetUint16 returns the value associated with the key as an unsigned integer.
func GetUint16(key string) uint16 { return v.GetUint16(key) }
// GetUint16 returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint16(key string) uint16 {
return cast.ToUint16(v.Get(key))
}
@@ -845,6 +854,7 @@ func (v *Viper) GetUint16(key string) uint16 {
// GetUint32 returns the value associated with the key as an unsigned integer.
func GetUint32(key string) uint32 { return v.GetUint32(key) }
// GetUint32 returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint32(key string) uint32 {
return cast.ToUint32(v.Get(key))
}
@@ -852,6 +862,7 @@ func (v *Viper) GetUint32(key string) uint32 {
// GetUint64 returns the value associated with the key as an unsigned integer.
func GetUint64(key string) uint64 { return v.GetUint64(key) }
// GetUint64 returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint64(key string) uint64 {
return cast.ToUint64(v.Get(key))
}
@@ -859,6 +870,7 @@ func (v *Viper) GetUint64(key string) uint64 {
// GetFloat64 returns the value associated with the key as a float64.
func GetFloat64(key string) float64 { return v.GetFloat64(key) }
// GetFloat64 returns the value associated with the key as a float64.
func (v *Viper) GetFloat64(key string) float64 {
return cast.ToFloat64(v.Get(key))
}
@@ -866,6 +878,7 @@ func (v *Viper) GetFloat64(key string) float64 {
// GetTime returns the value associated with the key as time.
func GetTime(key string) time.Time { return v.GetTime(key) }
// GetTime returns the value associated with the key as time.
func (v *Viper) GetTime(key string) time.Time {
return cast.ToTime(v.Get(key))
}
@@ -873,6 +886,7 @@ func (v *Viper) GetTime(key string) time.Time {
// GetDuration returns the value associated with the key as a duration.
func GetDuration(key string) time.Duration { return v.GetDuration(key) }
// GetDuration returns the value associated with the key as a duration.
func (v *Viper) GetDuration(key string) time.Duration {
return cast.ToDuration(v.Get(key))
}
@@ -880,6 +894,7 @@ func (v *Viper) GetDuration(key string) time.Duration {
// GetIntSlice returns the value associated with the key as a slice of int values.
func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
// GetIntSlice returns the value associated with the key as a slice of int values.
func (v *Viper) GetIntSlice(key string) []int {
return cast.ToIntSlice(v.Get(key))
}
@@ -887,6 +902,7 @@ func (v *Viper) GetIntSlice(key string) []int {
// GetStringSlice returns the value associated with the key as a slice of strings.
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
// GetStringSlice returns the value associated with the key as a slice of strings.
func (v *Viper) GetStringSlice(key string) []string {
return cast.ToStringSlice(v.Get(key))
}
@@ -894,6 +910,7 @@ func (v *Viper) GetStringSlice(key string) []string {
// GetStringMap returns the value associated with the key as a map of interfaces.
func GetStringMap(key string) map[string]any { return v.GetStringMap(key) }
// GetStringMap returns the value associated with the key as a map of interfaces.
func (v *Viper) GetStringMap(key string) map[string]any {
return cast.ToStringMap(v.Get(key))
}
@@ -901,6 +918,7 @@ func (v *Viper) GetStringMap(key string) map[string]any {
// GetStringMapString returns the value associated with the key as a map of strings.
func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) }
// GetStringMapString returns the value associated with the key as a map of strings.
func (v *Viper) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(v.Get(key))
}
@@ -908,6 +926,7 @@ func (v *Viper) GetStringMapString(key string) map[string]string {
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) }
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
return cast.ToStringMapStringSlice(v.Get(key))
}
@@ -916,6 +935,8 @@ func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
// in bytes.
func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func (v *Viper) GetSizeInBytes(key string) uint {
sizeStr := cast.ToString(v.Get(key))
return parseSizeInBytes(sizeStr)
@@ -926,6 +947,7 @@ func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
return v.UnmarshalKey(key, rawVal, opts...)
}
// UnmarshalKey takes a single key and unmarshals it into a Struct.
func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
return decode(v.Get(key), v.defaultDecoderConfig(rawVal, opts...))
}
@@ -936,6 +958,8 @@ func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
return v.Unmarshal(rawVal, opts...)
}
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
keys := v.AllKeys()
@@ -1036,6 +1060,8 @@ func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
return v.UnmarshalExact(rawVal, opts...)
}
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
config := v.defaultDecoderConfig(rawVal, opts...)
config.ErrorUnused = true
@@ -1060,6 +1086,8 @@ func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
// name as the config key.
func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) }
// BindPFlags binds a full flag set to the configuration, using each flag's long
// name as the config key.
func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
return v.BindFlagValues(pflagValueSet{flags})
}
@@ -1071,6 +1099,11 @@ func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }
// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {
if flag == nil {
return fmt.Errorf("flag for %q is nil", key)
@@ -1082,6 +1115,8 @@ func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {
// name as the config key.
func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) }
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
flags.VisitAll(func(flag FlagValue) {
if err = v.BindFlagValue(flag.Name(), flag); err != nil {
@@ -1094,6 +1129,7 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
// BindFlagValue binds a specific key to a FlagValue.
func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }
// BindFlagValue binds a specific key to a FlagValue.
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
if flag == nil {
return fmt.Errorf("flag for %q is nil", key)
@@ -1110,6 +1146,12 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
// EnvPrefix will be used when set when env name is not provided.
func BindEnv(input ...string) error { return v.BindEnv(input...) }
// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
// If more arguments are provided, they will represent the env variable names that
// should bind to this key and will be taken in the specified order.
// EnvPrefix will be used when set when env name is not provided.
func (v *Viper) BindEnv(input ...string) error {
if len(input) == 0 {
return fmt.Errorf("missing key to bind to")
@@ -1131,6 +1173,9 @@ func (v *Viper) BindEnv(input ...string) error {
// panic.
func MustBindEnv(input ...string) { v.MustBindEnv(input...) }
// MustBindEnv wraps BindEnv in a panic.
// If there is an error binding an environment variable, MustBindEnv will
// panic.
func (v *Viper) MustBindEnv(input ...string) {
if err := v.BindEnv(input...); err != nil {
panic(fmt.Sprintf("error while binding environment variable: %v", err))
@@ -1388,6 +1433,8 @@ func stringToIntConv(val string) any {
// IsSet is case-insensitive for a key.
func IsSet(key string) bool { return v.IsSet(key) }
// IsSet checks to see if the key has been set in any of the data locations.
// IsSet is case-insensitive for a key.
func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, false)
@@ -1398,6 +1445,8 @@ func (v *Viper) IsSet(key string) bool {
// (config, default or flags). If matching env vars are found, they are loaded into Viper.
func AutomaticEnv() { v.AutomaticEnv() }
// AutomaticEnv makes Viper check if environment variables match any of the existing keys
// (config, default or flags). If matching env vars are found, they are loaded into Viper.
func (v *Viper) AutomaticEnv() {
v.automaticEnvApplied = true
}
@@ -1407,6 +1456,9 @@ func (v *Viper) AutomaticEnv() {
// not match it.
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
v.envKeyReplacer = r
}
@@ -1415,6 +1467,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
// This enables one to change a name without breaking the application.
func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) }
// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
func (v *Viper) RegisterAlias(alias, key string) {
v.registerAlias(alias, strings.ToLower(key))
}
@@ -1464,6 +1518,7 @@ func (v *Viper) realKey(key string) string {
// InConfig checks to see if the given key (or an alias) is in the config file.
func InConfig(key string) bool { return v.InConfig(key) }
// InConfig checks to see if the given key (or an alias) is in the config file.
func (v *Viper) InConfig(key string) bool {
lcaseKey := strings.ToLower(key)
@@ -1479,6 +1534,9 @@ func (v *Viper) InConfig(key string) bool {
// Default only used when no value is provided by the user via flag, config or ENV.
func SetDefault(key string, value any) { v.SetDefault(key, value) }
// SetDefault sets the default value for this key.
// SetDefault is case-insensitive for a key.
// Default only used when no value is provided by the user via flag, config or ENV.
func (v *Viper) SetDefault(key string, value any) {
// If alias passed in, then set the proper default
key = v.realKey(strings.ToLower(key))
@@ -1498,6 +1556,10 @@ func (v *Viper) SetDefault(key string, value any) {
// flags, config file, ENV, default, or key/value store.
func Set(key string, value any) { v.Set(key, value) }
// Set sets the value for the key in the override register.
// Set is case-insensitive for a key.
// Will be used instead of values obtained via
// flags, config file, ENV, default, or key/value store.
func (v *Viper) Set(key string, value any) {
// If alias passed in, then set the proper override
key = v.realKey(strings.ToLower(key))
@@ -1515,6 +1577,8 @@ func (v *Viper) Set(key string, value any) {
// and key/value stores, searching in one of the defined paths.
func ReadInConfig() error { return v.ReadInConfig() }
// ReadInConfig will discover and load the configuration file from disk
// and key/value stores, searching in one of the defined paths.
func (v *Viper) ReadInConfig() error {
v.logger.Info("attempting to read in config file")
filename, err := v.getConfigFile()
@@ -1527,8 +1591,13 @@ func (v *Viper) ReadInConfig() error {
}
v.logger.Debug("reading file", "file", filename)
file, err := afero.ReadFile(v.fs, filename)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
// The specified config file is missing
return FileNotFoundError{err: err, path: filename}
} else if err != nil {
// We hit some other error from the filesystem that isn't a missing file
return err
}
@@ -1546,6 +1615,7 @@ func (v *Viper) ReadInConfig() error {
// MergeInConfig merges a new configuration with an existing config.
func MergeInConfig() error { return v.MergeInConfig() }
// MergeInConfig merges a new configuration with an existing config.
func (v *Viper) MergeInConfig() error {
v.logger.Info("attempting to merge in config file")
filename, err := v.getConfigFile()
@@ -1569,6 +1639,8 @@ func (v *Viper) MergeInConfig() error {
// key does not exist in the file.
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
// ReadConfig reads a configuration file, setting existing keys to nil if the
// key does not exist in the file.
func (v *Viper) ReadConfig(in io.Reader) error {
config := make(map[string]any)
@@ -1585,6 +1657,7 @@ func (v *Viper) ReadConfig(in io.Reader) error {
// MergeConfig merges a new configuration with an existing config.
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
// MergeConfig merges a new configuration with an existing config.
func (v *Viper) MergeConfig(in io.Reader) error {
config := make(map[string]any)
@@ -1599,6 +1672,8 @@ func (v *Viper) MergeConfig(in io.Reader) error {
// Note that the map given may be modified.
func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(cfg) }
// MergeConfigMap merges the configuration from the map given with an existing config.
// Note that the map given may be modified.
func (v *Viper) MergeConfigMap(cfg map[string]any) error {
if v.config == nil {
v.config = make(map[string]any)
@@ -1611,6 +1686,7 @@ func (v *Viper) MergeConfigMap(cfg map[string]any) error {
// WriteConfig writes the current configuration to a file.
func WriteConfig() error { return v.WriteConfig() }
// WriteConfig writes the current configuration to a file.
func (v *Viper) WriteConfig() error {
filename, err := v.getConfigFile()
if err != nil {
@@ -1622,6 +1698,7 @@ func (v *Viper) WriteConfig() error {
// SafeWriteConfig writes current configuration to file only if the file does not exist.
func SafeWriteConfig() error { return v.SafeWriteConfig() }
// SafeWriteConfig writes current configuration to file only if the file does not exist.
func (v *Viper) SafeWriteConfig() error {
if len(v.configPaths) < 1 {
return errors.New("missing configuration for 'configPath'")
@@ -1632,6 +1709,7 @@ func (v *Viper) SafeWriteConfig() error {
// WriteConfigAs writes current configuration to a given filename.
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }
// WriteConfigAs writes current configuration to a given filename.
func (v *Viper) WriteConfigAs(filename string) error {
return v.writeConfig(filename, true)
}
@@ -1639,6 +1717,7 @@ func (v *Viper) WriteConfigAs(filename string) error {
// WriteConfigTo writes current configuration to an [io.Writer].
func WriteConfigTo(w io.Writer) error { return v.WriteConfigTo(w) }
// WriteConfigTo writes current configuration to an [io.Writer].
func (v *Viper) WriteConfigTo(w io.Writer) error {
format := strings.ToLower(v.getConfigType())
@@ -1652,6 +1731,7 @@ func (v *Viper) WriteConfigTo(w io.Writer) error {
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
func (v *Viper) SafeWriteConfigAs(filename string) error {
alreadyExists, err := afero.Exists(v.fs, filename)
if alreadyExists && err == nil {
@@ -1885,6 +1965,8 @@ func mergeMaps(src, tgt map[string]any, itgt map[any]any) {
// Nested keys are returned with a v.keyDelim separator.
func AllKeys() []string { return v.AllKeys() }
// AllKeys returns all keys holding a value, regardless of where they are set.
// Nested keys are returned with a v.keyDelim separator.
func (v *Viper) AllKeys() []string {
m := map[string]bool{}
// add all paths, by order of descending priority to ensure correct shadowing
@@ -1967,6 +2049,7 @@ outer:
// AllSettings merges all settings and returns them as a map[string]any.
func AllSettings() map[string]any { return v.AllSettings() }
// AllSettings merges all settings and returns them as a map[string]any.
func (v *Viper) AllSettings() map[string]any {
return v.getSettings(v.AllKeys())
}
@@ -1993,6 +2076,7 @@ func (v *Viper) getSettings(keys []string) map[string]any {
// SetFs sets the filesystem to use to read configuration.
func SetFs(fs afero.Fs) { v.SetFs(fs) }
// SetFs sets the filesystem to use to read configuration.
func (v *Viper) SetFs(fs afero.Fs) {
v.fs = fs
}
@@ -2001,6 +2085,8 @@ func (v *Viper) SetFs(fs afero.Fs) {
// Does not include extension.
func SetConfigName(in string) { v.SetConfigName(in) }
// SetConfigName sets the name for the config file.
// Does not include extension.
func (v *Viper) SetConfigName(in string) {
if v.finder != nil {
v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "SetConfigName"))
@@ -2016,6 +2102,8 @@ func (v *Viper) SetConfigName(in string) {
// remote source, e.g. "json".
func SetConfigType(in string) { v.SetConfigType(in) }
// SetConfigType sets the type of the configuration returned by the
// remote source, e.g. "json".
func (v *Viper) SetConfigType(in string) {
if in != "" {
v.configType = in
@@ -2025,6 +2113,7 @@ func (v *Viper) SetConfigType(in string) {
// SetConfigPermissions sets the permissions for the config file.
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
// SetConfigPermissions sets the permissions for the config file.
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
v.configPermissions = perm.Perm()
}
@@ -2061,11 +2150,15 @@ func (v *Viper) getConfigFile() (string, error) {
// Debug prints all configuration registries for debugging
// purposes.
func Debug() { v.Debug() }
func Debug() { v.Debug() }
// DebugTo writes all configuration registries to the given [io.Writer].
func DebugTo(w io.Writer) { v.DebugTo(w) }
// Debug prints all configuration registries for debugging purposes.
func (v *Viper) Debug() { v.DebugTo(os.Stdout) }
// DebugTo writes all configuration registries to the given [io.Writer].
func (v *Viper) DebugTo(w io.Writer) {
fmt.Fprintf(w, "Aliases:\n%#v\n", v.aliases)
fmt.Fprintf(w, "Override:\n%#v\n", v.override)
+37 -3
View File
@@ -359,7 +359,7 @@ func TestReadInConfig(t *testing.T) {
t.Run("config file set", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir("/etc/viper", 0o777)
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
@@ -1573,6 +1573,28 @@ func TestReadConfigWithSetConfigFile(t *testing.T) {
assert.Equal(t, 45000, v.GetInt("hello.pop"))
}
func TestWrongFileNotFound(t *testing.T) {
_, config := initDirs(t)
v := New()
v.SetConfigName(config)
v.SetDefault(`key`, `default`)
v.SetConfigFile(`whatareyoutalkingabout.yaml`)
err := v.ReadInConfig()
var fileLookupError FileLookupError
// It matches all error types and the shared error interface.
assert.ErrorAs(t, err, &FileNotFoundError{})
assert.ErrorAs(t, err, &fileLookupError)
// Even though config did not load and the error might have
// been ignored by the client, the default still loads
assert.Equal(t, `default`, v.GetString(`key`))
}
func TestIsSet(t *testing.T) {
v := New()
v.SetConfigType("yaml")
@@ -1652,7 +1674,13 @@ func TestWrongDirsSearchNotFound(t *testing.T) {
v.AddConfigPath(`thispathaintthere`)
err := v.ReadInConfig()
assert.IsType(t, ConfigFileNotFoundError{"", ""}, err)
var fileLookupError FileLookupError
// It matches all error types and the shared error interface.
assert.ErrorAs(t, err, &ConfigFileNotFoundError{})
assert.ErrorAs(t, err, &FileNotFoundFromSearchError{})
assert.ErrorAs(t, err, &fileLookupError)
// Even though config did not load and the error might have
// been ignored by the client, the default still loads
@@ -1670,7 +1698,13 @@ func TestWrongDirsSearchNotFoundForMerge(t *testing.T) {
v.AddConfigPath(`thispathaintthere`)
err := v.MergeInConfig()
assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err))
var fileLookupError FileLookupError
// It matches both types of errors.
assert.ErrorAs(t, err, &ConfigFileNotFoundError{})
assert.ErrorAs(t, err, &FileNotFoundFromSearchError{})
assert.ErrorAs(t, err, &fileLookupError)
// Even though config did not load and the error might have
// been ignored by the client, the default still loads