mirror of
https://github.com/spf13/viper.git
synced 2024-04-21 12:31:38 +00:00
Compare commits
133
Commits
@@ -13,3 +13,6 @@ indent_style = tab
|
|||||||
|
|
||||||
[{Makefile,*.mk}]
|
[{Makefile,*.mk}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.nix]
|
||||||
|
indent_size = 2
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
|
||||||
|
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
|
||||||
|
fi
|
||||||
|
use flake . --impure
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 38 KiB |
@@ -11,8 +11,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check minimum labels
|
- name: Check minimum labels
|
||||||
uses: mheap/github-action-required-labels@v2
|
uses: mheap/github-action-required-labels@4e9ef4ce8c697cf55716ecbf7f13a3d9e0b6ac6a # v5.1.0
|
||||||
with:
|
with:
|
||||||
mode: minimum
|
mode: minimum
|
||||||
count: 1
|
count: 1
|
||||||
labels: "release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update"
|
labels: |
|
||||||
|
release-note/ignore
|
||||||
|
kind/feature
|
||||||
|
release-note/new-feature
|
||||||
|
kind/enhancement
|
||||||
|
release-note/enhancement
|
||||||
|
kind/bug
|
||||||
|
release-note/bug-fix
|
||||||
|
release-note/breaking-change
|
||||||
|
release-note/deprecation
|
||||||
|
area/dependencies
|
||||||
|
release-note/dependency-update
|
||||||
|
release-note/misc
|
||||||
|
|||||||
+57
-28
@@ -2,14 +2,14 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [master]
|
||||||
- master
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -20,13 +20,13 @@ jobs:
|
|||||||
goarch: ppc64
|
goarch: ppc64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Checkout repository
|
||||||
uses: actions/setup-go@v3
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
|
||||||
go-version: 1.19
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Set up Go
|
||||||
uses: actions/checkout@v3
|
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||||
|
with:
|
||||||
|
go-version: "1.21"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build .
|
run: go build .
|
||||||
@@ -37,50 +37,79 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
# Fail fast is disabled because there are Go version specific features and tests
|
# Fail fast is disabled because there are Go version specific features and tests
|
||||||
# that should be able to fail independently.
|
# that should be able to fail independently.
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
go: ['1.16', '1.17', '1.18', '1.19']
|
go: ["1.19", "1.20", "1.21"]
|
||||||
tags: ['', 'viper_yaml2', 'viper_toml1']
|
tags: ["", "finder", "viper_bind_struct"]
|
||||||
env:
|
|
||||||
GOFLAGS: -mod=readonly
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -race -tags '${{ matrix.tags }}' -v ./...
|
run: go test -race -v -tags '${{ matrix.tags }}' -shuffle=on ./...
|
||||||
if: runner.os != 'Windows'
|
if: runner.os != 'Windows'
|
||||||
|
|
||||||
- name: Test (without race detector)
|
- name: Test (without race detector)
|
||||||
run: go test -tags '${{ matrix.tags }}' -v ./...
|
run: go test -v -tags '${{ matrix.tags }}' -shuffle=on ./...
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
GOFLAGS: -mod=readonly
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Checkout repository
|
||||||
uses: actions/setup-go@v3
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
|
||||||
go-version: 1.19
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Set up Go
|
||||||
uses: actions/checkout@v3
|
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||||
|
with:
|
||||||
|
go-version: "1.21"
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
|
||||||
with:
|
with:
|
||||||
version: v1.49
|
version: v1.54.2
|
||||||
|
|
||||||
|
dev:
|
||||||
|
name: Developer environment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
|
- name: Set up Nix
|
||||||
|
uses: cachix/install-nix-action@7ac1ec25491415c381d9b62f0657c7a028df52a7 # v24
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Check
|
||||||
|
run: nix flake check --impure
|
||||||
|
|
||||||
|
- name: Dev shell
|
||||||
|
run: nix develop --impure
|
||||||
|
|
||||||
|
dependency-review:
|
||||||
|
name: Dependency review
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
|
- name: Dependency Review
|
||||||
|
uses: actions/dependency-review-action@01bc87099ba56df1e897b6874784491ea6309bc4 # v3.1.4
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -68,5 +68,5 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ jobs:
|
|||||||
comment:
|
comment:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ jobs:
|
|||||||
comment:
|
comment:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
/.devenv/
|
||||||
|
/.direnv/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/.pre-commit-config.yaml
|
||||||
/bin/
|
/bin/
|
||||||
/build/
|
/build/
|
||||||
/var/
|
/var/
|
||||||
|
|||||||
+17
-5
@@ -7,6 +7,16 @@ linters-settings:
|
|||||||
- standard
|
- standard
|
||||||
- default
|
- default
|
||||||
- prefix(github.com/spf13/viper)
|
- prefix(github.com/spf13/viper)
|
||||||
|
gocritic:
|
||||||
|
# Enable multiple checks by tags. See "Tags" section in https://github.com/go-critic/go-critic#usage.
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- style
|
||||||
|
disabled-checks:
|
||||||
|
- importShadow
|
||||||
|
- unnamedResult
|
||||||
golint:
|
golint:
|
||||||
min-confidence: 0
|
min-confidence: 0
|
||||||
goimports:
|
goimports:
|
||||||
@@ -16,13 +26,14 @@ linters:
|
|||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- deadcode
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- gci
|
- gci
|
||||||
|
- gocritic
|
||||||
|
- godot
|
||||||
- gofmt
|
- gofmt
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -43,14 +54,12 @@ linters:
|
|||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- tparallel
|
- tparallel
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
@@ -65,9 +74,7 @@ linters:
|
|||||||
# - gochecknoinits
|
# - gochecknoinits
|
||||||
# - gocognit
|
# - gocognit
|
||||||
# - goconst
|
# - goconst
|
||||||
# - gocritic
|
|
||||||
# - gocyclo
|
# - gocyclo
|
||||||
# - godot
|
|
||||||
# - gosec
|
# - gosec
|
||||||
# - gosimple
|
# - gosimple
|
||||||
# - ifshort
|
# - ifshort
|
||||||
@@ -83,6 +90,11 @@ linters:
|
|||||||
# - goheader
|
# - goheader
|
||||||
# - gomodguard
|
# - gomodguard
|
||||||
|
|
||||||
|
# deprecated
|
||||||
|
# - deadcode
|
||||||
|
# - structcheck
|
||||||
|
# - varcheck
|
||||||
|
|
||||||
# don't enable:
|
# don't enable:
|
||||||
# - asciicheck
|
# - asciicheck
|
||||||
# - funlen
|
# - funlen
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# TODO: FIXME
|
||||||
|
/.github/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ignore-from-file: [.gitignore, .yamlignore]
|
||||||
|
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
rules:
|
||||||
|
line-length: disable
|
||||||
@@ -15,8 +15,8 @@ TEST_FORMAT = short-verbose
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# Dependency versions
|
# Dependency versions
|
||||||
GOTESTSUM_VERSION = 1.8.0
|
GOTESTSUM_VERSION = 1.9.0
|
||||||
GOLANGCI_VERSION = 1.49.0
|
GOLANGCI_VERSION = 1.53.3
|
||||||
|
|
||||||
# Add the ability to override some variables
|
# Add the ability to override some variables
|
||||||
# Use with care
|
# Use with care
|
||||||
@@ -29,11 +29,6 @@ clear: ## Clear the working area and the project
|
|||||||
.PHONY: check
|
.PHONY: check
|
||||||
check: test lint ## Run tests and linters
|
check: test lint ## Run tests and linters
|
||||||
|
|
||||||
bin/gotestsum: bin/gotestsum-${GOTESTSUM_VERSION}
|
|
||||||
@ln -sf gotestsum-${GOTESTSUM_VERSION} bin/gotestsum
|
|
||||||
bin/gotestsum-${GOTESTSUM_VERSION}:
|
|
||||||
@mkdir -p bin
|
|
||||||
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum-${GOTESTSUM_VERSION} && chmod +x ./bin/gotestsum-${GOTESTSUM_VERSION}
|
|
||||||
|
|
||||||
TEST_PKGS ?= ./...
|
TEST_PKGS ?= ./...
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
@@ -44,20 +39,36 @@ test: bin/gotestsum ## Run tests
|
|||||||
@mkdir -p ${BUILD_DIR}
|
@mkdir -p ${BUILD_DIR}
|
||||||
bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
|
bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
|
||||||
|
|
||||||
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
|
||||||
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
|
||||||
bin/golangci-lint-${GOLANGCI_VERSION}:
|
|
||||||
@mkdir -p bin
|
|
||||||
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}
|
|
||||||
@mv bin/golangci-lint "$@"
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: bin/golangci-lint ## Run linter
|
lint: lint-go lint-yaml
|
||||||
bin/golangci-lint run
|
lint: ## Run linters
|
||||||
|
|
||||||
.PHONY: fix
|
.PHONY: lint-go
|
||||||
fix: bin/golangci-lint ## Fix lint violations
|
lint-go:
|
||||||
bin/golangci-lint run --fix
|
golangci-lint run $(if ${CI},--out-format github-actions,)
|
||||||
|
|
||||||
|
.PHONY: lint-yaml
|
||||||
|
lint-yaml:
|
||||||
|
yamllint $(if ${CI},-f github,) --no-warnings .
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: ## Format code
|
||||||
|
golangci-lint run --fix
|
||||||
|
|
||||||
|
deps: bin/golangci-lint bin/gotestsum yamllint
|
||||||
|
deps: ## Install dependencies
|
||||||
|
|
||||||
|
bin/gotestsum:
|
||||||
|
@mkdir -p bin
|
||||||
|
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum && chmod +x ./bin/gotestsum
|
||||||
|
|
||||||
|
bin/golangci-lint:
|
||||||
|
@mkdir -p bin
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- v${GOLANGCI_VERSION}
|
||||||
|
|
||||||
|
.PHONY: yamllint
|
||||||
|
yamllint:
|
||||||
|
pip3 install --user yamllint
|
||||||
|
|
||||||
# Add custom targets here
|
# Add custom targets here
|
||||||
-include custom.mk
|
-include custom.mk
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
[](https://github.com/avelino/awesome-go#configuration)
|
[](https://github.com/avelino/awesome-go#configuration)
|
||||||
[](https://repl.it/@sagikazarmark/Viper-example#main.go)
|
[](https://repl.it/@sagikazarmark/Viper-example#main.go)
|
||||||
|
|
||||||
[](https://github.com/spf13/viper/actions?query=workflow%3ACI)
|
[](https://github.com/spf13/viper/actions?query=workflow%3ACI)
|
||||||
[](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[](https://goreportcard.com/report/github.com/spf13/viper)
|
[](https://goreportcard.com/report/github.com/spf13/viper)
|
||||||

|

|
||||||
[](https://pkg.go.dev/mod/github.com/spf13/viper)
|
[](https://pkg.go.dev/mod/github.com/spf13/viper)
|
||||||
|
|
||||||
**Go configuration with fangs!**
|
**Go configuration with fangs!**
|
||||||
@@ -27,6 +27,10 @@ Many Go projects are built using Viper including:
|
|||||||
* [doctl](https://github.com/digitalocean/doctl)
|
* [doctl](https://github.com/digitalocean/doctl)
|
||||||
* [Clairctl](https://github.com/jgsqware/clairctl)
|
* [Clairctl](https://github.com/jgsqware/clairctl)
|
||||||
* [Mercure](https://mercure.rocks)
|
* [Mercure](https://mercure.rocks)
|
||||||
|
* [Meshery](https://github.com/meshery/meshery)
|
||||||
|
* [Bearer](https://github.com/bearer/bearer)
|
||||||
|
* [Coder](https://github.com/coder/coder)
|
||||||
|
* [Vitess](https://vitess.io/)
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
@@ -40,8 +44,8 @@ go get github.com/spf13/viper
|
|||||||
|
|
||||||
## What is Viper?
|
## What is Viper?
|
||||||
|
|
||||||
Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed
|
Viper is a complete configuration solution for Go applications including [12-Factor apps](https://12factor.net/#the_twelve_factors).
|
||||||
to work within an application, and can handle all types of configuration needs
|
It is designed to work within an application, and can handle all types of configuration needs
|
||||||
and formats. It supports:
|
and formats. It supports:
|
||||||
|
|
||||||
* setting defaults
|
* setting defaults
|
||||||
@@ -137,7 +141,7 @@ if err := viper.ReadInConfig(); err != nil {
|
|||||||
// Config file found and successfully parsed
|
// Config file found and successfully parsed
|
||||||
```
|
```
|
||||||
|
|
||||||
*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
|
*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmatically. For those configuration files that lie in the home of the user without any extension like `.bashrc`
|
||||||
|
|
||||||
### Writing Config Files
|
### Writing Config Files
|
||||||
|
|
||||||
@@ -218,6 +222,7 @@ These could be from a command line flag, or from your own application logic.
|
|||||||
```go
|
```go
|
||||||
viper.Set("Verbose", true)
|
viper.Set("Verbose", true)
|
||||||
viper.Set("LogFile", LogFile)
|
viper.Set("LogFile", LogFile)
|
||||||
|
viper.Set("host.port", 5899) // set subset
|
||||||
```
|
```
|
||||||
|
|
||||||
### Registering and Using Aliases
|
### Registering and Using Aliases
|
||||||
@@ -413,6 +418,8 @@ in a Key/Value store such as etcd or Consul. These values take precedence over
|
|||||||
default values, but are overridden by configuration values retrieved from disk,
|
default values, but are overridden by configuration values retrieved from disk,
|
||||||
flags, or environment variables.
|
flags, or environment variables.
|
||||||
|
|
||||||
|
Viper supports multiple hosts. To use, pass a list of endpoints separated by `;`. For example `http://127.0.0.1:4001;http://127.0.0.1:4002`.
|
||||||
|
|
||||||
Viper uses [crypt](https://github.com/bketelsen/crypt) to retrieve
|
Viper uses [crypt](https://github.com/bketelsen/crypt) to retrieve
|
||||||
configuration from the K/V store, which means that you can store your
|
configuration from the K/V store, which means that you can store your
|
||||||
configuration values encrypted and have them automatically decrypted if you have
|
configuration values encrypted and have them automatically decrypted if you have
|
||||||
@@ -484,6 +491,15 @@ err := viper.ReadRemoteConfig()
|
|||||||
|
|
||||||
Of course, you're allowed to use `SecureRemoteProvider` also
|
Of course, you're allowed to use `SecureRemoteProvider` also
|
||||||
|
|
||||||
|
|
||||||
|
#### NATS
|
||||||
|
|
||||||
|
```go
|
||||||
|
viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config")
|
||||||
|
viper.SetConfigType("json")
|
||||||
|
err := viper.ReadRemoteConfig()
|
||||||
|
```
|
||||||
|
|
||||||
### Remote Key/Value Store Example - Encrypted
|
### Remote Key/Value Store Example - Encrypted
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -531,24 +547,27 @@ go func(){
|
|||||||
In Viper, there are a few ways to get a value depending on the value’s type.
|
In Viper, there are a few ways to get a value depending on the value’s type.
|
||||||
The following functions and methods exist:
|
The following functions and methods exist:
|
||||||
|
|
||||||
* `Get(key string) : interface{}`
|
* `Get(key string) : any`
|
||||||
* `GetBool(key string) : bool`
|
* `GetBool(key string) : bool`
|
||||||
* `GetFloat64(key string) : float64`
|
* `GetFloat64(key string) : float64`
|
||||||
* `GetInt(key string) : int`
|
* `GetInt(key string) : int`
|
||||||
* `GetIntSlice(key string) : []int`
|
* `GetIntSlice(key string) : []int`
|
||||||
* `GetString(key string) : string`
|
* `GetString(key string) : string`
|
||||||
* `GetStringMap(key string) : map[string]interface{}`
|
* `GetStringMap(key string) : map[string]any`
|
||||||
* `GetStringMapString(key string) : map[string]string`
|
* `GetStringMapString(key string) : map[string]string`
|
||||||
* `GetStringSlice(key string) : []string`
|
* `GetStringSlice(key string) : []string`
|
||||||
* `GetTime(key string) : time.Time`
|
* `GetTime(key string) : time.Time`
|
||||||
* `GetDuration(key string) : time.Duration`
|
* `GetDuration(key string) : time.Duration`
|
||||||
* `IsSet(key string) : bool`
|
* `IsSet(key string) : bool`
|
||||||
* `AllSettings() : map[string]interface{}`
|
* `AllSettings() : map[string]any`
|
||||||
|
|
||||||
One important thing to recognize is that each Get function will return a zero
|
One important thing to recognize is that each Get function will return a zero
|
||||||
value if it’s not found. To check if a given key exists, the `IsSet()` method
|
value if it’s not found. To check if a given key exists, the `IsSet()` method
|
||||||
has been provided.
|
has been provided.
|
||||||
|
|
||||||
|
The zero value will also be returned if the value is set, but fails to parse
|
||||||
|
as the requested type.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```go
|
```go
|
||||||
viper.GetString("logfile") // case-insensitive Setting & Getting
|
viper.GetString("logfile") // case-insensitive Setting & Getting
|
||||||
@@ -706,8 +725,8 @@ etc.
|
|||||||
|
|
||||||
There are two methods to do this:
|
There are two methods to do this:
|
||||||
|
|
||||||
* `Unmarshal(rawVal interface{}) : error`
|
* `Unmarshal(rawVal any) : error`
|
||||||
* `UnmarshalKey(key string, rawVal interface{}) : error`
|
* `UnmarshalKey(key string, rawVal any) : error`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -732,9 +751,9 @@ you have to change the delimiter:
|
|||||||
```go
|
```go
|
||||||
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
||||||
|
|
||||||
v.SetDefault("chart::values", map[string]interface{}{
|
v.SetDefault("chart::values", map[string]any{
|
||||||
"ingress": map[string]interface{}{
|
"ingress": map[string]any{
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]any{
|
||||||
"traefik.frontend.rule.type": "PathPrefix",
|
"traefik.frontend.rule.type": "PathPrefix",
|
||||||
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
||||||
},
|
},
|
||||||
@@ -743,7 +762,7 @@ v.SetDefault("chart::values", map[string]interface{}{
|
|||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
Chart struct{
|
Chart struct{
|
||||||
Values map[string]interface{}
|
Values map[string]any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +802,7 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
|
Viper uses [github.com/go-viper/mapstructure](https://github.com/go-viper/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
|
||||||
|
|
||||||
### Decoding custom formats
|
### Decoding custom formats
|
||||||
|
|
||||||
@@ -879,3 +898,31 @@ No, you will need to synchronize access to the viper yourself (for example by us
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
**For an optimal developer experience, it is recommended to install [Nix](https://nixos.org/download.html) and [direnv](https://direnv.net/docs/installation.html).**
|
||||||
|
|
||||||
|
_Alternatively, install [Go](https://go.dev/dl/) on your computer then run `make deps` to install the rest of the dependencies._
|
||||||
|
|
||||||
|
Run the test suite:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
Run linters:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make lint # pass -j option to run them in parallel
|
||||||
|
```
|
||||||
|
|
||||||
|
Some linter violations can automatically be fixed:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The project is licensed under the [MIT License](LICENSE).
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build viper_logger
|
|
||||||
// +build viper_logger
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
// WithLogger sets a custom logger.
|
|
||||||
func WithLogger(l Logger) Option {
|
|
||||||
return optionFunc(func(v *Viper) {
|
|
||||||
v.logger = l
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !go1.16 || !finder
|
//go:build !finder
|
||||||
// +build !go1.16 !finder
|
|
||||||
|
|
||||||
package viper
|
package viper
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ func (v *Viper) searchInPath(in string) (filename string) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if file Exists
|
// exists checks if file exists.
|
||||||
func exists(fs afero.Fs, path string) (bool, error) {
|
func exists(fs afero.Fs, path string) (bool, error) {
|
||||||
stat, err := fs.Stat(path)
|
stat, err := fs.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build finder
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sagikazarmark/locafero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search all configPaths for any config file.
|
||||||
|
// Returns the first path that exists (and is a config file).
|
||||||
|
func (v *Viper) findConfigFile() (string, error) {
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
if v.configType != "" {
|
||||||
|
names = locafero.NameWithOptionalExtensions(v.configName, SupportedExts...)
|
||||||
|
} else {
|
||||||
|
names = locafero.NameWithExtensions(v.configName, SupportedExts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
finder := locafero.Finder{
|
||||||
|
Paths: v.configPaths,
|
||||||
|
Names: names,
|
||||||
|
Type: locafero.FileTypeFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := finder.Find(v.fs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results[0], nil
|
||||||
|
}
|
||||||
@@ -30,8 +30,8 @@ func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// pflagValue is a wrapper aroung *pflag.flag
|
// pflagValue is a wrapper around *pflag.flag
|
||||||
// that implements FlagValue
|
// that implements FlagValue.
|
||||||
type pflagValue struct {
|
type pflagValue struct {
|
||||||
flag *pflag.Flag
|
flag *pflag.Flag
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -5,9 +5,11 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBindFlagValueSet(t *testing.T) {
|
func TestBindFlagValueSet(t *testing.T) {
|
||||||
|
Reset()
|
||||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
||||||
testValues := map[string]*string{
|
testValues := map[string]*string{
|
||||||
@@ -29,9 +31,7 @@ func TestBindFlagValueSet(t *testing.T) {
|
|||||||
flagValueSet := pflagValueSet{flagSet}
|
flagValueSet := pflagValueSet{flagSet}
|
||||||
|
|
||||||
err := BindFlagValues(flagValueSet)
|
err := BindFlagValues(flagValueSet)
|
||||||
if err != nil {
|
require.NoError(t, err, "error binding flag set")
|
||||||
t.Fatalf("error binding flag set, %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
flagSet.VisitAll(func(flag *pflag.Flag) {
|
flagSet.VisitAll(func(flag *pflag.Flag) {
|
||||||
flag.Value.Set(mutatedTestValues[flag.Name])
|
flag.Value.Set(mutatedTestValues[flag.Name])
|
||||||
@@ -39,7 +39,7 @@ func TestBindFlagValueSet(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for name, expected := range mutatedTestValues {
|
for name, expected := range mutatedTestValues {
|
||||||
assert.Equal(t, Get(name), expected)
|
assert.Equal(t, expected, Get(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+255
@@ -0,0 +1,255 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"devenv": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nix": "nix",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687972261,
|
||||||
|
"narHash": "sha256-+mxvZfwMVoaZYETmuQWqTi/7T9UKoAE+WpdSQkOVJ2g=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "e85df562088573305e55906eaa964341f8cb0d9f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1673956053,
|
||||||
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687762428,
|
||||||
|
"narHash": "sha256-DIf7mi45PKo+s8dOYF+UlXHzE0Wl/+k3tXUyAoAnoGE=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "37dd7bb15791c86d55c5121740a1887ab55ee836",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667395993,
|
||||||
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"pre-commit-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1660459072,
|
||||||
|
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lowdown-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1633514407,
|
||||||
|
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix": {
|
||||||
|
"inputs": {
|
||||||
|
"lowdown-src": "lowdown-src",
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676545802,
|
||||||
|
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
|
||||||
|
"owner": "domenkozar",
|
||||||
|
"repo": "nix",
|
||||||
|
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "domenkozar",
|
||||||
|
"ref": "relaxed-flakes",
|
||||||
|
"repo": "nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1678875422,
|
||||||
|
"narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "lib",
|
||||||
|
"lastModified": 1685564631,
|
||||||
|
"narHash": "sha256-8ywr3AkblY4++3lIVxmrWZFzac7+f32ZEhH/A8pNscI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "4f53efe34b3a8877ac923b9350c874e3dcd5dc0a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "lib",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-regression": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1643052045,
|
||||||
|
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-stable": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1678872516,
|
||||||
|
"narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-22.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687886075,
|
||||||
|
"narHash": "sha256-PeayJDDDy+uw1Ats4moZnRdL1OFuZm1Tj+KiHlD67+o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a565059a348422af5af9026b5174dc5c0dcefdae",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pre-commit-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"devenv",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1686050334,
|
||||||
|
"narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"devenv": "devenv",
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
description = "Viper";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
devenv.url = "github:cachix/devenv";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs@{ flake-parts, ... }:
|
||||||
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
imports = [
|
||||||
|
inputs.devenv.flakeModule
|
||||||
|
];
|
||||||
|
|
||||||
|
systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
|
|
||||||
|
perSystem = { config, self', inputs', pkgs, system, ... }: rec {
|
||||||
|
devenv.shells = {
|
||||||
|
default = {
|
||||||
|
languages = {
|
||||||
|
go.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
pre-commit.hooks = {
|
||||||
|
nixpkgs-fmt.enable = true;
|
||||||
|
yamllint.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
gnumake
|
||||||
|
|
||||||
|
golangci-lint
|
||||||
|
yamllint
|
||||||
|
];
|
||||||
|
|
||||||
|
scripts = {
|
||||||
|
versions.exec = ''
|
||||||
|
go version
|
||||||
|
golangci-lint version
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
enterShell = ''
|
||||||
|
versions
|
||||||
|
'';
|
||||||
|
|
||||||
|
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||||
|
containers = pkgs.lib.mkForce { };
|
||||||
|
};
|
||||||
|
|
||||||
|
ci = devenv.shells.default;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
//go:build go1.16 && finder
|
|
||||||
// +build go1.16,finder
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type finder struct {
|
|
||||||
paths []string
|
|
||||||
fileNames []string
|
|
||||||
extensions []string
|
|
||||||
|
|
||||||
withoutExtension bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f finder) Find(fsys fs.FS) (string, error) {
|
|
||||||
for _, searchPath := range f.paths {
|
|
||||||
for _, fileName := range f.fileNames {
|
|
||||||
for _, extension := range f.extensions {
|
|
||||||
filePath := path.Join(searchPath, fileName+"."+extension)
|
|
||||||
|
|
||||||
ok, err := fileExists(fsys, filePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return filePath, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.withoutExtension {
|
|
||||||
filePath := path.Join(searchPath, fileName)
|
|
||||||
|
|
||||||
ok, err := fileExists(fsys, filePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return filePath, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileExists(fsys fs.FS, filePath string) (bool, error) {
|
|
||||||
fileInfo, err := fs.Stat(fsys, filePath)
|
|
||||||
if err == nil {
|
|
||||||
return !fileInfo.IsDir(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
-100
@@ -1,100 +0,0 @@
|
|||||||
//go:build go1.16 && finder
|
|
||||||
// +build go1.16,finder
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFinder(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fsys := fstest.MapFS{
|
|
||||||
"home/user/.config": &fstest.MapFile{},
|
|
||||||
"home/user/config.json": &fstest.MapFile{},
|
|
||||||
"home/user/config.yaml": &fstest.MapFile{},
|
|
||||||
"home/user/data.json": &fstest.MapFile{},
|
|
||||||
"etc/config/.config": &fstest.MapFile{},
|
|
||||||
"etc/config/a_random_file.txt": &fstest.MapFile{},
|
|
||||||
"etc/config/config.json": &fstest.MapFile{},
|
|
||||||
"etc/config/config.yaml": &fstest.MapFile{},
|
|
||||||
"etc/config/config.xml": &fstest.MapFile{},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
fsys func() fs.FS
|
|
||||||
finder finder
|
|
||||||
result string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "find file",
|
|
||||||
fsys: func() fs.FS { return fsys },
|
|
||||||
finder: finder{
|
|
||||||
paths: []string{"etc/config"},
|
|
||||||
fileNames: []string{"config"},
|
|
||||||
extensions: []string{"json"},
|
|
||||||
},
|
|
||||||
result: "etc/config/config.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file not found",
|
|
||||||
fsys: func() fs.FS { return fsys },
|
|
||||||
finder: finder{
|
|
||||||
paths: []string{"var/config"},
|
|
||||||
fileNames: []string{"config"},
|
|
||||||
extensions: []string{"json"},
|
|
||||||
},
|
|
||||||
result: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty search params",
|
|
||||||
fsys: func() fs.FS { return fsys },
|
|
||||||
finder: finder{},
|
|
||||||
result: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "precedence",
|
|
||||||
fsys: func() fs.FS { return fsys },
|
|
||||||
finder: finder{
|
|
||||||
paths: []string{"var/config", "home/user", "etc/config"},
|
|
||||||
fileNames: []string{"aconfig", "config"},
|
|
||||||
extensions: []string{"zml", "xml", "json"},
|
|
||||||
},
|
|
||||||
result: "home/user/config.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "without extension",
|
|
||||||
fsys: func() fs.FS { return fsys },
|
|
||||||
finder: finder{
|
|
||||||
paths: []string{"var/config", "home/user", "etc/config"},
|
|
||||||
fileNames: []string{".config"},
|
|
||||||
extensions: []string{"zml", "xml", "json"},
|
|
||||||
|
|
||||||
withoutExtension: true,
|
|
||||||
},
|
|
||||||
result: "home/user/.config",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fsys := testCase.fsys()
|
|
||||||
|
|
||||||
result, err := testCase.finder.Find(fsys)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.result, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +1,85 @@
|
|||||||
module github.com/spf13/viper
|
module github.com/spf13/viper
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1
|
||||||
github.com/hashicorp/hcl v1.0.0
|
github.com/hashicorp/hcl v1.0.0
|
||||||
github.com/magiconair/properties v1.8.6
|
github.com/magiconair/properties v1.8.7
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/sagikazarmark/crypt v0.17.0
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5
|
github.com/sagikazarmark/locafero v0.4.0
|
||||||
github.com/sagikazarmark/crypt v0.8.0
|
github.com/sagikazarmark/slog-shim v0.1.0
|
||||||
github.com/spf13/afero v1.9.2
|
github.com/spf13/afero v1.11.0
|
||||||
github.com/spf13/cast v1.5.0
|
github.com/spf13/cast v1.6.0
|
||||||
github.com/spf13/jwalterweatherman v1.1.0
|
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/subosito/gotenv v1.4.1
|
github.com/subosito/gotenv v1.6.0
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.104.0 // indirect
|
cloud.google.com/go v0.110.10 // indirect
|
||||||
cloud.google.com/go/compute v1.12.1 // indirect
|
cloud.google.com/go/compute v1.23.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
cloud.google.com/go/firestore v1.8.0 // indirect
|
cloud.google.com/go/firestore v1.14.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.0 // indirect
|
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||||
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.0 // indirect
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/fatih/color v1.13.0 // indirect
|
github.com/fatih/color v1.14.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
github.com/hashicorp/consul/api v1.15.3 // indirect
|
github.com/hashicorp/consul/api v1.25.1 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/hashicorp/serf v0.9.8 // indirect
|
github.com/hashicorp/serf v0.10.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/klauspost/compress v1.17.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/nats-io/nats.go v1.31.0 // indirect
|
||||||
|
github.com/nats-io/nkeys v0.4.6 // indirect
|
||||||
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.5 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
|
||||||
go.etcd.io/etcd/client/v2 v2.305.5 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.5 // indirect
|
go.etcd.io/etcd/client/v2 v2.305.10 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.8.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.21.0 // indirect
|
go.uber.org/zap v1.21.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
golang.org/x/crypto v0.16.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/oauth2 v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/text v0.4.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
google.golang.org/api v0.102.0 // indirect
|
google.golang.org/api v0.153.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
|
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
google.golang.org/grpc v1.50.1 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||||
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Decoder decodes the contents of b into v.
|
// Decoder decodes the contents of b into v.
|
||||||
// It's primarily used for decoding contents of a file into a map[string]interface{}.
|
// It's primarily used for decoding contents of a file into a map[string]any.
|
||||||
type Decoder interface {
|
type Decoder interface {
|
||||||
Decode(b []byte, v map[string]interface{}) error
|
Decode(b []byte, v map[string]any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -48,7 +48,7 @@ func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode calls the underlying Decoder based on the format.
|
// Decode calls the underlying Decoder based on the format.
|
||||||
func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
|
func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]any) error {
|
||||||
e.mu.RLock()
|
e.mu.RLock()
|
||||||
decoder, ok := e.decoders[format]
|
decoder, ok := e.decoders[format]
|
||||||
e.mu.RUnlock()
|
e.mu.RUnlock()
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package encoding
|
package encoding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type decoder struct {
|
type decoder struct {
|
||||||
v map[string]interface{}
|
v map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
|
func (d decoder) Decode(_ []byte, v map[string]any) error {
|
||||||
for key, value := range d.v {
|
for key, value := range d.v {
|
||||||
v[key] = value
|
v[key] = value
|
||||||
}
|
}
|
||||||
@@ -22,23 +24,17 @@ func TestDecoderRegistry_RegisterDecoder(t *testing.T) {
|
|||||||
registry := NewDecoderRegistry()
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
err := registry.RegisterDecoder("myformat", decoder{})
|
err := registry.RegisterDecoder("myformat", decoder{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("AlreadyRegistered", func(t *testing.T) {
|
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||||
registry := NewDecoderRegistry()
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
err := registry.RegisterDecoder("myformat", decoder{})
|
err := registry.RegisterDecoder("myformat", decoder{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = registry.RegisterDecoder("myformat", decoder{})
|
err = registry.RegisterDecoder("myformat", decoder{})
|
||||||
if err != ErrDecoderFormatAlreadyRegistered {
|
assert.ErrorIs(t, err, ErrDecoderFormatAlreadyRegistered)
|
||||||
t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,36 +42,28 @@ func TestDecoderRegistry_Decode(t *testing.T) {
|
|||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
registry := NewDecoderRegistry()
|
registry := NewDecoderRegistry()
|
||||||
decoder := decoder{
|
decoder := decoder{
|
||||||
v: map[string]interface{}{
|
v: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := registry.RegisterDecoder("myformat", decoder)
|
err := registry.RegisterDecoder("myformat", decoder)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err = registry.Decode("myformat", []byte("key: value"), v)
|
err = registry.Decode("myformat", []byte("key: value"), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(decoder.v, v) {
|
assert.Equal(t, decoder.v, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("DecoderNotFound", func(t *testing.T) {
|
t.Run("DecoderNotFound", func(t *testing.T) {
|
||||||
registry := NewDecoderRegistry()
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := registry.Decode("myformat", nil, v)
|
err := registry.Decode("myformat", nil, v)
|
||||||
if err != ErrDecoderNotFound {
|
assert.ErrorIs(t, err, ErrDecoderNotFound)
|
||||||
t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const keyDelimiter = "_"
|
|||||||
// (commonly called as dotenv format).
|
// (commonly called as dotenv format).
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
flattened := map[string]interface{}{}
|
flattened := map[string]any{}
|
||||||
|
|
||||||
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (Codec) Decode(b []byte, v map[string]any) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
_, err := buf.Write(b)
|
_, err := buf.Write(b)
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
package dotenv
|
package dotenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data.
|
||||||
const original = `# key-value pair
|
const original = `# key-value pair
|
||||||
KEY=value
|
KEY=value
|
||||||
`
|
`
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `KEY=value
|
const encoded = `KEY=value
|
||||||
`
|
`
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"KEY": "value",
|
"KEY": "value",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,40 +25,30 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(data, v) {
|
assert.Equal(t, data, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,27 +7,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// flattenAndMergeMap recursively flattens the given map into a new map
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
// Code is based on the function with the same name in tha main package.
|
// Code is based on the function with the same name in the main package.
|
||||||
// TODO: move it to a common place
|
// TODO: move it to a common place.
|
||||||
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any {
|
||||||
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
// prefix is shadowed => nothing more to flatten
|
// prefix is shadowed => nothing more to flatten
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
if shadow == nil {
|
if shadow == nil {
|
||||||
shadow = make(map[string]interface{})
|
shadow = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
var m2 map[string]interface{}
|
var m2 map[string]any
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += delimiter
|
prefix += delimiter
|
||||||
}
|
}
|
||||||
for k, val := range m {
|
for k, val := range m {
|
||||||
fullKey := prefix + k
|
fullKey := prefix + k
|
||||||
switch val.(type) {
|
switch val := val.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
m2 = val.(map[string]interface{})
|
m2 = val
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
m2 = cast.ToStringMap(val)
|
m2 = cast.ToStringMap(val)
|
||||||
default:
|
default:
|
||||||
// immediate value
|
// immediate value
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Encoder encodes the contents of v into a byte representation.
|
// Encoder encodes the contents of v into a byte representation.
|
||||||
// It's primarily used for encoding a map[string]interface{} into a file format.
|
// It's primarily used for encoding a map[string]any into a file format.
|
||||||
type Encoder interface {
|
type Encoder interface {
|
||||||
Encode(v map[string]interface{}) ([]byte, error)
|
Encode(v map[string]any) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -47,7 +47,7 @@ func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
|
func (e *EncoderRegistry) Encode(format string, v map[string]any) ([]byte, error) {
|
||||||
e.mu.RLock()
|
e.mu.RLock()
|
||||||
encoder, ok := e.encoders[format]
|
encoder, ok := e.encoders[format]
|
||||||
e.mu.RUnlock()
|
e.mu.RUnlock()
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package encoding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type encoder struct {
|
type encoder struct {
|
||||||
b []byte
|
b []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
|
func (e encoder) Encode(_ map[string]any) ([]byte, error) {
|
||||||
return e.b, nil
|
return e.b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,23 +20,17 @@ func TestEncoderRegistry_RegisterEncoder(t *testing.T) {
|
|||||||
registry := NewEncoderRegistry()
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
err := registry.RegisterEncoder("myformat", encoder{})
|
err := registry.RegisterEncoder("myformat", encoder{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("AlreadyRegistered", func(t *testing.T) {
|
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||||
registry := NewEncoderRegistry()
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
err := registry.RegisterEncoder("myformat", encoder{})
|
err := registry.RegisterEncoder("myformat", encoder{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = registry.RegisterEncoder("myformat", encoder{})
|
err = registry.RegisterEncoder("myformat", encoder{})
|
||||||
if err != ErrEncoderFormatAlreadyRegistered {
|
assert.ErrorIs(t, err, ErrEncoderFormatAlreadyRegistered)
|
||||||
t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,26 +42,18 @@ func TestEncoderRegistry_Decode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := registry.RegisterEncoder("myformat", encoder)
|
err := registry.RegisterEncoder("myformat", encoder)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
b, err := registry.Encode("myformat", map[string]any{"key": "value"})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(b) != "key: value" {
|
assert.Equal(t, "key: value", string(b))
|
||||||
t.Fatalf("expected 'key: value', got: %#v", string(b))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("EncoderNotFound", func(t *testing.T) {
|
t.Run("EncoderNotFound", func(t *testing.T) {
|
||||||
registry := NewEncoderRegistry()
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
_, err := registry.Encode("myformat", map[string]any{"key": "value"})
|
||||||
if err != ErrEncoderNotFound {
|
assert.ErrorIs(t, err, ErrEncoderNotFound)
|
||||||
t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
// TODO: add printer config to the codec?
|
// TODO: add printer config to the codec?
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -35,6 +35,6 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (Codec) Decode(b []byte, v map[string]any) error {
|
||||||
return hcl.Unmarshal(b, &v)
|
return hcl.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package hcl
|
package hcl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data.
|
||||||
const original = `# key-value pair
|
const original = `# key-value pair
|
||||||
"key" = "value"
|
"key" = "value"
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ nested map
|
|||||||
"list" = ["item1", "item2", "item3"]
|
"list" = ["item1", "item2", "item3"]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `"key" = "value"
|
const encoded = `"key" = "value"
|
||||||
|
|
||||||
"list" = ["item1", "item2", "item3"]
|
"list" = ["item1", "item2", "item3"]
|
||||||
@@ -41,28 +43,28 @@ const encoded = `"key" = "value"
|
|||||||
"list" = ["item1", "item2", "item3"]
|
"list" = ["item1", "item2", "item3"]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// decoded form of the data
|
// decoded form of the data.
|
||||||
//
|
//
|
||||||
// in case of HCL it's slightly different from Viper's internal representation
|
// In case of HCL it's slightly different from Viper's internal representation
|
||||||
// (eg. map is decoded into a list of maps)
|
// (e.g. map is decoded into a list of maps).
|
||||||
var decoded = map[string]interface{}{
|
var decoded = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
},
|
},
|
||||||
"map": []map[string]interface{}{
|
"map": []map[string]any{
|
||||||
{
|
{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"nested_map": []map[string]interface{}{
|
"nested_map": []map[string]any{
|
||||||
{
|
{
|
||||||
"map": []map[string]interface{}{
|
"map": []map[string]any{
|
||||||
{
|
{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
@@ -73,21 +75,21 @@ var decoded = map[string]interface{}{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
},
|
},
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
"nested_map": map[string]interface{}{
|
"nested_map": map[string]any{
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
@@ -100,40 +102,30 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(decoded, v) {
|
assert.Equal(t, decoded, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ type Codec struct {
|
|||||||
LoadOptions LoadOptions
|
LoadOptions LoadOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (c Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
cfg := ini.Empty()
|
cfg := ini.Empty()
|
||||||
ini.PrettyFormat = false
|
ini.PrettyFormat = false
|
||||||
|
|
||||||
flattened := map[string]interface{}{}
|
flattened := map[string]any{}
|
||||||
|
|
||||||
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (c Codec) Decode(b []byte, v map[string]any) error {
|
||||||
cfg := ini.Empty(c.LoadOptions)
|
cfg := ini.Empty(c.LoadOptions)
|
||||||
|
|
||||||
err := cfg.Append(b)
|
err := cfg.Append(b)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package ini
|
package ini
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data.
|
||||||
const original = `; key-value pair
|
const original = `; key-value pair
|
||||||
key=value ; key-value pair
|
key=value ; key-value pair
|
||||||
|
|
||||||
@@ -15,30 +17,30 @@ key=%(key)s
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `key=value
|
const encoded = `key=value
|
||||||
|
|
||||||
[map]
|
[map]
|
||||||
key=value
|
key=value
|
||||||
`
|
`
|
||||||
|
|
||||||
// decoded form of the data
|
// decoded form of the data.
|
||||||
//
|
//
|
||||||
// in case of INI it's slightly different from Viper's internal representation
|
// In case of INI it's slightly different from Viper's internal representation
|
||||||
// (eg. top level keys land in a section called default)
|
// (e.g. top level keys land in a section called default).
|
||||||
var decoded = map[string]interface{}{
|
var decoded = map[string]any{
|
||||||
"DEFAULT": map[string]interface{}{
|
"DEFAULT": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -48,35 +50,27 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Default", func(t *testing.T) {
|
t.Run("Default", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]any{
|
||||||
"default": map[string]interface{}{
|
"default": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,27 +78,21 @@ func TestCodec_Decode(t *testing.T) {
|
|||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(decoded, v) {
|
assert.Equal(t, decoded, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,22 +15,22 @@ import (
|
|||||||
// In case intermediate keys do not exist, or map to a non-map value,
|
// In case intermediate keys do not exist, or map to a non-map value,
|
||||||
// a new map is created and inserted, and the search continues from there:
|
// a new map is created and inserted, and the search continues from there:
|
||||||
// the initial map "m" may be modified!
|
// the initial map "m" may be modified!
|
||||||
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
func deepSearch(m map[string]any, path []string) map[string]any {
|
||||||
for _, k := range path {
|
for _, k := range path {
|
||||||
m2, ok := m[k]
|
m2, ok := m[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key does not exist
|
// intermediate key does not exist
|
||||||
// => create it and continue from there
|
// => create it and continue from there
|
||||||
m3 := make(map[string]interface{})
|
m3 := make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
m = m3
|
m = m3
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m3, ok := m2.(map[string]interface{})
|
m3, ok := m2.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key is a value
|
// intermediate key is a value
|
||||||
// => replace with a new map
|
// => replace with a new map
|
||||||
m3 = make(map[string]interface{})
|
m3 = make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
}
|
}
|
||||||
// continue search from here
|
// continue search from here
|
||||||
@@ -40,27 +40,27 @@ func deepSearch(m map[string]interface{}, path []string) map[string]interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flattenAndMergeMap recursively flattens the given map into a new map
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
// Code is based on the function with the same name in tha main package.
|
// Code is based on the function with the same name in the main package.
|
||||||
// TODO: move it to a common place
|
// TODO: move it to a common place.
|
||||||
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any {
|
||||||
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
// prefix is shadowed => nothing more to flatten
|
// prefix is shadowed => nothing more to flatten
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
if shadow == nil {
|
if shadow == nil {
|
||||||
shadow = make(map[string]interface{})
|
shadow = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
var m2 map[string]interface{}
|
var m2 map[string]any
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += delimiter
|
prefix += delimiter
|
||||||
}
|
}
|
||||||
for k, val := range m {
|
for k, val := range m {
|
||||||
fullKey := prefix + k
|
fullKey := prefix + k
|
||||||
switch val.(type) {
|
switch val := val.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
m2 = val.(map[string]interface{})
|
m2 = val
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
m2 = cast.ToStringMap(val)
|
m2 = cast.ToStringMap(val)
|
||||||
default:
|
default:
|
||||||
// immediate value
|
// immediate value
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ type Codec struct {
|
|||||||
Properties *properties.Properties
|
Properties *properties.Properties
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (c *Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
if c.Properties == nil {
|
if c.Properties == nil {
|
||||||
c.Properties = properties.NewProperties()
|
c.Properties = properties.NewProperties()
|
||||||
}
|
}
|
||||||
|
|
||||||
flattened := map[string]interface{}{}
|
flattened := map[string]any{}
|
||||||
|
|
||||||
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (c *Codec) Decode(b []byte, v map[string]any) error {
|
||||||
var err error
|
var err error
|
||||||
c.Properties, err = properties.Load(b, properties.UTF8)
|
c.Properties, err = properties.Load(b, properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
package javaproperties
|
package javaproperties
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data.
|
||||||
const original = `#key-value pair
|
const original = `#key-value pair
|
||||||
key = value
|
key = value
|
||||||
map.key = value
|
map.key = value
|
||||||
`
|
`
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `key = value
|
const encoded = `key = value
|
||||||
map.key = value
|
map.key = value
|
||||||
`
|
`
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -28,29 +30,21 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(data, v) {
|
assert.Equal(t, data, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
@@ -58,32 +52,24 @@ func TestCodec_Decode(t *testing.T) {
|
|||||||
|
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
codec.Decode([]byte(``), v)
|
codec.Decode([]byte(``), v)
|
||||||
|
|
||||||
if len(v) > 0 {
|
assert.Empty(t, v)
|
||||||
t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_DecodeEncode(t *testing.T) {
|
func TestCodec_DecodeEncode(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if original != string(b) {
|
assert.Equal(t, original, string(b))
|
||||||
t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,22 @@ import (
|
|||||||
// In case intermediate keys do not exist, or map to a non-map value,
|
// In case intermediate keys do not exist, or map to a non-map value,
|
||||||
// a new map is created and inserted, and the search continues from there:
|
// a new map is created and inserted, and the search continues from there:
|
||||||
// the initial map "m" may be modified!
|
// the initial map "m" may be modified!
|
||||||
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
func deepSearch(m map[string]any, path []string) map[string]any {
|
||||||
for _, k := range path {
|
for _, k := range path {
|
||||||
m2, ok := m[k]
|
m2, ok := m[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key does not exist
|
// intermediate key does not exist
|
||||||
// => create it and continue from there
|
// => create it and continue from there
|
||||||
m3 := make(map[string]interface{})
|
m3 := make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
m = m3
|
m = m3
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m3, ok := m2.(map[string]interface{})
|
m3, ok := m2.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key is a value
|
// intermediate key is a value
|
||||||
// => replace with a new map
|
// => replace with a new map
|
||||||
m3 = make(map[string]interface{})
|
m3 = make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
}
|
}
|
||||||
// continue search from here
|
// continue search from here
|
||||||
@@ -40,27 +40,27 @@ func deepSearch(m map[string]interface{}, path []string) map[string]interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flattenAndMergeMap recursively flattens the given map into a new map
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
// Code is based on the function with the same name in tha main package.
|
// Code is based on the function with the same name in the main package.
|
||||||
// TODO: move it to a common place
|
// TODO: move it to a common place.
|
||||||
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any {
|
||||||
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
// prefix is shadowed => nothing more to flatten
|
// prefix is shadowed => nothing more to flatten
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
if shadow == nil {
|
if shadow == nil {
|
||||||
shadow = make(map[string]interface{})
|
shadow = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
var m2 map[string]interface{}
|
var m2 map[string]any
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += delimiter
|
prefix += delimiter
|
||||||
}
|
}
|
||||||
for k, val := range m {
|
for k, val := range m {
|
||||||
fullKey := prefix + k
|
fullKey := prefix + k
|
||||||
switch val.(type) {
|
switch val := val.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
m2 = val.(map[string]interface{})
|
m2 = val
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
m2 = cast.ToStringMap(val)
|
m2 = cast.ToStringMap(val)
|
||||||
default:
|
default:
|
||||||
// immediate value
|
// immediate value
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
// TODO: expose prefix and indent in the Codec as setting?
|
// TODO: expose prefix and indent in the Codec as setting?
|
||||||
return json.MarshalIndent(v, "", " ")
|
return json.MarshalIndent(v, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (Codec) Decode(b []byte, v map[string]any) error {
|
||||||
return json.Unmarshal(b, &v)
|
return json.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `{
|
const encoded = `{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": [
|
"list": [
|
||||||
@@ -28,21 +30,21 @@ const encoded = `{
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
},
|
},
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
"nested_map": map[string]interface{}{
|
"nested_map": map[string]any{
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
@@ -55,40 +57,30 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(encoded), v)
|
err := codec.Decode([]byte(encoded), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(data, v) {
|
assert.Equal(t, data, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,39 +1,16 @@
|
|||||||
//go:build viper_toml1
|
|
||||||
// +build viper_toml1
|
|
||||||
|
|
||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
t, err := toml.TreeFromMap(v)
|
return toml.Marshal(v)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := t.ToTomlString()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(s), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (Codec) Decode(b []byte, v map[string]any) error {
|
||||||
tree, err := toml.LoadBytes(b)
|
return toml.Unmarshal(b, &v)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmap := tree.ToMap()
|
|
||||||
for key, value := range tmap {
|
|
||||||
v[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
//go:build !viper_toml1
|
|
||||||
// +build !viper_toml1
|
|
||||||
|
|
||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
|
||||||
type Codec struct{}
|
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|
||||||
return toml.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
|
||||||
return toml.Unmarshal(b, &v)
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
//go:build !viper_toml1
|
|
||||||
// +build !viper_toml1
|
|
||||||
|
|
||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// original form of the data
|
|
||||||
const original = `# key-value pair
|
|
||||||
key = "value"
|
|
||||||
list = ["item1", "item2", "item3"]
|
|
||||||
|
|
||||||
[map]
|
|
||||||
key = "value"
|
|
||||||
|
|
||||||
# nested
|
|
||||||
# map
|
|
||||||
[nested_map]
|
|
||||||
[nested_map.map]
|
|
||||||
key = "value"
|
|
||||||
list = [
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
]
|
|
||||||
`
|
|
||||||
|
|
||||||
// encoded form of the data
|
|
||||||
const encoded = `key = 'value'
|
|
||||||
list = ['item1', 'item2', 'item3']
|
|
||||||
|
|
||||||
[map]
|
|
||||||
key = 'value'
|
|
||||||
|
|
||||||
[nested_map]
|
|
||||||
[nested_map.map]
|
|
||||||
key = 'value'
|
|
||||||
list = ['item1', 'item2', 'item3']
|
|
||||||
`
|
|
||||||
|
|
||||||
// Viper's internal representation
|
|
||||||
var data = map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"nested_map": map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodec_Encode(t *testing.T) {
|
|
||||||
codec := Codec{}
|
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
|
||||||
codec := Codec{}
|
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(data, v) {
|
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
|
||||||
codec := Codec{}
|
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
//go:build viper_toml1
|
|
||||||
// +build viper_toml1
|
|
||||||
|
|
||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data.
|
||||||
const original = `# key-value pair
|
const original = `# key-value pair
|
||||||
key = "value"
|
key = "value"
|
||||||
list = ["item1", "item2", "item3"]
|
list = ["item1", "item2", "item3"]
|
||||||
@@ -28,35 +27,34 @@ list = [
|
|||||||
]
|
]
|
||||||
`
|
`
|
||||||
|
|
||||||
// encoded form of the data
|
// encoded form of the data.
|
||||||
const encoded = `key = "value"
|
const encoded = `key = 'value'
|
||||||
list = ["item1", "item2", "item3"]
|
list = ['item1', 'item2', 'item3']
|
||||||
|
|
||||||
[map]
|
[map]
|
||||||
key = "value"
|
key = 'value'
|
||||||
|
|
||||||
[nested_map]
|
[nested_map]
|
||||||
|
[nested_map.map]
|
||||||
[nested_map.map]
|
key = 'value'
|
||||||
key = "value"
|
list = ['item1', 'item2', 'item3']
|
||||||
list = ["item1", "item2", "item3"]
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// Viper's internal representation
|
// data is Viper's internal representation.
|
||||||
var data = map[string]interface{}{
|
var data = map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
},
|
},
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
},
|
},
|
||||||
"nested_map": map[string]interface{}{
|
"nested_map": map[string]any{
|
||||||
"map": map[string]interface{}{
|
"map": map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"list": []interface{}{
|
"list": []any{
|
||||||
"item1",
|
"item1",
|
||||||
"item2",
|
"item2",
|
||||||
"item3",
|
"item3",
|
||||||
@@ -69,40 +67,30 @@ func TestCodec_Encode(t *testing.T) {
|
|||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(data, v) {
|
assert.Equal(t, data, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package yaml
|
package yaml
|
||||||
|
|
||||||
// import "gopkg.in/yaml.v2"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
func (Codec) Encode(v map[string]any) ([]byte, error) {
|
||||||
return yaml.Marshal(v)
|
return yaml.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (Codec) Decode(b []byte, v map[string]any) error {
|
||||||
return yaml.Unmarshal(b, &v)
|
return yaml.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,127 @@
|
|||||||
package yaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// original form of the data.
|
||||||
|
const original = `# key-value pair
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
|
||||||
|
# nested
|
||||||
|
# map
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data.
|
||||||
|
const encoded = `key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// decoded form of the data.
|
||||||
|
//
|
||||||
|
// In case of YAML it's slightly different from Viper's internal representation
|
||||||
|
// (e.g. map is decoded into a map with interface key).
|
||||||
|
var decoded = map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
"list": []any{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]any{
|
||||||
|
"map": map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
"list": []any{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// data is Viper's internal representation.
|
||||||
|
var data = map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
"list": []any{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]any{
|
||||||
|
"map": map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
"list": []any{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestCodec_Encode(t *testing.T) {
|
func TestCodec_Encode(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
b, err := codec.Encode(data)
|
b, err := codec.Encode(data)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encoded != string(b) {
|
assert.Equal(t, encoded, string(b))
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodec_Decode(t *testing.T) {
|
func TestCodec_Decode(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(original), v)
|
err := codec.Decode([]byte(original), v)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(decoded, v) {
|
assert.Equal(t, decoded, v)
|
||||||
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidData", func(t *testing.T) {
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
codec := Codec{}
|
codec := Codec{}
|
||||||
|
|
||||||
v := map[string]interface{}{}
|
v := map[string]any{}
|
||||||
|
|
||||||
err := codec.Decode([]byte(`invalid data`), v)
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatal("expected decoding to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("decoding failed as expected: %s", err)
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build viper_yaml2
|
|
||||||
// +build viper_yaml2
|
|
||||||
|
|
||||||
package yaml
|
|
||||||
|
|
||||||
import yamlv2 "gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
var yaml = struct {
|
|
||||||
Marshal func(in interface{}) (out []byte, err error)
|
|
||||||
Unmarshal func(in []byte, out interface{}) (err error)
|
|
||||||
}{
|
|
||||||
Marshal: yamlv2.Marshal,
|
|
||||||
Unmarshal: yamlv2.Unmarshal,
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
//go:build viper_yaml2
|
|
||||||
// +build viper_yaml2
|
|
||||||
|
|
||||||
package yaml
|
|
||||||
|
|
||||||
// original form of the data
|
|
||||||
const original = `# key-value pair
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
|
|
||||||
# nested
|
|
||||||
# map
|
|
||||||
nested_map:
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
`
|
|
||||||
|
|
||||||
// encoded form of the data
|
|
||||||
const encoded = `key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
nested_map:
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
`
|
|
||||||
|
|
||||||
// decoded form of the data
|
|
||||||
//
|
|
||||||
// in case of YAML it's slightly different from Viper's internal representation
|
|
||||||
// (eg. map is decoded into a map with interface key)
|
|
||||||
var decoded = map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
"map": map[interface{}]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"nested_map": map[interface{}]interface{}{
|
|
||||||
"map": map[interface{}]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Viper's internal representation
|
|
||||||
var data = map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"nested_map": map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build !viper_yaml2
|
|
||||||
// +build !viper_yaml2
|
|
||||||
|
|
||||||
package yaml
|
|
||||||
|
|
||||||
import yamlv3 "gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
var yaml = struct {
|
|
||||||
Marshal func(in interface{}) (out []byte, err error)
|
|
||||||
Unmarshal func(in []byte, out interface{}) (err error)
|
|
||||||
}{
|
|
||||||
Marshal: yamlv3.Marshal,
|
|
||||||
Unmarshal: yamlv3.Unmarshal,
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
//go:build !viper_yaml2
|
|
||||||
// +build !viper_yaml2
|
|
||||||
|
|
||||||
package yaml
|
|
||||||
|
|
||||||
// original form of the data
|
|
||||||
const original = `# key-value pair
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
|
|
||||||
# nested
|
|
||||||
# map
|
|
||||||
nested_map:
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
`
|
|
||||||
|
|
||||||
// encoded form of the data
|
|
||||||
const encoded = `key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
nested_map:
|
|
||||||
map:
|
|
||||||
key: value
|
|
||||||
list:
|
|
||||||
- item1
|
|
||||||
- item2
|
|
||||||
- item3
|
|
||||||
`
|
|
||||||
|
|
||||||
// decoded form of the data
|
|
||||||
//
|
|
||||||
// in case of YAML it's slightly different from Viper's internal representation
|
|
||||||
// (eg. map is decoded into a map with interface key)
|
|
||||||
var decoded = map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"nested_map": map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Viper's internal representation
|
|
||||||
var data = map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"nested_map": map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"key": "value",
|
|
||||||
"list": []interface{}{
|
|
||||||
"item1",
|
|
||||||
"item2",
|
|
||||||
"item3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build viper_bind_struct
|
||||||
|
|
||||||
|
package features
|
||||||
|
|
||||||
|
const BindStruct = true
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !viper_bind_struct
|
||||||
|
|
||||||
|
package features
|
||||||
|
|
||||||
|
const BindStruct = false
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
//go:build !go1.17
|
|
||||||
// +build !go1.17
|
|
||||||
|
|
||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Based on https://github.com/frankban/quicktest/blob/577841610793d24f99e31cc2c0ef3a541fefd7c7/patch.go#L34-L64
|
|
||||||
// Licensed under the MIT license
|
|
||||||
// Copyright (c) 2017 Canonical Ltd.
|
|
||||||
|
|
||||||
// Setenv sets an environment variable to a temporary value for the
|
|
||||||
// duration of the test.
|
|
||||||
//
|
|
||||||
// At the end of the test (see "Deferred execution" in the package docs), the
|
|
||||||
// environment variable is returned to its original value.
|
|
||||||
func Setenv(t *testing.T, name, val string) {
|
|
||||||
setenv(t, name, val, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setenv sets or unsets an environment variable to a temporary value for the
|
|
||||||
// duration of the test
|
|
||||||
func setenv(t *testing.T, name, val string, valOK bool) {
|
|
||||||
oldVal, oldOK := os.LookupEnv(name)
|
|
||||||
if valOK {
|
|
||||||
os.Setenv(name, val)
|
|
||||||
} else {
|
|
||||||
os.Unsetenv(name)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if oldOK {
|
|
||||||
os.Setenv(name, oldVal)
|
|
||||||
} else {
|
|
||||||
os.Unsetenv(name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build go1.17
|
|
||||||
// +build go1.17
|
|
||||||
|
|
||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Setenv sets an environment variable to a temporary value for the
|
|
||||||
// duration of the test.
|
|
||||||
//
|
|
||||||
// This shim can be removed once support for Go <1.17 is dropped.
|
|
||||||
func Setenv(t *testing.T, name, val string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
t.Setenv(name, val)
|
|
||||||
}
|
|
||||||
@@ -1,77 +1,68 @@
|
|||||||
package viper
|
package viper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
slog "github.com/sagikazarmark/slog-shim"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger is a unified interface for various logging use cases and practices, including:
|
// Logger is a unified interface for various logging use cases and practices, including:
|
||||||
// - leveled logging
|
// - leveled logging
|
||||||
// - structured logging
|
// - structured logging
|
||||||
|
//
|
||||||
|
// Deprecated: use `log/slog` instead.
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// Trace logs a Trace event.
|
// Trace logs a Trace event.
|
||||||
//
|
//
|
||||||
// Even more fine-grained information than Debug events.
|
// Even more fine-grained information than Debug events.
|
||||||
// Loggers not supporting this level should fall back to Debug.
|
// Loggers not supporting this level should fall back to Debug.
|
||||||
Trace(msg string, keyvals ...interface{})
|
Trace(msg string, keyvals ...any)
|
||||||
|
|
||||||
// Debug logs a Debug event.
|
// Debug logs a Debug event.
|
||||||
//
|
//
|
||||||
// A verbose series of information events.
|
// A verbose series of information events.
|
||||||
// They are useful when debugging the system.
|
// They are useful when debugging the system.
|
||||||
Debug(msg string, keyvals ...interface{})
|
Debug(msg string, keyvals ...any)
|
||||||
|
|
||||||
// Info logs an Info event.
|
// Info logs an Info event.
|
||||||
//
|
//
|
||||||
// General information about what's happening inside the system.
|
// General information about what's happening inside the system.
|
||||||
Info(msg string, keyvals ...interface{})
|
Info(msg string, keyvals ...any)
|
||||||
|
|
||||||
// Warn logs a Warn(ing) event.
|
// Warn logs a Warn(ing) event.
|
||||||
//
|
//
|
||||||
// Non-critical events that should be looked at.
|
// Non-critical events that should be looked at.
|
||||||
Warn(msg string, keyvals ...interface{})
|
Warn(msg string, keyvals ...any)
|
||||||
|
|
||||||
// Error logs an Error event.
|
// Error logs an Error event.
|
||||||
//
|
//
|
||||||
// Critical events that require immediate attention.
|
// Critical events that require immediate attention.
|
||||||
// Loggers commonly provide Fatal and Panic levels above Error level,
|
// Loggers commonly provide Fatal and Panic levels above Error level,
|
||||||
// but exiting and panicing is out of scope for a logging library.
|
// but exiting and panicking is out of scope for a logging library.
|
||||||
Error(msg string, keyvals ...interface{})
|
Error(msg string, keyvals ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jwwLogger struct{}
|
// WithLogger sets a custom logger.
|
||||||
|
func WithLogger(l *slog.Logger) Option {
|
||||||
func (jwwLogger) Trace(msg string, keyvals ...interface{}) {
|
return optionFunc(func(v *Viper) {
|
||||||
jww.TRACE.Printf(jwwLogMessage(msg, keyvals...))
|
v.logger = l
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jwwLogger) Debug(msg string, keyvals ...interface{}) {
|
type discardHandler struct{}
|
||||||
jww.DEBUG.Printf(jwwLogMessage(msg, keyvals...))
|
|
||||||
|
func (n *discardHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jwwLogger) Info(msg string, keyvals ...interface{}) {
|
func (n *discardHandler) Handle(_ context.Context, _ slog.Record) error {
|
||||||
jww.INFO.Printf(jwwLogMessage(msg, keyvals...))
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jwwLogger) Warn(msg string, keyvals ...interface{}) {
|
func (n *discardHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||||
jww.WARN.Printf(jwwLogMessage(msg, keyvals...))
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jwwLogger) Error(msg string, keyvals ...interface{}) {
|
func (n *discardHandler) WithGroup(_ string) slog.Handler {
|
||||||
jww.ERROR.Printf(jwwLogMessage(msg, keyvals...))
|
return n
|
||||||
}
|
|
||||||
|
|
||||||
func jwwLogMessage(msg string, keyvals ...interface{}) string {
|
|
||||||
out := msg
|
|
||||||
|
|
||||||
if len(keyvals) > 0 && len(keyvals)%2 == 1 {
|
|
||||||
keyvals = append(keyvals, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i <= len(keyvals)-2; i += 2 {
|
|
||||||
out = fmt.Sprintf("%s %v=%v", out, keyvals[i], keyvals[i+1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-26
@@ -1,7 +1,6 @@
|
|||||||
package viper
|
package viper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -46,11 +45,11 @@ func TestNestedOverrides(t *testing.T) {
|
|||||||
deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4)
|
deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4)
|
||||||
|
|
||||||
// Case 4: key:value overridden by a map
|
// Case 4: key:value overridden by a map
|
||||||
v = overrideDefault(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10}
|
v = overrideDefault(assert, "tom.size", 4, "tom", map[string]any{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10}
|
||||||
assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable
|
assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable
|
||||||
assert.Equal(10, v.Get("tom.age")) // new value should be there
|
assert.Equal(10, v.Get("tom.age")) // new value should be there
|
||||||
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there
|
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there
|
||||||
v = override(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10})
|
v = override(assert, "tom.size", 4, "tom", map[string]any{"age": 10})
|
||||||
assert.Nil(v.Get("tom.size"))
|
assert.Nil(v.Get("tom.size"))
|
||||||
assert.Equal(10, v.Get("tom.age"))
|
assert.Equal(10, v.Get("tom.age"))
|
||||||
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10)
|
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10)
|
||||||
@@ -75,11 +74,11 @@ func TestNestedOverrides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
|
||||||
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
func override(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
|
||||||
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +93,11 @@ func override(assert *assert.Assertions, firstPath string, firstValue interface{
|
|||||||
//
|
//
|
||||||
// After each assignment, the value is checked, retrieved both by its full path
|
// After each assignment, the value is checked, retrieved both by its full path
|
||||||
// and by its key sequence (successive maps).
|
// and by its key sequence (successive maps).
|
||||||
func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
|
||||||
v := New()
|
v := New()
|
||||||
firstKeys := strings.Split(firstPath, v.keyDelim)
|
firstKeys := strings.Split(firstPath, v.keyDelim)
|
||||||
if assert == nil ||
|
if assert == nil ||
|
||||||
len(firstKeys) == 0 || len(firstKeys[0]) == 0 {
|
len(firstKeys) == 0 || firstKeys[0] == "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +115,7 @@ func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, fir
|
|||||||
|
|
||||||
// Override and check new value
|
// Override and check new value
|
||||||
secondKeys := strings.Split(secondPath, v.keyDelim)
|
secondKeys := strings.Split(secondPath, v.keyDelim)
|
||||||
if len(secondKeys) == 0 || len(secondKeys[0]) == 0 {
|
if len(secondKeys) == 0 || secondKeys[0] == "" {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
v.Set(secondPath, secondValue)
|
v.Set(secondPath, secondValue)
|
||||||
@@ -127,15 +126,15 @@ func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, fir
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deepCheckValue checks that all given keys correspond to a valid path in the
|
// deepCheckValue checks that all given keys correspond to a valid path in the
|
||||||
// configuration map of the given layer, and that the final value equals the one given
|
// configuration map of the given layer, and that the final value equals the one given.
|
||||||
func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value interface{}) {
|
func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value any) {
|
||||||
if assert == nil || v == nil ||
|
if assert == nil || v == nil ||
|
||||||
len(keys) == 0 || len(keys[0]) == 0 {
|
len(keys) == 0 || keys[0] == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
var val interface{}
|
var val any
|
||||||
var ms string
|
var ms string
|
||||||
switch l {
|
switch l {
|
||||||
case defaultLayer:
|
case defaultLayer:
|
||||||
@@ -147,28 +146,25 @@ func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loop through map
|
// loop through map
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
err := false
|
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms))
|
assert.Failf("%s is not a map[string]any", ms)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// deep scan of the map to get the final value
|
// deep scan of the map to get the final value
|
||||||
switch val.(type) {
|
switch val := val.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
m = cast.ToStringMap(val)
|
m = cast.ToStringMap(val)
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
m = val.(map[string]interface{})
|
m = val
|
||||||
default:
|
default:
|
||||||
assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms))
|
assert.Failf("%s is not a map[string]any", ms)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ms = ms + "[\"" + k + "\"]"
|
ms = ms + "[\"" + k + "\"]"
|
||||||
val = m[k]
|
val = m[k]
|
||||||
}
|
}
|
||||||
if !err {
|
assert.Equal(value, val)
|
||||||
assert.Equal(value, val)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-8
@@ -10,6 +10,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
crypt "github.com/sagikazarmark/crypt/config"
|
crypt "github.com/sagikazarmark/crypt/config"
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
|||||||
var cm crypt.ConfigManager
|
var cm crypt.ConfigManager
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
endpoints := strings.Split(rp.Endpoint(), ";")
|
||||||
if rp.SecretKeyring() != "" {
|
if rp.SecretKeyring() != "" {
|
||||||
var kr *os.File
|
var kr *os.File
|
||||||
kr, err = os.Open(rp.SecretKeyring())
|
kr, err = os.Open(rp.SecretKeyring())
|
||||||
@@ -84,24 +86,28 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
|||||||
defer kr.Close()
|
defer kr.Close()
|
||||||
switch rp.Provider() {
|
switch rp.Provider() {
|
||||||
case "etcd":
|
case "etcd":
|
||||||
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
cm, err = crypt.NewEtcdConfigManager(endpoints, kr)
|
||||||
case "etcd3":
|
case "etcd3":
|
||||||
cm, err = crypt.NewEtcdV3ConfigManager([]string{rp.Endpoint()}, kr)
|
cm, err = crypt.NewEtcdV3ConfigManager(endpoints, kr)
|
||||||
case "firestore":
|
case "firestore":
|
||||||
cm, err = crypt.NewFirestoreConfigManager([]string{rp.Endpoint()}, kr)
|
cm, err = crypt.NewFirestoreConfigManager(endpoints, kr)
|
||||||
|
case "nats":
|
||||||
|
cm, err = crypt.NewNatsConfigManager(endpoints, kr)
|
||||||
default:
|
default:
|
||||||
cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
|
cm, err = crypt.NewConsulConfigManager(endpoints, kr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch rp.Provider() {
|
switch rp.Provider() {
|
||||||
case "etcd":
|
case "etcd":
|
||||||
cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
|
cm, err = crypt.NewStandardEtcdConfigManager(endpoints)
|
||||||
case "etcd3":
|
case "etcd3":
|
||||||
cm, err = crypt.NewStandardEtcdV3ConfigManager([]string{rp.Endpoint()})
|
cm, err = crypt.NewStandardEtcdV3ConfigManager(endpoints)
|
||||||
case "firestore":
|
case "firestore":
|
||||||
cm, err = crypt.NewStandardFirestoreConfigManager([]string{rp.Endpoint()})
|
cm, err = crypt.NewStandardFirestoreConfigManager(endpoints)
|
||||||
|
case "nats":
|
||||||
|
cm, err = crypt.NewStandardNatsConfigManager(endpoints)
|
||||||
default:
|
default:
|
||||||
cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
|
cm, err = crypt.NewStandardConsulConfigManager(endpoints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
slog "github.com/sagikazarmark/slog-shim"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,13 +32,18 @@ func (pe ConfigParseError) Error() string {
|
|||||||
return fmt.Sprintf("While parsing config: %s", pe.err.Error())
|
return fmt.Sprintf("While parsing config: %s", pe.err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the wrapped error.
|
||||||
|
func (pe ConfigParseError) Unwrap() error {
|
||||||
|
return pe.err
|
||||||
|
}
|
||||||
|
|
||||||
// toCaseInsensitiveValue checks if the value is a map;
|
// toCaseInsensitiveValue checks if the value is a map;
|
||||||
// if so, create a copy and lower-case the keys recursively.
|
// if so, create a copy and lower-case the keys recursively.
|
||||||
func toCaseInsensitiveValue(value interface{}) interface{} {
|
func toCaseInsensitiveValue(value any) any {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
value = copyAndInsensitiviseMap(cast.ToStringMap(v))
|
value = copyAndInsensitiviseMap(cast.ToStringMap(v))
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
value = copyAndInsensitiviseMap(v)
|
value = copyAndInsensitiviseMap(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +52,15 @@ func toCaseInsensitiveValue(value interface{}) interface{} {
|
|||||||
|
|
||||||
// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of
|
// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of
|
||||||
// any map it makes case insensitive.
|
// any map it makes case insensitive.
|
||||||
func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
|
func copyAndInsensitiviseMap(m map[string]any) map[string]any {
|
||||||
nm := make(map[string]interface{})
|
nm := make(map[string]any)
|
||||||
|
|
||||||
for key, val := range m {
|
for key, val := range m {
|
||||||
lkey := strings.ToLower(key)
|
lkey := strings.ToLower(key)
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
|
nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
nm[lkey] = copyAndInsensitiviseMap(v)
|
nm[lkey] = copyAndInsensitiviseMap(v)
|
||||||
default:
|
default:
|
||||||
nm[lkey] = v
|
nm[lkey] = v
|
||||||
@@ -64,23 +70,23 @@ func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
|
|||||||
return nm
|
return nm
|
||||||
}
|
}
|
||||||
|
|
||||||
func insensitiviseVal(val interface{}) interface{} {
|
func insensitiviseVal(val any) any {
|
||||||
switch val.(type) {
|
switch v := val.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
// nested map: cast and recursively insensitivise
|
// nested map: cast and recursively insensitivise
|
||||||
val = cast.ToStringMap(val)
|
val = cast.ToStringMap(val)
|
||||||
insensitiviseMap(val.(map[string]interface{}))
|
insensitiviseMap(val.(map[string]any))
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
// nested map: recursively insensitivise
|
// nested map: recursively insensitivise
|
||||||
insensitiviseMap(val.(map[string]interface{}))
|
insensitiviseMap(v)
|
||||||
case []interface{}:
|
case []any:
|
||||||
// nested array: recursively insensitivise
|
// nested array: recursively insensitivise
|
||||||
insensitiveArray(val.([]interface{}))
|
insensitiveArray(v)
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func insensitiviseMap(m map[string]interface{}) {
|
func insensitiviseMap(m map[string]any) {
|
||||||
for key, val := range m {
|
for key, val := range m {
|
||||||
val = insensitiviseVal(val)
|
val = insensitiviseVal(val)
|
||||||
lower := strings.ToLower(key)
|
lower := strings.ToLower(key)
|
||||||
@@ -93,13 +99,13 @@ func insensitiviseMap(m map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func insensitiveArray(a []interface{}) {
|
func insensitiveArray(a []any) {
|
||||||
for i, val := range a {
|
for i, val := range a {
|
||||||
a[i] = insensitiviseVal(val)
|
a[i] = insensitiviseVal(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func absPathify(logger Logger, inPath string) string {
|
func absPathify(logger *slog.Logger, inPath string) string {
|
||||||
logger.Info("trying to resolve absolute path", "path", inPath)
|
logger.Info("trying to resolve absolute path", "path", inPath)
|
||||||
|
|
||||||
if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
|
if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
|
||||||
@@ -150,7 +156,7 @@ func safeMul(a, b uint) uint {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
|
// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes.
|
||||||
func parseSizeInBytes(sizeStr string) uint {
|
func parseSizeInBytes(sizeStr string) uint {
|
||||||
sizeStr = strings.TrimSpace(sizeStr)
|
sizeStr = strings.TrimSpace(sizeStr)
|
||||||
lastChar := len(sizeStr) - 1
|
lastChar := len(sizeStr) - 1
|
||||||
@@ -192,22 +198,22 @@ func parseSizeInBytes(sizeStr string) uint {
|
|||||||
// In case intermediate keys do not exist, or map to a non-map value,
|
// In case intermediate keys do not exist, or map to a non-map value,
|
||||||
// a new map is created and inserted, and the search continues from there:
|
// a new map is created and inserted, and the search continues from there:
|
||||||
// the initial map "m" may be modified!
|
// the initial map "m" may be modified!
|
||||||
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
func deepSearch(m map[string]any, path []string) map[string]any {
|
||||||
for _, k := range path {
|
for _, k := range path {
|
||||||
m2, ok := m[k]
|
m2, ok := m[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key does not exist
|
// intermediate key does not exist
|
||||||
// => create it and continue from there
|
// => create it and continue from there
|
||||||
m3 := make(map[string]interface{})
|
m3 := make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
m = m3
|
m = m3
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m3, ok := m2.(map[string]interface{})
|
m3, ok := m2.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// intermediate key is a value
|
// intermediate key is a value
|
||||||
// => replace with a new map
|
// => replace with a new map
|
||||||
m3 = make(map[string]interface{})
|
m3 = make(map[string]any)
|
||||||
m[k] = m3
|
m[k] = m3
|
||||||
}
|
}
|
||||||
// continue search from here
|
// continue search from here
|
||||||
|
|||||||
+18
-27
@@ -13,24 +13,24 @@ package viper
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/viper/internal/testutil"
|
slog "github.com/sagikazarmark/slog-shim"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
given = map[string]interface{}{
|
given = map[string]any{
|
||||||
"Foo": 32,
|
"Foo": 32,
|
||||||
"Bar": map[interface{}]interface{}{
|
"Bar": map[any]any{
|
||||||
"ABc": "A",
|
"ABc": "A",
|
||||||
"cDE": "B",
|
"cDE": "B",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expected = map[string]interface{}{
|
expected = map[string]any{
|
||||||
"foo": 32,
|
"foo": 32,
|
||||||
"bar": map[string]interface{}{
|
"bar": map[string]any{
|
||||||
"abc": "A",
|
"abc": "A",
|
||||||
"cde": "B",
|
"cde": "B",
|
||||||
},
|
},
|
||||||
@@ -39,22 +39,15 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
|
|||||||
|
|
||||||
got := copyAndInsensitiviseMap(given)
|
got := copyAndInsensitiviseMap(given)
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, expected) {
|
assert.Equal(t, expected, got)
|
||||||
t.Fatalf("Got %q\nexpected\n%q", got, expected)
|
_, ok := given["foo"]
|
||||||
}
|
assert.False(t, ok)
|
||||||
|
_, ok = given["bar"]
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
if _, ok := given["foo"]; ok {
|
m := given["Bar"].(map[any]any)
|
||||||
t.Fatal("Input map changed")
|
_, ok = m["ABc"]
|
||||||
}
|
assert.True(t, ok)
|
||||||
|
|
||||||
if _, ok := given["bar"]; ok {
|
|
||||||
t.Fatal("Input map changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
m := given["Bar"].(map[interface{}]interface{})
|
|
||||||
if _, ok := m["ABc"]; !ok {
|
|
||||||
t.Fatal("Input map changed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbsPathify(t *testing.T) {
|
func TestAbsPathify(t *testing.T) {
|
||||||
@@ -64,8 +57,8 @@ func TestAbsPathify(t *testing.T) {
|
|||||||
homer := filepath.Join(home, "homer")
|
homer := filepath.Join(home, "homer")
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
testutil.Setenv(t, "HOMER_ABSOLUTE_PATH", homer)
|
t.Setenv("HOMER_ABSOLUTE_PATH", homer)
|
||||||
testutil.Setenv(t, "VAR_WITH_RELATIVE_PATH", "relative")
|
t.Setenv("VAR_WITH_RELATIVE_PATH", "relative")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
@@ -87,9 +80,7 @@ func TestAbsPathify(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
got := absPathify(jwwLogger{}, test.input)
|
got := absPathify(slog.Default(), test.input)
|
||||||
if got != test.output {
|
assert.Equal(t, test.output, got)
|
||||||
t.Errorf("Got %v\nexpected\n%q", got, test.output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -35,7 +34,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
slog "github.com/sagikazarmark/slog-shim"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -48,6 +48,7 @@ import (
|
|||||||
"github.com/spf13/viper/internal/encoding/json"
|
"github.com/spf13/viper/internal/encoding/json"
|
||||||
"github.com/spf13/viper/internal/encoding/toml"
|
"github.com/spf13/viper/internal/encoding/toml"
|
||||||
"github.com/spf13/viper/internal/encoding/yaml"
|
"github.com/spf13/viper/internal/encoding/yaml"
|
||||||
|
"github.com/spf13/viper/internal/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigMarshalError happens when failing to marshal the configuration.
|
// ConfigMarshalError happens when failing to marshal the configuration.
|
||||||
@@ -77,7 +78,7 @@ type remoteConfigFactory interface {
|
|||||||
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
|
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteConfig is optional, see the remote package
|
// RemoteConfig is optional, see the remote package.
|
||||||
var RemoteConfig remoteConfigFactory
|
var RemoteConfig remoteConfigFactory
|
||||||
|
|
||||||
// UnsupportedConfigError denotes encountering an unsupported
|
// UnsupportedConfigError denotes encountering an unsupported
|
||||||
@@ -102,7 +103,7 @@ func (str UnsupportedRemoteProviderError) Error() string {
|
|||||||
// pull the configuration from the remote provider.
|
// pull the configuration from the remote provider.
|
||||||
type RemoteConfigError string
|
type RemoteConfigError string
|
||||||
|
|
||||||
// Error returns the formatted remote provider error
|
// Error returns the formatted remote provider error.
|
||||||
func (rce RemoteConfigError) Error() string {
|
func (rce RemoteConfigError) Error() string {
|
||||||
return fmt.Sprintf("Remote Configurations Error: %s", string(rce))
|
return fmt.Sprintf("Remote Configurations Error: %s", string(rce))
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,7 @@ func (faee ConfigFileAlreadyExistsError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
|
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
|
||||||
// mapstructure.DecoderConfig options
|
// mapstructure.DecoderConfig options.
|
||||||
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
||||||
|
|
||||||
// DecodeHook returns a DecoderConfigOption which overrides the default
|
// DecodeHook returns a DecoderConfigOption which overrides the default
|
||||||
@@ -206,10 +207,11 @@ type Viper struct {
|
|||||||
envKeyReplacer StringReplacer
|
envKeyReplacer StringReplacer
|
||||||
allowEmptyEnv bool
|
allowEmptyEnv bool
|
||||||
|
|
||||||
config map[string]interface{}
|
parents []string
|
||||||
override map[string]interface{}
|
config map[string]any
|
||||||
defaults map[string]interface{}
|
override map[string]any
|
||||||
kvstore map[string]interface{}
|
defaults map[string]any
|
||||||
|
kvstore map[string]any
|
||||||
pflags map[string]FlagValue
|
pflags map[string]FlagValue
|
||||||
env map[string][]string
|
env map[string][]string
|
||||||
aliases map[string]string
|
aliases map[string]string
|
||||||
@@ -217,7 +219,7 @@ type Viper struct {
|
|||||||
|
|
||||||
onConfigChange func(fsnotify.Event)
|
onConfigChange func(fsnotify.Event)
|
||||||
|
|
||||||
logger Logger
|
logger *slog.Logger
|
||||||
|
|
||||||
// TODO: should probably be protected with a mutex
|
// TODO: should probably be protected with a mutex
|
||||||
encoderRegistry *encoding.EncoderRegistry
|
encoderRegistry *encoding.EncoderRegistry
|
||||||
@@ -231,15 +233,16 @@ func New() *Viper {
|
|||||||
v.configName = "config"
|
v.configName = "config"
|
||||||
v.configPermissions = os.FileMode(0o644)
|
v.configPermissions = os.FileMode(0o644)
|
||||||
v.fs = afero.NewOsFs()
|
v.fs = afero.NewOsFs()
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]any)
|
||||||
v.override = make(map[string]interface{})
|
v.parents = []string{}
|
||||||
v.defaults = make(map[string]interface{})
|
v.override = make(map[string]any)
|
||||||
v.kvstore = make(map[string]interface{})
|
v.defaults = make(map[string]any)
|
||||||
|
v.kvstore = make(map[string]any)
|
||||||
v.pflags = make(map[string]FlagValue)
|
v.pflags = make(map[string]FlagValue)
|
||||||
v.env = make(map[string][]string)
|
v.env = make(map[string][]string)
|
||||||
v.aliases = make(map[string]string)
|
v.aliases = make(map[string]string)
|
||||||
v.typeByDefValue = false
|
v.typeByDefValue = false
|
||||||
v.logger = jwwLogger{}
|
v.logger = slog.New(&discardHandler{})
|
||||||
|
|
||||||
v.resetEncoding()
|
v.resetEncoding()
|
||||||
|
|
||||||
@@ -300,10 +303,10 @@ func NewWithOptions(opts ...Option) *Viper {
|
|||||||
func Reset() {
|
func Reset() {
|
||||||
v = New()
|
v = New()
|
||||||
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
|
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
|
||||||
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
|
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this lazy initialization instead
|
// TODO: make this lazy initialization instead.
|
||||||
func (v *Viper) resetEncoding() {
|
func (v *Viper) resetEncoding() {
|
||||||
encoderRegistry := encoding.NewEncoderRegistry()
|
encoderRegistry := encoding.NewEncoderRegistry()
|
||||||
decoderRegistry := encoding.NewDecoderRegistry()
|
decoderRegistry := encoding.NewDecoderRegistry()
|
||||||
@@ -419,28 +422,34 @@ type RemoteProvider interface {
|
|||||||
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
|
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
|
||||||
|
|
||||||
// SupportedRemoteProviders are universally supported remote providers.
|
// SupportedRemoteProviders are universally supported remote providers.
|
||||||
var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
|
var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"}
|
||||||
|
|
||||||
|
// OnConfigChange sets the event handler that is called when a config file changes.
|
||||||
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
|
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
|
||||||
|
|
||||||
|
// OnConfigChange sets the event handler that is called when a config file changes.
|
||||||
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
|
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
|
||||||
v.onConfigChange = run
|
v.onConfigChange = run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchConfig starts watching a config file for changes.
|
||||||
func WatchConfig() { v.WatchConfig() }
|
func WatchConfig() { v.WatchConfig() }
|
||||||
|
|
||||||
|
// WatchConfig starts watching a config file for changes.
|
||||||
func (v *Viper) WatchConfig() {
|
func (v *Viper) WatchConfig() {
|
||||||
initWG := sync.WaitGroup{}
|
initWG := sync.WaitGroup{}
|
||||||
initWG.Add(1)
|
initWG.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
watcher, err := newWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err))
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
|
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
|
||||||
filename, err := v.getConfigFile()
|
filename, err := v.getConfigFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error: %v\n", err)
|
v.logger.Error(fmt.Sprintf("get config file: %s", err))
|
||||||
initWG.Done()
|
initWG.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -469,7 +478,7 @@ func (v *Viper) WatchConfig() {
|
|||||||
realConfigFile = currentConfigFile
|
realConfigFile = currentConfigFile
|
||||||
err := v.ReadInConfig()
|
err := v.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error reading config file: %v\n", err)
|
v.logger.Error(fmt.Sprintf("read config file: %s", err))
|
||||||
}
|
}
|
||||||
if v.onConfigChange != nil {
|
if v.onConfigChange != nil {
|
||||||
v.onConfigChange(event)
|
v.onConfigChange(event)
|
||||||
@@ -481,7 +490,7 @@ func (v *Viper) WatchConfig() {
|
|||||||
|
|
||||||
case err, ok := <-watcher.Errors:
|
case err, ok := <-watcher.Errors:
|
||||||
if ok { // 'Errors' channel is not closed
|
if ok { // 'Errors' channel is not closed
|
||||||
log.Printf("watcher error: %v\n", err)
|
v.logger.Error(fmt.Sprintf("watcher error: %s", err))
|
||||||
}
|
}
|
||||||
eventsWG.Done()
|
eventsWG.Done()
|
||||||
return
|
return
|
||||||
@@ -516,6 +525,12 @@ func (v *Viper) SetEnvPrefix(in string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEnvPrefix() string { return v.GetEnvPrefix() }
|
||||||
|
|
||||||
|
func (v *Viper) GetEnvPrefix() string {
|
||||||
|
return v.envPrefix
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Viper) mergeWithEnvPrefix(in string) string {
|
func (v *Viper) mergeWithEnvPrefix(in string) string {
|
||||||
if v.envPrefix != "" {
|
if v.envPrefix != "" {
|
||||||
return strings.ToUpper(v.envPrefix + "_" + in)
|
return strings.ToUpper(v.envPrefix + "_" + in)
|
||||||
@@ -571,12 +586,12 @@ func (v *Viper) AddConfigPath(in string) {
|
|||||||
|
|
||||||
// AddRemoteProvider adds a remote configuration source.
|
// AddRemoteProvider adds a remote configuration source.
|
||||||
// Remote Providers are searched in the order they are added.
|
// Remote Providers are searched in the order they are added.
|
||||||
// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
|
// 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
|
// 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
|
// path is the path in the k/v store to retrieve configuration
|
||||||
// To retrieve a config file called myapp.json from /configs/myapp.json
|
// To retrieve a config file called myapp.json from /configs/myapp.json
|
||||||
// you should set path to /configs and set config name (SetConfigName()) to
|
// you should set path to /configs and set config name (SetConfigName()) to
|
||||||
// "myapp"
|
// "myapp".
|
||||||
func AddRemoteProvider(provider, endpoint, path string) error {
|
func AddRemoteProvider(provider, endpoint, path string) error {
|
||||||
return v.AddRemoteProvider(provider, endpoint, path)
|
return v.AddRemoteProvider(provider, endpoint, path)
|
||||||
}
|
}
|
||||||
@@ -602,14 +617,14 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
|
|||||||
|
|
||||||
// AddSecureRemoteProvider adds a remote configuration source.
|
// AddSecureRemoteProvider adds a remote configuration source.
|
||||||
// Secure Remote Providers are searched in the order they are added.
|
// Secure Remote Providers are searched in the order they are added.
|
||||||
// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
|
// 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
|
// 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
|
// 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
|
// path is the path in the k/v store to retrieve configuration
|
||||||
// To retrieve a config file called myapp.json from /configs/myapp.json
|
// To retrieve a config file called myapp.json from /configs/myapp.json
|
||||||
// you should set path to /configs and set config name (SetConfigName()) to
|
// you should set path to /configs and set config name (SetConfigName()) to
|
||||||
// "myapp"
|
// "myapp".
|
||||||
// Secure Remote Providers are implemented with github.com/bketelsen/crypt
|
// Secure Remote Providers are implemented with github.com/bketelsen/crypt.
|
||||||
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
|
||||||
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
|
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
|
||||||
}
|
}
|
||||||
@@ -646,7 +661,7 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
|
|||||||
// searchMap recursively searches for a value for path in source map.
|
// searchMap recursively searches for a value for path in source map.
|
||||||
// Returns nil if not found.
|
// Returns nil if not found.
|
||||||
// Note: This assumes that the path entries and map keys are lower cased.
|
// Note: This assumes that the path entries and map keys are lower cased.
|
||||||
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
|
func (v *Viper) searchMap(source map[string]any, path []string) any {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
@@ -659,13 +674,13 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Nested case
|
// Nested case
|
||||||
switch next.(type) {
|
switch next := next.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
return v.searchMap(cast.ToStringMap(next), path[1:])
|
return v.searchMap(cast.ToStringMap(next), path[1:])
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
// Type assertion is safe here since it is only reached
|
// Type assertion is safe here since it is only reached
|
||||||
// if the type of `next` is the same as the type being asserted
|
// if the type of `next` is the same as the type being asserted
|
||||||
return v.searchMap(next.(map[string]interface{}), path[1:])
|
return v.searchMap(next, path[1:])
|
||||||
default:
|
default:
|
||||||
// got a value but nested key expected, return "nil" for not found
|
// got a value but nested key expected, return "nil" for not found
|
||||||
return nil
|
return nil
|
||||||
@@ -685,7 +700,7 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac
|
|||||||
// in their keys).
|
// in their keys).
|
||||||
//
|
//
|
||||||
// Note: This assumes that the path entries and map keys are lower cased.
|
// Note: This assumes that the path entries and map keys are lower cased.
|
||||||
func (v *Viper) searchIndexableWithPathPrefixes(source interface{}, path []string) interface{} {
|
func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
@@ -694,11 +709,11 @@ func (v *Viper) searchIndexableWithPathPrefixes(source interface{}, path []strin
|
|||||||
for i := len(path); i > 0; i-- {
|
for i := len(path); i > 0; i-- {
|
||||||
prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))
|
prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))
|
||||||
|
|
||||||
var val interface{}
|
var val any
|
||||||
switch sourceIndexable := source.(type) {
|
switch sourceIndexable := source.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)
|
val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path)
|
val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path)
|
||||||
}
|
}
|
||||||
if val != nil {
|
if val != nil {
|
||||||
@@ -715,11 +730,11 @@ func (v *Viper) searchIndexableWithPathPrefixes(source interface{}, path []strin
|
|||||||
// This function is part of the searchIndexableWithPathPrefixes recurring search and
|
// This function is part of the searchIndexableWithPathPrefixes recurring search and
|
||||||
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
|
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
|
||||||
func (v *Viper) searchSliceWithPathPrefixes(
|
func (v *Viper) searchSliceWithPathPrefixes(
|
||||||
sourceSlice []interface{},
|
sourceSlice []any,
|
||||||
prefixKey string,
|
prefixKey string,
|
||||||
pathIndex int,
|
pathIndex int,
|
||||||
path []string,
|
path []string,
|
||||||
) interface{} {
|
) any {
|
||||||
// if the prefixKey is not a number or it is out of bounds of the slice
|
// if the prefixKey is not a number or it is out of bounds of the slice
|
||||||
index, err := strconv.Atoi(prefixKey)
|
index, err := strconv.Atoi(prefixKey)
|
||||||
if err != nil || len(sourceSlice) <= index {
|
if err != nil || len(sourceSlice) <= index {
|
||||||
@@ -734,9 +749,9 @@ func (v *Viper) searchSliceWithPathPrefixes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch n := next.(type) {
|
switch n := next.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
|
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
|
||||||
case map[string]interface{}, []interface{}:
|
case map[string]any, []any:
|
||||||
return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
|
return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
|
||||||
default:
|
default:
|
||||||
// got a value but nested key expected, do nothing and look for next prefix
|
// got a value but nested key expected, do nothing and look for next prefix
|
||||||
@@ -751,11 +766,11 @@ func (v *Viper) searchSliceWithPathPrefixes(
|
|||||||
// This function is part of the searchIndexableWithPathPrefixes recurring search and
|
// This function is part of the searchIndexableWithPathPrefixes recurring search and
|
||||||
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
|
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
|
||||||
func (v *Viper) searchMapWithPathPrefixes(
|
func (v *Viper) searchMapWithPathPrefixes(
|
||||||
sourceMap map[string]interface{},
|
sourceMap map[string]any,
|
||||||
prefixKey string,
|
prefixKey string,
|
||||||
pathIndex int,
|
pathIndex int,
|
||||||
path []string,
|
path []string,
|
||||||
) interface{} {
|
) any {
|
||||||
next, ok := sourceMap[prefixKey]
|
next, ok := sourceMap[prefixKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -768,9 +783,9 @@ func (v *Viper) searchMapWithPathPrefixes(
|
|||||||
|
|
||||||
// Nested case
|
// Nested case
|
||||||
switch n := next.(type) {
|
switch n := next.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
|
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
|
||||||
case map[string]interface{}, []interface{}:
|
case map[string]any, []any:
|
||||||
return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
|
return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
|
||||||
default:
|
default:
|
||||||
// got a value but nested key expected, do nothing and look for next prefix
|
// got a value but nested key expected, do nothing and look for next prefix
|
||||||
@@ -785,8 +800,8 @@ func (v *Viper) searchMapWithPathPrefixes(
|
|||||||
// e.g., if "foo.bar" has a value in the given map, it “shadows”
|
// e.g., if "foo.bar" has a value in the given map, it “shadows”
|
||||||
//
|
//
|
||||||
// "foo.bar.baz" in a lower-priority map
|
// "foo.bar.baz" in a lower-priority map
|
||||||
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string {
|
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string {
|
||||||
var parentVal interface{}
|
var parentVal any
|
||||||
for i := 1; i < len(path); i++ {
|
for i := 1; i < len(path); i++ {
|
||||||
parentVal = v.searchMap(m, path[0:i])
|
parentVal = v.searchMap(m, path[0:i])
|
||||||
if parentVal == nil {
|
if parentVal == nil {
|
||||||
@@ -794,9 +809,9 @@ func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{})
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
switch parentVal.(type) {
|
switch parentVal.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
continue
|
continue
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
// parentVal is a regular value which shadows "path"
|
// parentVal is a regular value which shadows "path"
|
||||||
@@ -811,12 +826,14 @@ func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{})
|
|||||||
// e.g., if "foo.bar" has a value in the given map, it “shadows”
|
// e.g., if "foo.bar" has a value in the given map, it “shadows”
|
||||||
//
|
//
|
||||||
// "foo.bar.baz" in a lower-priority map
|
// "foo.bar.baz" in a lower-priority map
|
||||||
func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
|
func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
|
||||||
// unify input map
|
// unify input map
|
||||||
var m map[string]interface{}
|
var m map[string]interface{}
|
||||||
switch mi.(type) {
|
switch miv := mi.(type) {
|
||||||
case map[string]string, map[string]FlagValue:
|
case map[string]string:
|
||||||
m = cast.ToStringMap(mi)
|
m = castMapStringToMapInterface(miv)
|
||||||
|
case map[string]FlagValue:
|
||||||
|
m = castMapFlagToMapInterface(miv)
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -880,9 +897,9 @@ func GetViper() *Viper {
|
|||||||
// override, flag, env, config file, key/value store, default
|
// override, flag, env, config file, key/value store, default
|
||||||
//
|
//
|
||||||
// Get returns an interface. For a specific value use one of the Get____ methods.
|
// Get returns an interface. For a specific value use one of the Get____ methods.
|
||||||
func Get(key string) interface{} { return v.Get(key) }
|
func Get(key string) any { return v.Get(key) }
|
||||||
|
|
||||||
func (v *Viper) Get(key string) interface{} {
|
func (v *Viper) Get(key string) any {
|
||||||
lcaseKey := strings.ToLower(key)
|
lcaseKey := strings.ToLower(key)
|
||||||
val := v.find(lcaseKey, true)
|
val := v.find(lcaseKey, true)
|
||||||
if val == nil {
|
if val == nil {
|
||||||
@@ -923,6 +940,8 @@ func (v *Viper) Get(key string) interface{} {
|
|||||||
return cast.ToStringSlice(val)
|
return cast.ToStringSlice(val)
|
||||||
case []int:
|
case []int:
|
||||||
return cast.ToIntSlice(val)
|
return cast.ToIntSlice(val)
|
||||||
|
case []time.Duration:
|
||||||
|
return cast.ToDurationSlice(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,6 +960,11 @@ func (v *Viper) Sub(key string) *Viper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reflect.TypeOf(data).Kind() == reflect.Map {
|
if reflect.TypeOf(data).Kind() == reflect.Map {
|
||||||
|
subv.parents = append([]string(nil), v.parents...)
|
||||||
|
subv.parents = append(subv.parents, strings.ToLower(key))
|
||||||
|
subv.automaticEnvApplied = v.automaticEnvApplied
|
||||||
|
subv.envPrefix = v.envPrefix
|
||||||
|
subv.envKeyReplacer = v.envKeyReplacer
|
||||||
subv.config = cast.ToStringMap(data)
|
subv.config = cast.ToStringMap(data)
|
||||||
return subv
|
return subv
|
||||||
}
|
}
|
||||||
@@ -1046,9 +1070,9 @@ func (v *Viper) GetStringSlice(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStringMap returns the value associated with the key as a map of interfaces.
|
// GetStringMap returns the value associated with the key as a map of interfaces.
|
||||||
func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) }
|
func GetStringMap(key string) map[string]any { return v.GetStringMap(key) }
|
||||||
|
|
||||||
func (v *Viper) GetStringMap(key string) map[string]interface{} {
|
func (v *Viper) GetStringMap(key string) map[string]any {
|
||||||
return cast.ToStringMap(v.Get(key))
|
return cast.ToStringMap(v.Get(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,34 +1100,66 @@ func (v *Viper) GetSizeInBytes(key string) uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
||||||
func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
|
||||||
return v.UnmarshalKey(key, rawVal, opts...)
|
return v.UnmarshalKey(key, rawVal, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
|
||||||
return decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
|
return decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||||||
// on the fields of the structure are properly set.
|
// on the fields of the structure are properly set.
|
||||||
func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
|
||||||
return v.Unmarshal(rawVal, opts...)
|
return v.Unmarshal(rawVal, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
|
||||||
return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
|
keys := v.AllKeys()
|
||||||
|
|
||||||
|
if features.BindStruct {
|
||||||
|
// TODO: make this optional?
|
||||||
|
structKeys, err := v.decodeStructKeys(rawVal, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, structKeys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: struct keys should be enough?
|
||||||
|
return decode(v.getSettings(keys), defaultDecoderConfig(rawVal, opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {
|
||||||
// of time.Duration values & string slices
|
var structKeyMap map[string]any
|
||||||
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
|
||||||
|
err := decode(input, defaultDecoderConfig(&structKeyMap, opts...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flattenedStructKeyMap := v.flattenAndMergeMap(map[string]bool{}, structKeyMap, "")
|
||||||
|
|
||||||
|
r := make([]string, 0, len(flattenedStructKeyMap))
|
||||||
|
for v := range flattenedStructKeyMap {
|
||||||
|
r = append(r, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
|
||||||
|
// of time.Duration values & string slices.
|
||||||
|
func defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||||
c := &mapstructure.DecoderConfig{
|
c := &mapstructure.DecoderConfig{
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
Result: output,
|
Result: output,
|
||||||
WeaklyTypedInput: true,
|
WeaklyTypedInput: true,
|
||||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||||
mapstructure.StringToTimeDurationHookFunc(),
|
mapstructure.StringToTimeDurationHookFunc(),
|
||||||
mapstructure.StringToSliceHookFunc(","),
|
// mapstructure.StringToSliceHookFunc(","),
|
||||||
|
stringToWeakSliceHookFunc(","),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@@ -1112,8 +1168,30 @@ func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *maps
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
// As of mapstructure v2.0.0 StringToSliceHookFunc checks if the return type is a string slice.
|
||||||
func decode(input interface{}, config *mapstructure.DecoderConfig) error {
|
// This function removes that check.
|
||||||
|
// TODO: implement a function that checks if the value can be converted to the return type and use it instead.
|
||||||
|
func stringToWeakSliceHookFunc(sep string) mapstructure.DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{},
|
||||||
|
) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := data.(string)
|
||||||
|
if raw == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(raw, sep), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.
|
||||||
|
func decode(input any, config *mapstructure.DecoderConfig) error {
|
||||||
decoder, err := mapstructure.NewDecoder(config)
|
decoder, err := mapstructure.NewDecoder(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1123,15 +1201,28 @@ func decode(input interface{}, config *mapstructure.DecoderConfig) error {
|
|||||||
|
|
||||||
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
|
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
|
||||||
// in the destination struct.
|
// in the destination struct.
|
||||||
func UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
|
func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
|
||||||
return v.UnmarshalExact(rawVal, opts...)
|
return v.UnmarshalExact(rawVal, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
|
func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
|
||||||
config := defaultDecoderConfig(rawVal, opts...)
|
config := defaultDecoderConfig(rawVal, opts...)
|
||||||
config.ErrorUnused = true
|
config.ErrorUnused = true
|
||||||
|
|
||||||
return decode(v.AllSettings(), config)
|
keys := v.AllKeys()
|
||||||
|
|
||||||
|
if features.BindStruct {
|
||||||
|
// TODO: make this optional?
|
||||||
|
structKeys, err := v.decodeStructKeys(rawVal, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, structKeys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: struct keys should be enough?
|
||||||
|
return decode(v.getSettings(keys), config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindPFlags binds a full flag set to the configuration, using each flag's long
|
// BindPFlags binds a full flag set to the configuration, using each flag's long
|
||||||
@@ -1224,9 +1315,9 @@ func (v *Viper) MustBindEnv(input ...string) {
|
|||||||
// corresponds to a flag, the flag's default value is returned.
|
// corresponds to a flag, the flag's default value is returned.
|
||||||
//
|
//
|
||||||
// Note: this assumes a lower-cased key given.
|
// Note: this assumes a lower-cased key given.
|
||||||
func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
|
func (v *Viper) find(lcaseKey string, flagDefault bool) any {
|
||||||
var (
|
var (
|
||||||
val interface{}
|
val any
|
||||||
exists bool
|
exists bool
|
||||||
path = strings.Split(lcaseKey, v.keyDelim)
|
path = strings.Split(lcaseKey, v.keyDelim)
|
||||||
nested = len(path) > 1
|
nested = len(path) > 1
|
||||||
@@ -1269,8 +1360,15 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
|
|||||||
s = strings.TrimSuffix(s, "]")
|
s = strings.TrimSuffix(s, "]")
|
||||||
res, _ := readAsCSV(s)
|
res, _ := readAsCSV(s)
|
||||||
return cast.ToIntSlice(res)
|
return cast.ToIntSlice(res)
|
||||||
|
case "durationSlice":
|
||||||
|
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||||
|
s = strings.TrimSuffix(s, "]")
|
||||||
|
slice := strings.Split(s, ",")
|
||||||
|
return cast.ToDurationSlice(slice)
|
||||||
case "stringToString":
|
case "stringToString":
|
||||||
return stringToStringConv(flag.ValueString())
|
return stringToStringConv(flag.ValueString())
|
||||||
|
case "stringToInt":
|
||||||
|
return stringToIntConv(flag.ValueString())
|
||||||
default:
|
default:
|
||||||
return flag.ValueString()
|
return flag.ValueString()
|
||||||
}
|
}
|
||||||
@@ -1281,9 +1379,10 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
|
|||||||
|
|
||||||
// Env override next
|
// Env override next
|
||||||
if v.automaticEnvApplied {
|
if v.automaticEnvApplied {
|
||||||
|
envKey := strings.Join(append(v.parents, lcaseKey), ".")
|
||||||
// even if it hasn't been registered, if automaticEnv is used,
|
// even if it hasn't been registered, if automaticEnv is used,
|
||||||
// check any Get request
|
// check any Get request
|
||||||
if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
|
if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
if nested && v.isPathShadowedInAutoEnv(path) != "" {
|
if nested && v.isPathShadowedInAutoEnv(path) != "" {
|
||||||
@@ -1350,6 +1449,13 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
|
|||||||
return cast.ToIntSlice(res)
|
return cast.ToIntSlice(res)
|
||||||
case "stringToString":
|
case "stringToString":
|
||||||
return stringToStringConv(flag.ValueString())
|
return stringToStringConv(flag.ValueString())
|
||||||
|
case "stringToInt":
|
||||||
|
return stringToIntConv(flag.ValueString())
|
||||||
|
case "durationSlice":
|
||||||
|
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||||
|
s = strings.TrimSuffix(s, "]")
|
||||||
|
slice := strings.Split(s, ",")
|
||||||
|
return cast.ToDurationSlice(slice)
|
||||||
default:
|
default:
|
||||||
return flag.ValueString()
|
return flag.ValueString()
|
||||||
}
|
}
|
||||||
@@ -1370,25 +1476,49 @@ func readAsCSV(val string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79
|
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79
|
||||||
// alterations are: errors are swallowed, map[string]interface{} is returned in order to enable cast.ToStringMap
|
// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.
|
||||||
func stringToStringConv(val string) interface{} {
|
func stringToStringConv(val string) any {
|
||||||
val = strings.Trim(val, "[]")
|
val = strings.Trim(val, "[]")
|
||||||
// An empty string would cause an empty map
|
// An empty string would cause an empty map
|
||||||
if len(val) == 0 {
|
if val == "" {
|
||||||
return map[string]interface{}{}
|
return map[string]any{}
|
||||||
}
|
}
|
||||||
r := csv.NewReader(strings.NewReader(val))
|
r := csv.NewReader(strings.NewReader(val))
|
||||||
ss, err := r.Read()
|
ss, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := make(map[string]interface{}, len(ss))
|
out := make(map[string]any, len(ss))
|
||||||
for _, pair := range ss {
|
for _, pair := range ss {
|
||||||
kv := strings.SplitN(pair, "=", 2)
|
k, vv, found := strings.Cut(pair, "=")
|
||||||
if len(kv) != 2 {
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out[k] = vv
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/string_to_int.go#L68
|
||||||
|
// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.
|
||||||
|
func stringToIntConv(val string) any {
|
||||||
|
val = strings.Trim(val, "[]")
|
||||||
|
// An empty string would cause an empty map
|
||||||
|
if val == "" {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
ss := strings.Split(val, ",")
|
||||||
|
out := make(map[string]any, len(ss))
|
||||||
|
for _, pair := range ss {
|
||||||
|
k, vv, found := strings.Cut(pair, "=")
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
out[k], err = strconv.Atoi(vv)
|
||||||
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out[kv[0]] = kv[1]
|
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -1422,13 +1552,13 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
|||||||
|
|
||||||
// RegisterAlias creates an alias that provides another accessor for the same key.
|
// RegisterAlias creates an alias that provides another accessor for the same key.
|
||||||
// This enables one to change a name without breaking the application.
|
// This enables one to change a name without breaking the application.
|
||||||
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
|
func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) }
|
||||||
|
|
||||||
func (v *Viper) RegisterAlias(alias string, key string) {
|
func (v *Viper) RegisterAlias(alias, key string) {
|
||||||
v.registerAlias(alias, strings.ToLower(key))
|
v.registerAlias(alias, strings.ToLower(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) registerAlias(alias string, key string) {
|
func (v *Viper) registerAlias(alias, key string) {
|
||||||
alias = strings.ToLower(alias)
|
alias = strings.ToLower(alias)
|
||||||
if alias != key && alias != v.realKey(key) {
|
if alias != key && alias != v.realKey(key) {
|
||||||
_, exists := v.aliases[alias]
|
_, exists := v.aliases[alias]
|
||||||
@@ -1486,9 +1616,9 @@ func (v *Viper) InConfig(key string) bool {
|
|||||||
// SetDefault sets the default value for this key.
|
// SetDefault sets the default value for this key.
|
||||||
// SetDefault is case-insensitive for a key.
|
// SetDefault is case-insensitive for a key.
|
||||||
// Default only used when no value is provided by the user via flag, config or ENV.
|
// Default only used when no value is provided by the user via flag, config or ENV.
|
||||||
func SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
|
func SetDefault(key string, value any) { v.SetDefault(key, value) }
|
||||||
|
|
||||||
func (v *Viper) SetDefault(key string, value interface{}) {
|
func (v *Viper) SetDefault(key string, value any) {
|
||||||
// If alias passed in, then set the proper default
|
// If alias passed in, then set the proper default
|
||||||
key = v.realKey(strings.ToLower(key))
|
key = v.realKey(strings.ToLower(key))
|
||||||
value = toCaseInsensitiveValue(value)
|
value = toCaseInsensitiveValue(value)
|
||||||
@@ -1505,9 +1635,9 @@ func (v *Viper) SetDefault(key string, value interface{}) {
|
|||||||
// Set is case-insensitive for a key.
|
// Set is case-insensitive for a key.
|
||||||
// Will be used instead of values obtained via
|
// Will be used instead of values obtained via
|
||||||
// flags, config file, ENV, default, or key/value store.
|
// flags, config file, ENV, default, or key/value store.
|
||||||
func Set(key string, value interface{}) { v.Set(key, value) }
|
func Set(key string, value any) { v.Set(key, value) }
|
||||||
|
|
||||||
func (v *Viper) Set(key string, value interface{}) {
|
func (v *Viper) Set(key string, value any) {
|
||||||
// If alias passed in, then set the proper override
|
// If alias passed in, then set the proper override
|
||||||
key = v.realKey(strings.ToLower(key))
|
key = v.realKey(strings.ToLower(key))
|
||||||
value = toCaseInsensitiveValue(value)
|
value = toCaseInsensitiveValue(value)
|
||||||
@@ -1541,7 +1671,7 @@ func (v *Viper) ReadInConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := make(map[string]interface{})
|
config := make(map[string]any)
|
||||||
|
|
||||||
err = v.unmarshalReader(bytes.NewReader(file), config)
|
err = v.unmarshalReader(bytes.NewReader(file), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1579,7 +1709,7 @@ func (v *Viper) MergeInConfig() error {
|
|||||||
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
|
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
|
||||||
|
|
||||||
func (v *Viper) ReadConfig(in io.Reader) error {
|
func (v *Viper) ReadConfig(in io.Reader) error {
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]any)
|
||||||
return v.unmarshalReader(in, v.config)
|
return v.unmarshalReader(in, v.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,7 +1717,7 @@ func (v *Viper) ReadConfig(in io.Reader) error {
|
|||||||
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
|
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
|
||||||
|
|
||||||
func (v *Viper) MergeConfig(in io.Reader) error {
|
func (v *Viper) MergeConfig(in io.Reader) error {
|
||||||
cfg := make(map[string]interface{})
|
cfg := make(map[string]any)
|
||||||
if err := v.unmarshalReader(in, cfg); err != nil {
|
if err := v.unmarshalReader(in, cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1596,11 +1726,11 @@ func (v *Viper) MergeConfig(in io.Reader) error {
|
|||||||
|
|
||||||
// MergeConfigMap merges the configuration from the map given with an existing config.
|
// MergeConfigMap merges the configuration from the map given with an existing config.
|
||||||
// Note that the map given may be modified.
|
// Note that the map given may be modified.
|
||||||
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
|
func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(cfg) }
|
||||||
|
|
||||||
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
func (v *Viper) MergeConfigMap(cfg map[string]any) error {
|
||||||
if v.config == nil {
|
if v.config == nil {
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]any)
|
||||||
}
|
}
|
||||||
insensitiviseMap(cfg)
|
insensitiviseMap(cfg)
|
||||||
mergeMaps(cfg, v.config, nil)
|
mergeMaps(cfg, v.config, nil)
|
||||||
@@ -1665,7 +1795,7 @@ func (v *Viper) writeConfig(filename string, force bool) error {
|
|||||||
return UnsupportedConfigError(configType)
|
return UnsupportedConfigError(configType)
|
||||||
}
|
}
|
||||||
if v.config == nil {
|
if v.config == nil {
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]any)
|
||||||
}
|
}
|
||||||
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
||||||
if !force {
|
if !force {
|
||||||
@@ -1686,11 +1816,11 @@ func (v *Viper) writeConfig(filename string, force bool) error {
|
|||||||
|
|
||||||
// Unmarshal a Reader into a map.
|
// Unmarshal a Reader into a map.
|
||||||
// Should probably be an unexported function.
|
// Should probably be an unexported function.
|
||||||
func unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
func unmarshalReader(in io.Reader, c map[string]any) error {
|
||||||
return v.unmarshalReader(in, c)
|
return v.unmarshalReader(in, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.ReadFrom(in)
|
buf.ReadFrom(in)
|
||||||
|
|
||||||
@@ -1724,7 +1854,7 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyExists(k string, m map[string]interface{}) string {
|
func keyExists(k string, m map[string]any) string {
|
||||||
lk := strings.ToLower(k)
|
lk := strings.ToLower(k)
|
||||||
for mk := range m {
|
for mk := range m {
|
||||||
lmk := strings.ToLower(mk)
|
lmk := strings.ToLower(mk)
|
||||||
@@ -1736,33 +1866,33 @@ func keyExists(k string, m map[string]interface{}) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func castToMapStringInterface(
|
func castToMapStringInterface(
|
||||||
src map[interface{}]interface{},
|
src map[any]any,
|
||||||
) map[string]interface{} {
|
) map[string]any {
|
||||||
tgt := map[string]interface{}{}
|
tgt := map[string]any{}
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
tgt[fmt.Sprintf("%v", k)] = v
|
tgt[fmt.Sprintf("%v", k)] = v
|
||||||
}
|
}
|
||||||
return tgt
|
return tgt
|
||||||
}
|
}
|
||||||
|
|
||||||
func castMapStringSliceToMapInterface(src map[string][]string) map[string]interface{} {
|
func castMapStringSliceToMapInterface(src map[string][]string) map[string]any {
|
||||||
tgt := map[string]interface{}{}
|
tgt := map[string]any{}
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
tgt[k] = v
|
tgt[k] = v
|
||||||
}
|
}
|
||||||
return tgt
|
return tgt
|
||||||
}
|
}
|
||||||
|
|
||||||
func castMapStringToMapInterface(src map[string]string) map[string]interface{} {
|
func castMapStringToMapInterface(src map[string]string) map[string]any {
|
||||||
tgt := map[string]interface{}{}
|
tgt := map[string]any{}
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
tgt[k] = v
|
tgt[k] = v
|
||||||
}
|
}
|
||||||
return tgt
|
return tgt
|
||||||
}
|
}
|
||||||
|
|
||||||
func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{} {
|
func castMapFlagToMapInterface(src map[string]FlagValue) map[string]any {
|
||||||
tgt := map[string]interface{}{}
|
tgt := map[string]any{}
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
tgt[k] = v
|
tgt[k] = v
|
||||||
}
|
}
|
||||||
@@ -1770,17 +1900,15 @@ func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's
|
// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's
|
||||||
// insistence on parsing nested structures as `map[interface{}]interface{}`
|
// insistence on parsing nested structures as `map[any]any`
|
||||||
// instead of using a `string` as the key for nest structures beyond one level
|
// instead of using a `string` as the key for nest structures beyond one level
|
||||||
// deep. Both map types are supported as there is a go-yaml fork that uses
|
// deep. Both map types are supported as there is a go-yaml fork that uses
|
||||||
// `map[string]interface{}` instead.
|
// `map[string]any` instead.
|
||||||
func mergeMaps(
|
func mergeMaps(src, tgt map[string]any, itgt map[any]any) {
|
||||||
src, tgt map[string]interface{}, itgt map[interface{}]interface{},
|
|
||||||
) {
|
|
||||||
for sk, sv := range src {
|
for sk, sv := range src {
|
||||||
tk := keyExists(sk, tgt)
|
tk := keyExists(sk, tgt)
|
||||||
if tk == "" {
|
if tk == "" {
|
||||||
v.logger.Trace("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv)
|
v.logger.Debug("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv)
|
||||||
tgt[sk] = sv
|
tgt[sk] = sv
|
||||||
if itgt != nil {
|
if itgt != nil {
|
||||||
itgt[sk] = sv
|
itgt[sk] = sv
|
||||||
@@ -1790,7 +1918,7 @@ func mergeMaps(
|
|||||||
|
|
||||||
tv, ok := tgt[tk]
|
tv, ok := tgt[tk]
|
||||||
if !ok {
|
if !ok {
|
||||||
v.logger.Trace("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv)
|
v.logger.Debug("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv)
|
||||||
tgt[sk] = sv
|
tgt[sk] = sv
|
||||||
if itgt != nil {
|
if itgt != nil {
|
||||||
itgt[sk] = sv
|
itgt[sk] = sv
|
||||||
@@ -1801,7 +1929,7 @@ func mergeMaps(
|
|||||||
svType := reflect.TypeOf(sv)
|
svType := reflect.TypeOf(sv)
|
||||||
tvType := reflect.TypeOf(tv)
|
tvType := reflect.TypeOf(tv)
|
||||||
|
|
||||||
v.logger.Trace(
|
v.logger.Debug(
|
||||||
"processing",
|
"processing",
|
||||||
"key", sk,
|
"key", sk,
|
||||||
"st", svType,
|
"st", svType,
|
||||||
@@ -1811,12 +1939,12 @@ func mergeMaps(
|
|||||||
)
|
)
|
||||||
|
|
||||||
switch ttv := tv.(type) {
|
switch ttv := tv.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
v.logger.Trace("merging maps (must convert)")
|
v.logger.Debug("merging maps (must convert)")
|
||||||
tsv, ok := sv.(map[interface{}]interface{})
|
tsv, ok := sv.(map[any]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
v.logger.Error(
|
v.logger.Error(
|
||||||
"Could not cast sv to map[interface{}]interface{}",
|
"Could not cast sv to map[any]any",
|
||||||
"key", sk,
|
"key", sk,
|
||||||
"st", svType,
|
"st", svType,
|
||||||
"tt", tvType,
|
"tt", tvType,
|
||||||
@@ -1829,12 +1957,12 @@ func mergeMaps(
|
|||||||
ssv := castToMapStringInterface(tsv)
|
ssv := castToMapStringInterface(tsv)
|
||||||
stv := castToMapStringInterface(ttv)
|
stv := castToMapStringInterface(ttv)
|
||||||
mergeMaps(ssv, stv, ttv)
|
mergeMaps(ssv, stv, ttv)
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
v.logger.Trace("merging maps")
|
v.logger.Debug("merging maps")
|
||||||
tsv, ok := sv.(map[string]interface{})
|
tsv, ok := sv.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
v.logger.Error(
|
v.logger.Error(
|
||||||
"Could not cast sv to map[string]interface{}",
|
"Could not cast sv to map[string]any",
|
||||||
"key", sk,
|
"key", sk,
|
||||||
"st", svType,
|
"st", svType,
|
||||||
"tt", tvType,
|
"tt", tvType,
|
||||||
@@ -1845,7 +1973,7 @@ func mergeMaps(
|
|||||||
}
|
}
|
||||||
mergeMaps(tsv, ttv, nil)
|
mergeMaps(tsv, ttv, nil)
|
||||||
default:
|
default:
|
||||||
v.logger.Trace("setting value")
|
v.logger.Debug("setting value")
|
||||||
tgt[tk] = sv
|
tgt[tk] = sv
|
||||||
if itgt != nil {
|
if itgt != nil {
|
||||||
itgt[tk] = sv
|
itgt[tk] = sv
|
||||||
@@ -1896,7 +2024,7 @@ func (v *Viper) getKeyValueConfig() error {
|
|||||||
return RemoteConfigError("No Files Found")
|
return RemoteConfigError("No Files Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]any, error) {
|
||||||
reader, err := RemoteConfig.Get(provider)
|
reader, err := RemoteConfig.Get(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1945,7 +2073,7 @@ func (v *Viper) watchKeyValueConfig() error {
|
|||||||
return RemoteConfigError("No Files Found")
|
return RemoteConfigError("No Files Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) {
|
func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]any, error) {
|
||||||
reader, err := RemoteConfig.Watch(provider)
|
reader, err := RemoteConfig.Watch(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1955,7 +2083,7 @@ func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AllKeys returns all keys holding a value, regardless of where they are set.
|
// AllKeys returns all keys holding a value, regardless of where they are set.
|
||||||
// Nested keys are returned with a v.keyDelim separator
|
// Nested keys are returned with a v.keyDelim separator.
|
||||||
func AllKeys() []string { return v.AllKeys() }
|
func AllKeys() []string { return v.AllKeys() }
|
||||||
|
|
||||||
func (v *Viper) AllKeys() []string {
|
func (v *Viper) AllKeys() []string {
|
||||||
@@ -1984,7 +2112,7 @@ func (v *Viper) AllKeys() []string {
|
|||||||
// it is skipped.
|
// it is skipped.
|
||||||
//
|
//
|
||||||
// The resulting set of paths is merged to the given shadow set at the same time.
|
// The resulting set of paths is merged to the given shadow set at the same time.
|
||||||
func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
|
func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]any, prefix string) map[string]bool {
|
||||||
if shadow != nil && prefix != "" && shadow[prefix] {
|
if shadow != nil && prefix != "" && shadow[prefix] {
|
||||||
// prefix is shadowed => nothing more to flatten
|
// prefix is shadowed => nothing more to flatten
|
||||||
return shadow
|
return shadow
|
||||||
@@ -1993,16 +2121,16 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
|||||||
shadow = make(map[string]bool)
|
shadow = make(map[string]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
var m2 map[string]interface{}
|
var m2 map[string]any
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += v.keyDelim
|
prefix += v.keyDelim
|
||||||
}
|
}
|
||||||
for k, val := range m {
|
for k, val := range m {
|
||||||
fullKey := prefix + k
|
fullKey := prefix + k
|
||||||
switch val.(type) {
|
switch val := val.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
m2 = val.(map[string]interface{})
|
m2 = val
|
||||||
case map[interface{}]interface{}:
|
case map[any]any:
|
||||||
m2 = cast.ToStringMap(val)
|
m2 = cast.ToStringMap(val)
|
||||||
default:
|
default:
|
||||||
// immediate value
|
// immediate value
|
||||||
@@ -2017,7 +2145,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
|||||||
|
|
||||||
// mergeFlatMap merges the given maps, excluding values of the second map
|
// mergeFlatMap merges the given maps, excluding values of the second map
|
||||||
// shadowed by values from the first map.
|
// shadowed by values from the first map.
|
||||||
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]any) map[string]bool {
|
||||||
// scan keys
|
// scan keys
|
||||||
outer:
|
outer:
|
||||||
for k := range m {
|
for k := range m {
|
||||||
@@ -2037,13 +2165,17 @@ outer:
|
|||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllSettings merges all settings and returns them as a map[string]interface{}.
|
// AllSettings merges all settings and returns them as a map[string]any.
|
||||||
func AllSettings() map[string]interface{} { return v.AllSettings() }
|
func AllSettings() map[string]any { return v.AllSettings() }
|
||||||
|
|
||||||
func (v *Viper) AllSettings() map[string]interface{} {
|
func (v *Viper) AllSettings() map[string]any {
|
||||||
m := map[string]interface{}{}
|
return v.getSettings(v.AllKeys())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Viper) getSettings(keys []string) map[string]any {
|
||||||
|
m := map[string]any{}
|
||||||
// start from the list of keys, and construct the map one value at a time
|
// start from the list of keys, and construct the map one value at a time
|
||||||
for _, k := range v.AllKeys() {
|
for _, k := range keys {
|
||||||
value := v.Get(k)
|
value := v.Get(k)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
// should not happen, since AllKeys() returns only keys holding a value,
|
// should not happen, since AllKeys() returns only keys holding a value,
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
//go:build go1.16 && finder
|
|
||||||
// +build go1.16,finder
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Search all configPaths for any config file.
|
|
||||||
// Returns the first path that exists (and is a config file).
|
|
||||||
func (v *Viper) findConfigFile() (string, error) {
|
|
||||||
finder := finder{
|
|
||||||
paths: v.configPaths,
|
|
||||||
fileNames: []string{v.configName},
|
|
||||||
extensions: SupportedExts,
|
|
||||||
withoutExtension: v.configType != "",
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := finder.Find(afero.NewIOFS(v.fs))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if file == "" {
|
|
||||||
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
+505
-421
File diff suppressed because it is too large
Load Diff
@@ -1,56 +0,0 @@
|
|||||||
//go:build viper_yaml2
|
|
||||||
// +build viper_yaml2
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
var yamlExample = []byte(`Hacker: true
|
|
||||||
name: steve
|
|
||||||
hobbies:
|
|
||||||
- skateboarding
|
|
||||||
- snowboarding
|
|
||||||
- go
|
|
||||||
clothing:
|
|
||||||
jacket: leather
|
|
||||||
trousers: denim
|
|
||||||
pants:
|
|
||||||
size: large
|
|
||||||
age: 35
|
|
||||||
eyes : brown
|
|
||||||
beard: true
|
|
||||||
`)
|
|
||||||
|
|
||||||
var yamlWriteExpected = []byte(`age: 35
|
|
||||||
beard: true
|
|
||||||
clothing:
|
|
||||||
jacket: leather
|
|
||||||
pants:
|
|
||||||
size: large
|
|
||||||
trousers: denim
|
|
||||||
eyes: brown
|
|
||||||
hacker: true
|
|
||||||
hobbies:
|
|
||||||
- skateboarding
|
|
||||||
- snowboarding
|
|
||||||
- go
|
|
||||||
name: steve
|
|
||||||
`)
|
|
||||||
|
|
||||||
var yamlExampleWithDot = []byte(`Hacker: true
|
|
||||||
name: steve
|
|
||||||
hobbies:
|
|
||||||
- skateboarding
|
|
||||||
- snowboarding
|
|
||||||
- go
|
|
||||||
clothing:
|
|
||||||
jacket: leather
|
|
||||||
trousers: denim
|
|
||||||
pants:
|
|
||||||
size: large
|
|
||||||
age: 35
|
|
||||||
eyes : brown
|
|
||||||
beard: true
|
|
||||||
emails:
|
|
||||||
steve@hacker.com:
|
|
||||||
created: 01/02/03
|
|
||||||
active: true
|
|
||||||
`)
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
//go:build !viper_yaml2
|
|
||||||
// +build !viper_yaml2
|
|
||||||
|
|
||||||
package viper
|
package viper
|
||||||
|
|
||||||
var yamlExample = []byte(`Hacker: true
|
var yamlExample = []byte(`Hacker: true
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows
|
|
||||||
// +build darwin dragonfly freebsd openbsd linux netbsd solaris windows
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
import "github.com/fsnotify/fsnotify"
|
|
||||||
|
|
||||||
type watcher = fsnotify.Watcher
|
|
||||||
|
|
||||||
func newWatcher() (*watcher, error) {
|
|
||||||
return fsnotify.NewWatcher()
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
|
|
||||||
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
|
||||||
|
|
||||||
package viper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newWatcher() (*watcher, error) {
|
|
||||||
return &watcher{}, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
type watcher struct {
|
|
||||||
Events chan fsnotify.Event
|
|
||||||
Errors chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*watcher) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*watcher) Add(name string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*watcher) Remove(name string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user