chore: add globale config

This commit is contained in:
kekskurse 2025-03-14 01:16:08 +01:00
parent 90490cd611
commit 8be8077d94
14 changed files with 1718 additions and 4 deletions

View file

@ -1 +1,9 @@
# Miniauth.
## Config
| Name | Description | Default |
|------|-------------|---------|
| USERSTORE_SQLITE_PATH | Path to the Sqlite Database | `none` |
| PORT | Port for the Webserver | 8080 |

1
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b
github.com/google/uuid v1.6.0
github.com/rs/zerolog v1.33.0
github.com/sethvargo/go-envconfig v1.1.1
github.com/stretchr/testify v1.9.0
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d
golang.org/x/crypto v0.23.0

6
go.sum
View file

@ -37,8 +37,8 @@ github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
@ -75,6 +75,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sethvargo/go-envconfig v1.1.1 h1:JDu8Q9baIzJf47NPkzhIB6aLYL0vQ+pPypoYrejS9QY=
github.com/sethvargo/go-envconfig v1.1.1/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=

22
main.go
View file

@ -1,19 +1,39 @@
package main
import (
"context"
"embed"
"fmt"
"html/template"
"git.keks.cloud/kekskurse/miniauth/pkg/userstore"
"git.keks.cloud/kekskurse/miniauth/pkg/web"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/sethvargo/go-envconfig"
)
//go:embed templates/*
var templatesFS embed.FS
func main() {
cnf := config()
router := setupRouter()
router.Run(":8080")
router.Run(fmt.Sprintf(":%v", cnf.Port))
}
type gloableConfig struct {
UserStoreConfig userstore.Config `env:", prefix=USERSTORE_"`
Port int `env:"PORT, default=8080"`
}
func config() gloableConfig {
var c gloableConfig
err := envconfig.Process(context.Background(), &c)
if err != nil {
log.Fatal().Err(err).Msg("cant parse config")
}
return c
}
func loadTemplates() *template.Template {

View file

@ -20,7 +20,7 @@ var schema string
type Config struct {
SQLite struct {
Path string
Path string `env:"SQLITE_PATH"`
}
Logger zerolog.Logger
}

131
vendor/github.com/rs/zerolog/log/log.go generated vendored Normal file
View file

@ -0,0 +1,131 @@
// Package log provides a global logger for zerolog.
package log
import (
"context"
"fmt"
"io"
"os"
"github.com/rs/zerolog"
)
// Logger is the global logger.
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
// Output duplicates the global logger and sets w as its output.
func Output(w io.Writer) zerolog.Logger {
return Logger.Output(w)
}
// With creates a child logger with the field added to its context.
func With() zerolog.Context {
return Logger.With()
}
// Level creates a child logger with the minimum accepted level set to level.
func Level(level zerolog.Level) zerolog.Logger {
return Logger.Level(level)
}
// Sample returns a logger with the s sampler.
func Sample(s zerolog.Sampler) zerolog.Logger {
return Logger.Sample(s)
}
// Hook returns a logger with the h Hook.
func Hook(h zerolog.Hook) zerolog.Logger {
return Logger.Hook(h)
}
// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil.
//
// You must call Msg on the returned event in order to send the event.
func Err(err error) *zerolog.Event {
return Logger.Err(err)
}
// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
func Trace() *zerolog.Event {
return Logger.Trace()
}
// Debug starts a new message with debug level.
//
// You must call Msg on the returned event in order to send the event.
func Debug() *zerolog.Event {
return Logger.Debug()
}
// Info starts a new message with info level.
//
// You must call Msg on the returned event in order to send the event.
func Info() *zerolog.Event {
return Logger.Info()
}
// Warn starts a new message with warn level.
//
// You must call Msg on the returned event in order to send the event.
func Warn() *zerolog.Event {
return Logger.Warn()
}
// Error starts a new message with error level.
//
// You must call Msg on the returned event in order to send the event.
func Error() *zerolog.Event {
return Logger.Error()
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method.
//
// You must call Msg on the returned event in order to send the event.
func Fatal() *zerolog.Event {
return Logger.Fatal()
}
// Panic starts a new message with panic level. The message is also sent
// to the panic function.
//
// You must call Msg on the returned event in order to send the event.
func Panic() *zerolog.Event {
return Logger.Panic()
}
// WithLevel starts a new message with level.
//
// You must call Msg on the returned event in order to send the event.
func WithLevel(level zerolog.Level) *zerolog.Event {
return Logger.WithLevel(level)
}
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
// zerolog.Disabled will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
func Log() *zerolog.Event {
return Logger.Log()
}
// Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
Logger.Debug().CallerSkipFrame(1).Msg(fmt.Sprint(v...))
}
// Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
Logger.Debug().CallerSkipFrame(1).Msgf(format, v...)
}
// Ctx returns the Logger associated with the ctx. If no logger
// is associated, a disabled logger is returned.
func Ctx(ctx context.Context) *zerolog.Logger {
return zerolog.Ctx(ctx)
}

8
vendor/github.com/sethvargo/go-envconfig/AUTHORS generated vendored Normal file
View file

@ -0,0 +1,8 @@
# This is the list of envconfig authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google LLC
Seth Vargo

202
vendor/github.com/sethvargo/go-envconfig/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

22
vendor/github.com/sethvargo/go-envconfig/Makefile generated vendored Normal file
View file

@ -0,0 +1,22 @@
# Copyright The envconfig Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
test:
@go test \
-count=1 \
-race \
-shuffle=on \
-timeout=10m \
./...
.PHONY: test

306
vendor/github.com/sethvargo/go-envconfig/README.md generated vendored Normal file
View file

@ -0,0 +1,306 @@
# Envconfig
[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godoc]
Envconfig populates struct field values based on environment variables or
arbitrary lookup functions. It supports pre-setting mutations, which is useful
for things like converting values to uppercase, trimming whitespace, or looking
up secrets.
## Usage
Define a struct with fields using the `env` tag:
```go
type MyConfig struct {
Port string `env:"PORT"`
Username string `env:"USERNAME"`
}
```
Set some environment variables:
```sh
export PORT=5555
export USERNAME=yoyo
```
Process it using envconfig:
```go
package main
import (
"context"
"log"
"github.com/sethvargo/go-envconfig"
)
func main() {
ctx := context.Background()
var c MyConfig
if err := envconfig.Process(ctx, &c); err != nil {
log.Fatal(err)
}
// c.Port = 5555
// c.Username = "yoyo"
}
```
You can also use nested structs, just remember that any fields you want to
process must be public:
```go
type MyConfig struct {
Database *DatabaseConfig
}
type DatabaseConfig struct {
Port string `env:"PORT"`
Username string `env:"USERNAME"`
}
```
## Configuration
Use the `env` struct tag to define configuration. See the [godoc][] for usage
examples.
- `required` - marks a field as required. If a field is required, decoding
will error if the environment variable is unset.
```go
type MyStruct struct {
Port string `env:"PORT, required"`
}
```
- `default` - sets the default value for the environment variable is not set.
The environment variable must not be set (e.g. `unset PORT`). If the
environment variable is the empty string, envconfig considers that a "value"
and the default will **not** be used.
You can also set the default value to the value from another field or a
value from a different environment variable.
```go
type MyStruct struct {
Port string `env:"PORT, default=5555"`
User string `env:"USER, default=$CURRENT_USER"`
}
```
- `prefix` - sets the prefix to use for looking up environment variable keys
on child structs and fields. This is useful for shared configurations:
```go
type RedisConfig struct {
Host string `env:"REDIS_HOST"`
User string `env:"REDIS_USER"`
}
type ServerConfig struct {
// CacheConfig will process values from $CACHE_REDIS_HOST and
// $CACHE_REDIS_USER respectively.
CacheConfig *RedisConfig `env:", prefix=CACHE_"`
// RateLimitConfig will process values from $RATE_LIMIT_REDIS_HOST and
// $RATE_LIMIT_REDIS_USER respectively.
RateLimitConfig *RedisConfig `env:", prefix=RATE_LIMIT_"`
}
```
- `overwrite` - force overwriting existing non-zero struct values if the
environment variable was provided.
```go
type MyStruct struct {
Port string `env:"PORT, overwrite"`
}
```
The rules for overwrite + default are:
- If the struct field has the zero value and a default is set:
- If no environment variable is specified, the struct field will be
populated with the default value.
- If an environment variable is specified, the struct field will be
populate with the environment variable value.
- If the struct field has a non-zero value and a default is set:
- If no environment variable is specified, the struct field's existing
value will be used (the default is ignored).
- If an environment variable is specified, the struct field's existing
value will be overwritten with the environment variable value.
- `delimiter` - choose a custom character to denote individual slice and map
entries. The default value is the comma (`,`).
```go
type MyStruct struct {
MyVar []string `env:"MYVAR, delimiter=;"`
```
```bash
export MYVAR="a;b;c;d" # []string{"a", "b", "c", "d"}
```
- `separator` - choose a custom character to denote the separation between
keys and values in map entries. The default value is the colon (`:`) Define
a separator with `separator`:
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR, separator=|"`
}
```
```bash
export MYVAR="a|b,c|d" # map[string]string{"a":"b", "c":"d"}
```
- `noinit` - do not initialize struct fields unless environment variables were
provided. The default behavior is to deeply initialize all fields to their
default (zero) value.
```go
type MyStruct struct {
MyVar *url.URL `env:"MYVAR, noinit"`
}
```
- `decodeunset` - force envconfig to run decoders even on unset environment
variable values. The default behavior is to skip running decoders on unset
environment variable values.
```go
type MyStruct struct {
MyVar *url.URL `env:"MYVAR, decodeunset"`
}
```
## Decoding
> [!NOTE]
>
> Complex types are only decoded or unmarshalled when the environment variable
> is defined or a default value is specified.
### Durations
In the environment, `time.Duration` values are specified as a parsable Go
duration:
```go
type MyStruct struct {
MyVar time.Duration `env:"MYVAR"`
}
```
```bash
export MYVAR="10m" # 10 * time.Minute
```
### TextUnmarshaler / BinaryUnmarshaler
Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as
such.
### json.Unmarshaler
Types that implement `json.Unmarshaler` are processed as such.
### gob.Decoder
Types that implement `gob.Decoder` are processed as such.
### Slices
Slices are specified as comma-separated values.
```go
type MyStruct struct {
MyVar []string `env:"MYVAR"`
}
```
```bash
export MYVAR="a,b,c,d" # []string{"a", "b", "c", "d"}
```
Note that byte slices are special cased and interpreted as strings from the
environment.
### Maps
Maps are specified as comma-separated key:value pairs:
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR"`
}
```
```bash
export MYVAR="a:b,c:d" # map[string]string{"a":"b", "c":"d"}
```
### Structs
Envconfig walks the entire struct, including nested structs, so deeply-nested
fields are also supported.
If a nested struct is a pointer type, it will automatically be instantianted to
the non-nil value. To change this behavior, see
[Initialization](#Initialization).
### Custom Decoders
You can also define your own decoders. See the [godoc][godoc] for more
information.
## Testing
Relying on the environment in tests can be troublesome because environment
variables are global, which makes it difficult to parallelize the tests.
Envconfig supports extracting data from anything that returns a value:
```go
lookuper := envconfig.MapLookuper(map[string]string{
"FOO": "bar",
"ZIP": "zap",
})
var config Config
envconfig.ProcessWith(ctx, &envconfig.Config{
Target: &config,
Lookuper: lookuper,
})
```
Now you can parallelize all your tests by providing a map for the lookup
function. In fact, that's how the tests in this repo work, so check there for an
example.
You can also combine multiple lookupers with `MultiLookuper`. See the GoDoc for
more information and examples.
[godoc]: https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig

57
vendor/github.com/sethvargo/go-envconfig/decoding.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envconfig
import (
"encoding/base64"
"encoding/hex"
"strings"
)
// Base64Bytes is a slice of bytes where the information is base64-encoded in
// the environment variable.
type Base64Bytes []byte
// EnvDecode implements env.Decoder.
func (b *Base64Bytes) EnvDecode(val string) error {
val = strings.ReplaceAll(val, "+", "-")
val = strings.ReplaceAll(val, "/", "_")
val = strings.TrimRight(val, "=")
var err error
*b, err = base64.RawURLEncoding.DecodeString(val)
return err
}
// Bytes returns the underlying bytes.
func (b Base64Bytes) Bytes() []byte {
return []byte(b)
}
// HexBytes is a slice of bytes where the information is hex-encoded in the
// environment variable.
type HexBytes []byte
// EnvDecode implements env.Decoder.
func (b *HexBytes) EnvDecode(val string) error {
var err error
*b, err = hex.DecodeString(val)
return err
}
// Bytes returns the underlying bytes.
func (b HexBytes) Bytes() []byte {
return []byte(b)
}

877
vendor/github.com/sethvargo/go-envconfig/envconfig.go generated vendored Normal file
View file

@ -0,0 +1,877 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package envconfig populates struct fields based on environment variable
// values (or anything that responds to "Lookup"). Structs declare their
// environment dependencies using the "env" tag with the key being the name of
// the environment variable, case sensitive.
//
// type MyStruct struct {
// A string `env:"A"` // resolves A to $A
// B string `env:"B,required"` // resolves B to $B, errors if $B is unset
// C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"
//
// D string `env:"D,required,default=foo"` // error, cannot be required and default
// E string `env:""` // error, must specify key
// }
//
// All built-in types are supported except Func and Chan. If you need to define
// a custom decoder, implement Decoder:
//
// type MyStruct struct {
// field string
// }
//
// func (v *MyStruct) EnvDecode(val string) error {
// v.field = fmt.Sprintf("PREFIX-%s", val)
// return nil
// }
//
// In the environment, slices are specified as comma-separated values:
//
// export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}
//
// In the environment, maps are specified as comma-separated key:value pairs:
//
// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
//
// For more configuration options and examples, see the documentation.
package envconfig
import (
"context"
"encoding"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
"unicode"
)
const (
envTag = "env"
optDecodeUnset = "decodeunset"
optDefault = "default="
optDelimiter = "delimiter="
optNoInit = "noinit"
optOverwrite = "overwrite"
optPrefix = "prefix="
optRequired = "required"
optSeparator = "separator="
)
// internalError is a custom error type for errors returned by envconfig.
type internalError string
// Error implements error.
func (e internalError) Error() string {
return string(e)
}
const (
ErrInvalidEnvvarName = internalError("invalid environment variable name")
ErrInvalidMapItem = internalError("invalid map item")
ErrLookuperNil = internalError("lookuper cannot be nil")
ErrMissingKey = internalError("missing key")
ErrMissingRequired = internalError("missing required value")
ErrNoInitNotPtr = internalError("field must be a pointer to have noinit")
ErrNotPtr = internalError("input must be a pointer")
ErrNotStruct = internalError("input must be a struct")
ErrPrefixNotStruct = internalError("prefix is only valid on struct types")
ErrPrivateField = internalError("cannot parse private fields")
ErrRequiredAndDefault = internalError("field cannot be required and have a default value")
ErrUnknownOption = internalError("unknown option")
)
// Lookuper is an interface that provides a lookup for a string-based key.
type Lookuper interface {
// Lookup searches for the given key and returns the corresponding string
// value. If a value is found, it returns the value and true. If a value is
// not found, it returns the empty string and false.
Lookup(key string) (string, bool)
}
// osLookuper looks up environment configuration from the local environment.
type osLookuper struct{}
// Verify implements interface.
var _ Lookuper = (*osLookuper)(nil)
func (o *osLookuper) Lookup(key string) (string, bool) {
return os.LookupEnv(key)
}
// OsLookuper returns a lookuper that uses the environment ([os.LookupEnv]) to
// resolve values.
func OsLookuper() Lookuper {
return new(osLookuper)
}
type mapLookuper map[string]string
var _ Lookuper = (*mapLookuper)(nil)
func (m mapLookuper) Lookup(key string) (string, bool) {
v, ok := m[key]
return v, ok
}
// MapLookuper looks up environment configuration from a provided map. This is
// useful for testing, especially in parallel, since it does not require you to
// mutate the parent environment (which is stateful).
func MapLookuper(m map[string]string) Lookuper {
return mapLookuper(m)
}
type multiLookuper struct {
ls []Lookuper
}
var _ Lookuper = (*multiLookuper)(nil)
func (m *multiLookuper) Lookup(key string) (string, bool) {
for _, l := range m.ls {
if v, ok := l.Lookup(key); ok {
return v, true
}
}
return "", false
}
// PrefixLookuper looks up environment configuration using the specified prefix.
// This is useful if you want all your variables to start with a particular
// prefix like "MY_APP_".
func PrefixLookuper(prefix string, l Lookuper) Lookuper {
if typ, ok := l.(*prefixLookuper); ok {
return &prefixLookuper{prefix: typ.prefix + prefix, l: typ.l}
}
return &prefixLookuper{prefix: prefix, l: l}
}
type prefixLookuper struct {
l Lookuper
prefix string
}
func (p *prefixLookuper) Lookup(key string) (string, bool) {
return p.l.Lookup(p.Key(key))
}
func (p *prefixLookuper) Key(key string) string {
return p.prefix + key
}
func (p *prefixLookuper) Unwrap() Lookuper {
l := p.l
for v, ok := l.(unwrappableLookuper); ok; {
l = v.Unwrap()
}
return l
}
// unwrappableLookuper is a lookuper that can return the underlying lookuper.
type unwrappableLookuper interface {
Unwrap() Lookuper
}
// MultiLookuper wraps a collection of lookupers. It does not combine them, and
// lookups appear in the order in which they are provided to the initializer.
func MultiLookuper(lookupers ...Lookuper) Lookuper {
return &multiLookuper{ls: lookupers}
}
// keyedLookuper is an extension to the [Lookuper] interface that returns the
// underlying key (used by the [PrefixLookuper] or custom implementations).
type keyedLookuper interface {
Key(key string) string
}
// Decoder is an interface that custom types/fields can implement to control how
// decoding takes place. For example:
//
// type MyType string
//
// func (mt MyType) EnvDecode(val string) error {
// return "CUSTOM-"+val
// }
type Decoder interface {
EnvDecode(val string) error
}
// options are internal options for decoding.
type options struct {
Default string
Delimiter string
Prefix string
Separator string
NoInit bool
Overwrite bool
DecodeUnset bool
Required bool
}
// Config represent inputs to the envconfig decoding.
type Config struct {
// Target is the destination structure to decode. This value is required, and
// it must be a pointer to a struct.
Target any
// Lookuper is the lookuper implementation to use. If not provided, it
// defaults to the OS Lookuper.
Lookuper Lookuper
// DefaultDelimiter is the default value to use for the delimiter in maps and
// slices. This can be overridden on a per-field basis, which takes
// precedence. The default value is ",".
DefaultDelimiter string
// DefaultSeparator is the default value to use for the separator in maps.
// This can be overridden on a per-field basis, which takes precedence. The
// default value is ":".
DefaultSeparator string
// DefaultNoInit is the default value for skipping initialization of
// unprovided fields. The default value is false (deeply initialize all
// fields and nested structs).
DefaultNoInit bool
// DefaultOverwrite is the default value for overwriting an existing value set
// on the struct before processing. The default value is false.
DefaultOverwrite bool
// DefaultDecodeUnset is the default value for running decoders even when no
// value was given for the environment variable.
DefaultDecodeUnset bool
// DefaultRequired is the default value for marking a field as required. The
// default value is false.
DefaultRequired bool
// Mutators is an optional list of mutators to apply to lookups.
Mutators []Mutator
}
// Process decodes the struct using values from environment variables. See
// [ProcessWith] for a more customizable version.
//
// As a special case, if the input for the target is a [*Config], then this
// function will call [ProcessWith] using the provided config, with any mutation
// appended.
func Process(ctx context.Context, i any, mus ...Mutator) error {
if v, ok := i.(*Config); ok {
v.Mutators = append(v.Mutators, mus...)
return ProcessWith(ctx, v)
}
return ProcessWith(ctx, &Config{
Target: i,
Mutators: mus,
})
}
// MustProcess is a helper that calls [Process] and panics if an error is
// encountered. Unlike [Process], the input value is returned, making it ideal
// for anonymous initializations:
//
// var env = envconfig.MustProcess(context.Background(), &struct{
// Field string `env:"FIELD,required"`
// })
//
// This is not recommend for production services, but it can be useful for quick
// CLIs and scripts that want to take advantage of envconfig's environment
// parsing at the expense of testability and graceful error handling.
func MustProcess[T any](ctx context.Context, i T, mus ...Mutator) T {
if err := Process(ctx, i, mus...); err != nil {
panic(err)
}
return i
}
// ProcessWith executes the decoding process using the provided [Config].
func ProcessWith(ctx context.Context, c *Config) error {
if c == nil {
c = new(Config)
}
if c.Lookuper == nil {
c.Lookuper = OsLookuper()
}
// Deep copy the slice and remove any nil mutators.
var mus []Mutator
for _, m := range c.Mutators {
if m != nil {
mus = append(mus, m)
}
}
c.Mutators = mus
return processWith(ctx, c)
}
// processWith is a helper that retains configuration from the parent structs.
func processWith(ctx context.Context, c *Config) error {
i := c.Target
l := c.Lookuper
if l == nil {
return ErrLookuperNil
}
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
return ErrNotPtr
}
e := v.Elem()
if e.Kind() != reflect.Struct {
return ErrNotStruct
}
t := e.Type()
structDelimiter := c.DefaultDelimiter
if structDelimiter == "" {
structDelimiter = ","
}
structNoInit := c.DefaultNoInit
structSeparator := c.DefaultSeparator
if structSeparator == "" {
structSeparator = ":"
}
structOverwrite := c.DefaultOverwrite
structDecodeUnset := c.DefaultDecodeUnset
structRequired := c.DefaultRequired
mutators := c.Mutators
for i := 0; i < t.NumField(); i++ {
ef := e.Field(i)
tf := t.Field(i)
tag := tf.Tag.Get(envTag)
if !ef.CanSet() {
if tag != "" {
// There's an "env" tag on a private field, we can't alter it, and it's
// likely a mistake. Return an error so the user can handle.
return fmt.Errorf("%s: %w", tf.Name, ErrPrivateField)
}
// Otherwise continue to the next field.
continue
}
// Parse the key and options.
key, opts, err := keyAndOpts(tag)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
// NoInit is only permitted on pointers.
if opts.NoInit &&
ef.Kind() != reflect.Ptr &&
ef.Kind() != reflect.Slice &&
ef.Kind() != reflect.Map &&
ef.Kind() != reflect.UnsafePointer {
return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr)
}
// Compute defaults from local tags.
delimiter := structDelimiter
if v := opts.Delimiter; v != "" {
delimiter = v
}
separator := structSeparator
if v := opts.Separator; v != "" {
separator = v
}
noInit := structNoInit || opts.NoInit
overwrite := structOverwrite || opts.Overwrite
decodeUnset := structDecodeUnset || opts.DecodeUnset
required := structRequired || opts.Required
isNilStructPtr := false
setNilStruct := func(v reflect.Value) {
origin := e.Field(i)
if isNilStructPtr {
empty := reflect.New(origin.Type().Elem()).Interface()
// If a struct (after traversal) equals to the empty value, it means
// nothing was changed in any sub-fields. With the noinit opt, we skip
// setting the empty value to the original struct pointer (keep it nil).
if !reflect.DeepEqual(v.Interface(), empty) || !noInit {
origin.Set(v)
}
}
}
// Initialize pointer structs.
pointerWasSet := false
for ef.Kind() == reflect.Ptr {
if ef.IsNil() {
if ef.Type().Elem().Kind() != reflect.Struct {
// This is a nil pointer to something that isn't a struct, like
// *string. Move along.
break
}
isNilStructPtr = true
// Use an empty struct of the type so we can traverse.
ef = reflect.New(ef.Type().Elem()).Elem()
} else {
pointerWasSet = true
ef = ef.Elem()
}
}
// Special case handle structs. This has to come after the value resolution in
// case the struct has a custom decoder.
if ef.Kind() == reflect.Struct {
for ef.CanAddr() {
ef = ef.Addr()
}
// Lookup the value, ignoring an error if the key isn't defined. This is
// required for nested structs that don't declare their own `env` keys,
// but have internal fields with an `env` defined.
val, found, usedDefault, err := lookup(key, required, opts.Default, l)
if err != nil && !errors.Is(err, ErrMissingKey) {
return fmt.Errorf("%s: %w", tf.Name, err)
}
if found || usedDefault || decodeUnset {
if ok, err := processAsDecoder(val, ef); ok {
if err != nil {
return err
}
setNilStruct(ef)
continue
}
}
plu := l
if opts.Prefix != "" {
plu = PrefixLookuper(opts.Prefix, l)
}
if err := processWith(ctx, &Config{
Target: ef.Interface(),
Lookuper: plu,
DefaultDelimiter: delimiter,
DefaultSeparator: separator,
DefaultNoInit: noInit,
DefaultOverwrite: overwrite,
DefaultRequired: required,
Mutators: mutators,
}); err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
setNilStruct(ef)
continue
}
// It's invalid to have a prefix on a non-struct field.
if opts.Prefix != "" {
return ErrPrefixNotStruct
}
// Stop processing if there's no env tag (this comes after nested parsing),
// in case there's an env tag in an embedded struct.
if tag == "" {
continue
}
// The field already has a non-zero value and overwrite is false, do not
// overwrite.
if (pointerWasSet || !ef.IsZero()) && !overwrite {
continue
}
val, found, usedDefault, err := lookup(key, required, opts.Default, l)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
// If the field already has a non-zero value and there was no value directly
// specified, do not overwrite the existing field. We only want to overwrite
// when the envvar was provided directly.
if (pointerWasSet || !ef.IsZero()) && !found {
continue
}
// Apply any mutators. Mutators are applied after the lookup, but before any
// type conversions. They always resolve to a string (or error), so we don't
// call mutators when the environment variable was not set.
if found || usedDefault {
originalKey := key
resolvedKey := originalKey
if keyer, ok := l.(keyedLookuper); ok {
resolvedKey = keyer.Key(resolvedKey)
}
originalValue := val
stop := false
for _, mu := range mutators {
val, stop, err = mu.EnvMutate(ctx, originalKey, resolvedKey, originalValue, val)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
if stop {
break
}
}
}
// Set value.
if err := processField(val, ef, delimiter, separator, noInit); err != nil {
return fmt.Errorf("%s(%q): %w", tf.Name, val, err)
}
}
return nil
}
// SplitString splits the given string on the provided rune, unless the rune is
// escaped by the escape character.
func splitString(s, on, esc string) []string {
a := strings.Split(s, on)
for i := len(a) - 2; i >= 0; i-- {
if strings.HasSuffix(a[i], esc) {
a[i] = a[i][:len(a[i])-len(esc)] + on + a[i+1]
a = append(a[:i+1], a[i+2:]...)
}
}
return a
}
// keyAndOpts parses the given tag value (e.g. env:"foo,required") and
// returns the key name and options as a list.
func keyAndOpts(tag string) (string, *options, error) {
parts := splitString(tag, ",", "\\")
key, tagOpts := strings.TrimSpace(parts[0]), parts[1:]
if key != "" && !validateEnvName(key) {
return "", nil, fmt.Errorf("%q: %w ", key, ErrInvalidEnvvarName)
}
var opts options
LOOP:
for i, o := range tagOpts {
o = strings.TrimLeftFunc(o, unicode.IsSpace)
search := strings.ToLower(o)
switch {
case search == optDecodeUnset:
opts.DecodeUnset = true
case search == optOverwrite:
opts.Overwrite = true
case search == optRequired:
opts.Required = true
case search == optNoInit:
opts.NoInit = true
case strings.HasPrefix(search, optPrefix):
opts.Prefix = strings.TrimPrefix(o, optPrefix)
case strings.HasPrefix(search, optDelimiter):
opts.Delimiter = strings.TrimPrefix(o, optDelimiter)
case strings.HasPrefix(search, optSeparator):
opts.Separator = strings.TrimPrefix(o, optSeparator)
case strings.HasPrefix(search, optDefault):
// If a default value was given, assume everything after is the provided
// value, including comma-seprated items.
o = strings.TrimLeft(strings.Join(tagOpts[i:], ","), " ")
opts.Default = strings.TrimPrefix(o, optDefault)
break LOOP
default:
return "", nil, fmt.Errorf("%q: %w", o, ErrUnknownOption)
}
}
return key, &opts, nil
}
// lookup looks up the given key using the provided Lookuper and options. The
// first boolean parameter indicates whether the value was found in the
// lookuper. The second boolean parameter indicates whether the default value
// was used.
func lookup(key string, required bool, defaultValue string, l Lookuper) (string, bool, bool, error) {
if key == "" {
// The struct has something like `env:",required"`, which is likely a
// mistake. We could try to infer the envvar from the field name, but that
// feels too magical.
return "", false, false, ErrMissingKey
}
if required && defaultValue != "" {
// Having a default value on a required value doesn't make sense.
return "", false, false, ErrRequiredAndDefault
}
// Lookup value.
val, found := l.Lookup(key)
if !found {
if required {
if keyer, ok := l.(keyedLookuper); ok {
key = keyer.Key(key)
}
return "", false, false, fmt.Errorf("%w: %s", ErrMissingRequired, key)
}
if defaultValue != "" {
// Expand the default value. This allows for a default value that maps to
// a different environment variable.
val = os.Expand(defaultValue, func(i string) string {
lookuper := l
if v, ok := lookuper.(unwrappableLookuper); ok {
lookuper = v.Unwrap()
}
s, ok := lookuper.Lookup(i)
if ok {
return s
}
return ""
})
return val, false, true, nil
}
}
return val, found, false, nil
}
// processAsDecoder processes the given value as a decoder or custom
// unmarshaller.
func processAsDecoder(v string, ef reflect.Value) (bool, error) {
// Keep a running error. It's possible that a property might implement
// multiple decoders, and we don't know *which* decoder will succeed. If we
// get through all of them, we'll return the most recent error.
var imp bool
var err error
// Resolve any pointers.
for ef.CanAddr() {
ef = ef.Addr()
}
if ef.CanInterface() {
iface := ef.Interface()
// If a developer chooses to implement the Decoder interface on a type,
// never attempt to use other decoders in case of failure. EnvDecode's
// decoding logic is "the right one", and the error returned (if any)
// is the most specific we can get.
if dec, ok := iface.(Decoder); ok {
imp = true
err = dec.EnvDecode(v)
return imp, err
}
if tu, ok := iface.(encoding.TextUnmarshaler); ok {
imp = true
if err = tu.UnmarshalText([]byte(v)); err == nil {
return imp, nil
}
}
if tu, ok := iface.(json.Unmarshaler); ok {
imp = true
if err = tu.UnmarshalJSON([]byte(v)); err == nil {
return imp, nil
}
}
if tu, ok := iface.(encoding.BinaryUnmarshaler); ok {
imp = true
if err = tu.UnmarshalBinary([]byte(v)); err == nil {
return imp, nil
}
}
if tu, ok := iface.(gob.GobDecoder); ok {
imp = true
if err = tu.GobDecode([]byte(v)); err == nil {
return imp, nil
}
}
}
return imp, err
}
func processField(v string, ef reflect.Value, delimiter, separator string, noInit bool) error {
// If the input value is empty and initialization is skipped, do nothing.
if v == "" && noInit {
return nil
}
// Handle pointers and uninitialized pointers.
for ef.Type().Kind() == reflect.Ptr {
if ef.IsNil() {
ef.Set(reflect.New(ef.Type().Elem()))
}
ef = ef.Elem()
}
tf := ef.Type()
tk := tf.Kind()
// Handle existing decoders.
if ok, err := processAsDecoder(v, ef); ok {
return err
}
// We don't check if the value is empty earlier, because the user might want
// to define a custom decoder and treat the empty variable as a special case.
// However, if we got this far, none of the remaining parsers will succeed, so
// bail out now.
if v == "" {
return nil
}
switch tk {
case reflect.Bool:
b, err := strconv.ParseBool(v)
if err != nil {
return err
}
ef.SetBool(b)
case reflect.Float32, reflect.Float64:
f, err := strconv.ParseFloat(v, tf.Bits())
if err != nil {
return err
}
ef.SetFloat(f)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
i, err := strconv.ParseInt(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetInt(i)
case reflect.Int64:
// Special case time.Duration values.
if tf.PkgPath() == "time" && tf.Name() == "Duration" {
d, err := time.ParseDuration(v)
if err != nil {
return err
}
ef.SetInt(int64(d))
} else {
i, err := strconv.ParseInt(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetInt(i)
}
case reflect.String:
ef.SetString(v)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, err := strconv.ParseUint(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetUint(i)
case reflect.Interface:
return fmt.Errorf("cannot decode into interfaces")
// Maps
case reflect.Map:
vals := strings.Split(v, delimiter)
mp := reflect.MakeMapWithSize(tf, len(vals))
for _, val := range vals {
pair := strings.SplitN(val, separator, 2)
if len(pair) < 2 {
return fmt.Errorf("%s: %w", val, ErrInvalidMapItem)
}
mKey, mVal := strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1])
k := reflect.New(tf.Key()).Elem()
if err := processField(mKey, k, delimiter, separator, noInit); err != nil {
return fmt.Errorf("%s: %w", mKey, err)
}
v := reflect.New(tf.Elem()).Elem()
if err := processField(mVal, v, delimiter, separator, noInit); err != nil {
return fmt.Errorf("%s: %w", mVal, err)
}
mp.SetMapIndex(k, v)
}
ef.Set(mp)
// Slices
case reflect.Slice:
// Special case: []byte
if tf.Elem().Kind() == reflect.Uint8 {
ef.Set(reflect.ValueOf([]byte(v)))
} else {
vals := strings.Split(v, delimiter)
s := reflect.MakeSlice(tf, len(vals), len(vals))
for i, val := range vals {
val = strings.TrimSpace(val)
if err := processField(val, s.Index(i), delimiter, separator, noInit); err != nil {
return fmt.Errorf("%s: %w", val, err)
}
}
ef.Set(s)
}
}
return nil
}
// validateEnvName validates the given string conforms to being a valid
// environment variable.
//
// Per IEEE Std 1003.1-2001 environment variables consist solely of uppercase
// letters, digits, and _, and do not begin with a digit.
func validateEnvName(s string) bool {
if s == "" {
return false
}
for i, r := range s {
if (i == 0 && !isLetter(r) && r != '_') || (!isLetter(r) && !isNumber(r) && r != '_') {
return false
}
}
return true
}
// isLetter returns true if the given rune is a letter between a-z,A-Z. This is
// different than unicode.IsLetter which includes all L character cases.
func isLetter(r rune) bool {
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
}
// isNumber returns true if the given run is a number between 0-9. This is
// different than unicode.IsNumber in that it only allows 0-9.
func isNumber(r rune) bool {
return r >= '0' && r <= '9'
}

76
vendor/github.com/sethvargo/go-envconfig/mutator.go generated vendored Normal file
View file

@ -0,0 +1,76 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envconfig
import "context"
// Mutator is the interface for a mutator function. Mutators act like middleware
// and alter values for subsequent processing. This is useful if you want to
// mutate the environment variable value before it's converted to the proper
// type.
//
// Mutators are only called on defined values (or when decodeunset is true).
type Mutator interface {
// EnvMutate is called to alter the environment variable value.
//
// - `originalKey` is the unmodified environment variable name as it was defined
// on the struct.
//
// - `resolvedKey` is the fully-resolved environment variable name, which may
// include prefixes or modifications from processing. When there are
// no modifications, this will be equivalent to `originalKey`.
//
// - `originalValue` is the unmodified environment variable's value before any
// mutations were run.
//
// - `currentValue` is the currently-resolved value, which may have been
// modified by previous mutators and may be modified in the future by
// subsequent mutators in the stack.
//
// The function returns (in order):
//
// - The new value to use in both future mutations and final processing.
//
// - A boolean which indicates whether future mutations in the stack should be
// applied.
//
// - Any errors that occurred.
//
EnvMutate(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error)
}
var _ Mutator = (MutatorFunc)(nil)
// MutatorFunc implements the [Mutator] and provides a quick way to create an
// anonymous function.
type MutatorFunc func(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error)
// EnvMutate implements [Mutator].
func (m MutatorFunc) EnvMutate(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error) {
return m(ctx, originalKey, resolvedKey, originalValue, currentValue)
}
// LegacyMutatorFunc is a helper that eases the transition from the previous
// MutatorFunc signature. It wraps the previous-style mutator function and
// returns a new one. Since the former mutator function had less data, this is
// inherently lossy.
//
// Deprecated: Use [MutatorFunc] instead.
func LegacyMutatorFunc(fn func(ctx context.Context, key, value string) (string, error)) MutatorFunc {
return func(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error) {
v, err := fn(ctx, originalKey, currentValue)
return v, true, err
}
}

4
vendor/modules.txt vendored
View file

@ -139,6 +139,10 @@ github.com/remyoudompheng/bigfft
github.com/rs/zerolog
github.com/rs/zerolog/internal/cbor
github.com/rs/zerolog/internal/json
github.com/rs/zerolog/log
# github.com/sethvargo/go-envconfig v1.1.1
## explicit; go 1.20
github.com/sethvargo/go-envconfig
# github.com/stretchr/testify v1.9.0
## explicit; go 1.17
github.com/stretchr/testify/assert