From 5ea6fe0c4b9d0d2dfe50773b35fc846d969f1946 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 30 Sep 2023 20:10:54 +0600 Subject: [PATCH] :pushpin: Vendored go packages --- vendor/github.com/caarlos0/env/v9/.gitignore | 4 + .../github.com/caarlos0/env/v9/.golangci.yml | 8 + .../caarlos0/env/v9/.goreleaser.yml | 3 + vendor/github.com/caarlos0/env/v9/.mailmap | 7 + vendor/github.com/caarlos0/env/v9/LICENSE.md | 21 + vendor/github.com/caarlos0/env/v9/Makefile | 37 ++ vendor/github.com/caarlos0/env/v9/README.md | 577 ++++++++++++++++++ vendor/github.com/caarlos0/env/v9/env.go | 520 ++++++++++++++++ .../github.com/caarlos0/env/v9/env_tomap.go | 16 + .../caarlos0/env/v9/env_tomap_windows.go | 29 + vendor/github.com/caarlos0/env/v9/error.go | 164 +++++ vendor/modules.txt | 3 + 12 files changed, 1389 insertions(+) create mode 100644 vendor/github.com/caarlos0/env/v9/.gitignore create mode 100644 vendor/github.com/caarlos0/env/v9/.golangci.yml create mode 100644 vendor/github.com/caarlos0/env/v9/.goreleaser.yml create mode 100644 vendor/github.com/caarlos0/env/v9/.mailmap create mode 100644 vendor/github.com/caarlos0/env/v9/LICENSE.md create mode 100644 vendor/github.com/caarlos0/env/v9/Makefile create mode 100644 vendor/github.com/caarlos0/env/v9/README.md create mode 100644 vendor/github.com/caarlos0/env/v9/env.go create mode 100644 vendor/github.com/caarlos0/env/v9/env_tomap.go create mode 100644 vendor/github.com/caarlos0/env/v9/env_tomap_windows.go create mode 100644 vendor/github.com/caarlos0/env/v9/error.go diff --git a/vendor/github.com/caarlos0/env/v9/.gitignore b/vendor/github.com/caarlos0/env/v9/.gitignore new file mode 100644 index 0000000..ca6a0ff --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/.gitignore @@ -0,0 +1,4 @@ +coverage.txt +bin +card.png +dist diff --git a/vendor/github.com/caarlos0/env/v9/.golangci.yml b/vendor/github.com/caarlos0/env/v9/.golangci.yml new file mode 100644 index 0000000..ff791f8 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/.golangci.yml @@ -0,0 +1,8 @@ +linters: + enable: + - thelper + - gofumpt + - tparallel + - unconvert + - unparam + - wastedassign diff --git a/vendor/github.com/caarlos0/env/v9/.goreleaser.yml b/vendor/github.com/caarlos0/env/v9/.goreleaser.yml new file mode 100644 index 0000000..4688983 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/.goreleaser.yml @@ -0,0 +1,3 @@ +includes: + - from_url: + url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml diff --git a/vendor/github.com/caarlos0/env/v9/.mailmap b/vendor/github.com/caarlos0/env/v9/.mailmap new file mode 100644 index 0000000..eeeee60 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/.mailmap @@ -0,0 +1,7 @@ +Carlos Alexandro Becker Carlos A Becker +Carlos Alexandro Becker Carlos A Becker +Carlos Alexandro Becker Carlos Alexandro Becker +Carlos Alexandro Becker Carlos Alexandro Becker +Carlos Alexandro Becker Carlos Becker +dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> +actions-user github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> diff --git a/vendor/github.com/caarlos0/env/v9/LICENSE.md b/vendor/github.com/caarlos0/env/v9/LICENSE.md new file mode 100644 index 0000000..3a59b6b --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 Carlos Alexandro Becker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/caarlos0/env/v9/Makefile b/vendor/github.com/caarlos0/env/v9/Makefile new file mode 100644 index 0000000..da8595f --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/Makefile @@ -0,0 +1,37 @@ +SOURCE_FILES?=./... +TEST_PATTERN?=. + +export GO111MODULE := on + +setup: + go mod tidy +.PHONY: setup + +build: + go build +.PHONY: build + +test: + go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m +.PHONY: test + +cover: test + go tool cover -html=coverage.txt +.PHONY: cover + +fmt: + gofumpt -w -l . +.PHONY: fmt + +lint: + golangci-lint run ./... +.PHONY: lint + +ci: build test +.PHONY: ci + +card: + wget -O card.png -c "https://og.caarlos0.dev/**env**: parse envs to structs.png?theme=light&md=1&fontSize=100px&images=https://github.com/caarlos0.png" +.PHONY: card + +.DEFAULT_GOAL := ci diff --git a/vendor/github.com/caarlos0/env/v9/README.md b/vendor/github.com/caarlos0/env/v9/README.md new file mode 100644 index 0000000..9e4cd7a --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/README.md @@ -0,0 +1,577 @@ +# env + +[![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build) +[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env) +[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v9) + +A simple and zero-dependencies library to parse environment variables into +`struct`s. + +## Example + +Get the module with: + +```sh +go get github.com/caarlos0/env/v9 +``` + +The usage looks like this: + +```go +package main + +import ( + "fmt" + "time" + + "github.com/caarlos0/env/v9" +) + +type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + Password string `env:"PASSWORD,unset"` + IsProduction bool `env:"PRODUCTION"` + Hosts []string `env:"HOSTS" envSeparator:":"` + Duration time.Duration `env:"DURATION"` + TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/tmp"` +} + +func main() { + cfg := config{} + if err := env.Parse(&cfg); err != nil { + fmt.Printf("%+v\n", err) + } + + fmt.Printf("%+v\n", cfg) +} +``` + +You can run it like this: + +```sh +$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go +{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s} +``` + +## Caveats + +> **Warning** +> +> **This is important!** + +- _Unexported fields_ are **ignored** + +## Supported types and defaults + +Out of the box all built-in types are supported, plus a few others that +are commonly used. + +Complete list: + +- `string` +- `bool` +- `int` +- `int8` +- `int16` +- `int32` +- `int64` +- `uint` +- `uint8` +- `uint16` +- `uint32` +- `uint64` +- `float32` +- `float64` +- `time.Duration` +- `encoding.TextUnmarshaler` +- `url.URL` + +Pointers, slices and slices of pointers, and maps of those types are also +supported. + +You can also use/define a [custom parser func](#custom-parser-funcs) for any +other type you want. + +You can also use custom keys and values in your maps, as long as you provide a +parser function for them. + +If you set the `envDefault` tag for something, this value will be used in the +case of absence of it in the environment. + +By default, slice types will split the environment value on `,`; you can change +this behavior by setting the `envSeparator` tag. + +## Custom Parser Funcs + +If you have a type that is not supported out of the box by the lib, you are able +to use (or define) and pass custom parsers (and their associated `reflect.Type`) +to the `env.ParseWithOptions()` function. + +In addition to accepting a struct pointer (same as `Parse()`), this function +also accepts a `Options{}`, and you can set your custom parsers in the `FuncMap` +field. + +If you add a custom parser for, say `Foo`, it will also be used to parse +`*Foo` and `[]Foo` types. + +Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v9) +for more info. + +### A note about `TextUnmarshaler` and `time.Time` + +Env supports by default anything that implements the `TextUnmarshaler` interface. +That includes things like `time.Time` for example. +The upside is that depending on the format you need, you don't need to change +anything. +The downside is that if you do need time in another format, you'll need to +create your own type. + +Its fairly straightforward: + +```go +type MyTime time.Time + +func (t *MyTime) UnmarshalText(text []byte) error { + tt, err := time.Parse("2006-01-02", string(text)) + *t = MyTime(tt) + return err +} + +type Config struct { + SomeTime MyTime `env:"SOME_TIME"` +} +``` + +And then you can parse `Config` with `env.Parse`. + +## Required fields + +The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to +ensure that some environment variable is set. In the example above, an error is +returned if the `config` struct is changed to: + +```go +type config struct { + SecretKey string `env:"SECRET_KEY,required"` +} +``` + +> **Warning** +> +> Note that being set is not the same as being empty. +> If the variable is set, but empty, the field will have its type's default +> value. +> This also means that custom parser funcs will not be invoked. + +## Expand vars + +If you set the `expand` option, environment variables (either in `${var}` or +`$var` format) in the string will be replaced according with the actual value +of the variable. For example: + +```go +type config struct { + SecretKey string `env:"SECRET_KEY,expand"` +} +``` + +This also works with `envDefault`. + +## Not Empty fields + +While `required` demands the environment variable to be set, it doesn't check +its value. If you want to make sure the environment is set and not empty, you +need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`). + +Example: + +```go +type config struct { + SecretKey string `env:"SECRET_KEY,notEmpty"` +} +``` + +## Unset environment variable after reading it + +The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added +to ensure that some environment variable is unset after reading it. + +Example: + +```go +type config struct { + SecretKey string `env:"SECRET_KEY,unset"` +} +``` + +## From file + +The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added +in order to indicate that the value of the variable shall be loaded from a +file. +The path of that file is given by the environment variable associated with it: + +```go +package main + +import ( + "fmt" + "time" + "github.com/caarlos0/env/v9" +) + +type config struct { + Secret string `env:"SECRET,file"` + Password string `env:"PASSWORD,file" envDefault:"/tmp/password"` + Certificate string `env:"CERTIFICATE,file,expand" envDefault:"${CERTIFICATE_FILE}"` +} + +func main() { + cfg := config{} + if err := env.Parse(&cfg); err != nil { + fmt.Printf("%+v\n", err) + } + + fmt.Printf("%+v\n", cfg) +} +``` + +```sh +$ echo qwerty > /tmp/secret +$ echo dvorak > /tmp/password +$ echo coleman > /tmp/certificate + +$ SECRET=/tmp/secret \ + CERTIFICATE_FILE=/tmp/certificate \ + go run main.go +{Secret:qwerty Password:dvorak Certificate:coleman} +``` + +## Options + +### Use field names as environment variables by default + +If you don't want to set the `env` tag on every field, you can use the +`UseFieldNameByDefault` option. + +It will use the field name as environment variable name. + +Here's an example: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Username string // will use $USERNAME + Password string // will use $PASSWORD + UserFullName string // will use $USER_FULL_NAME +} + +func main() { + cfg := &Config{} + opts := env.Options{UseFieldNameByDefault: true} + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +### Environment + +By setting the `Options.Environment` map you can tell `Parse` to add those +`keys` and `values` as `env` vars before parsing is done. +These `envs` are stored in the map and never actually set by `os.Setenv`. +This option effectively makes `env` ignore the OS environment variables: only +the ones provided in the option are used. + +This can make your testing scenarios a bit more clean and easy to handle. + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Password string `env:"PASSWORD"` +} + +func main() { + cfg := &Config{} + opts := env.Options{Environment: map[string]string{ + "PASSWORD": "MY_PASSWORD", + }} + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +### Changing default tag name + +You can change what tag name to use for setting the env vars by setting the +`Options.TagName` variable. + +For example + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Password string `json:"PASSWORD"` +} + +func main() { + cfg := &Config{} + opts := env.Options{TagName: "json"} + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +### Prefixes + +You can prefix sub-structs env tags, as well as a whole `env.Parse` call. + +Here's an example flexing it a bit: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Home string `env:"HOME"` +} + +type ComplexConfig struct { + Foo Config `envPrefix:"FOO_"` + Clean Config + Bar Config `envPrefix:"BAR_"` + Blah string `env:"BLAH"` +} + +func main() { + cfg := &ComplexConfig{} + opts := env.Options{ + Prefix: "T_", + Environment: map[string]string{ + "T_FOO_HOME": "/foo", + "T_BAR_HOME": "/bar", + "T_BLAH": "blahhh", + "T_HOME": "/clean", + }, + } + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +### On set hooks + +You might want to listen to value sets and, for example, log something or do +some other kind of logic. +You can do this by passing a `OnSet` option: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Username string `env:"USERNAME" envDefault:"admin"` + Password string `env:"PASSWORD"` +} + +func main() { + cfg := &Config{} + opts := env.Options{ + OnSet: func(tag string, value interface{}, isDefault bool) { + fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault) + }, + } + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +## Making all fields to required + +You can make all fields that don't have a default value be required by setting +the `RequiredIfNoDef: true` in the `Options`. + +For example + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Username string `env:"USERNAME" envDefault:"admin"` + Password string `env:"PASSWORD"` +} + +func main() { + cfg := &Config{} + opts := env.Options{RequiredIfNoDef: true} + + // Load env vars. + if err := env.ParseWithOptions(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg) +} +``` + +## Defaults from code + +You may define default value also in code, by initialising the config data +before it's filled by `env.Parse`. +Default values defined as struct tags will overwrite existing values during +Parse. + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Username string `env:"USERNAME" envDefault:"admin"` + Password string `env:"PASSWORD"` +} + +func main() { + var cfg = Config{ + Username: "test", + Password: "123456", + } + + if err := env.Parse(&cfg); err != nil { + fmt.Println("failed:", err) + } + + fmt.Printf("%+v", cfg) // {Username:admin Password:123456} +} +``` + +## Error handling + +You can handle the errors the library throws like so: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + Username string `env:"USERNAME" envDefault:"admin"` + Password string `env:"PASSWORD"` +} + +func main() { + var cfg Config + err := env.Parse(&cfg) + if e, ok := err.(*env.AggregateError); ok { + for _, er := range e.Errors { + switch v := er.(type) { + case env.ParseError: + // handle it + case env.NotStructPtrError: + // handle it + case env.NoParserError: + // handle it + case env.NoSupportedTagOptionError: + // handle it + default: + fmt.Printf("Unknown error type %v", v) + } + } + } + + fmt.Printf("%+v", cfg) // {Username:admin Password:123456} +} +``` + +> **Info** +> +> If you want to check if an specific error is in the chain, you can also use +> `errors.Is()`. + +## Stargazers over time + +[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env) diff --git a/vendor/github.com/caarlos0/env/v9/env.go b/vendor/github.com/caarlos0/env/v9/env.go new file mode 100644 index 0000000..be8ee53 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/env.go @@ -0,0 +1,520 @@ +package env + +import ( + "encoding" + "fmt" + "net/url" + "os" + "reflect" + "strconv" + "strings" + "time" + "unicode" +) + +// nolint: gochecknoglobals +var ( + defaultBuiltInParsers = map[reflect.Kind]ParserFunc{ + reflect.Bool: func(v string) (interface{}, error) { + return strconv.ParseBool(v) + }, + reflect.String: func(v string) (interface{}, error) { + return v, nil + }, + reflect.Int: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 32) + return int(i), err + }, + reflect.Int16: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 16) + return int16(i), err + }, + reflect.Int32: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 32) + return int32(i), err + }, + reflect.Int64: func(v string) (interface{}, error) { + return strconv.ParseInt(v, 10, 64) + }, + reflect.Int8: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 8) + return int8(i), err + }, + reflect.Uint: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 32) + return uint(i), err + }, + reflect.Uint16: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 16) + return uint16(i), err + }, + reflect.Uint32: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 32) + return uint32(i), err + }, + reflect.Uint64: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 64) + return i, err + }, + reflect.Uint8: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 8) + return uint8(i), err + }, + reflect.Float64: func(v string) (interface{}, error) { + return strconv.ParseFloat(v, 64) + }, + reflect.Float32: func(v string) (interface{}, error) { + f, err := strconv.ParseFloat(v, 32) + return float32(f), err + }, + } +) + +func defaultTypeParsers() map[reflect.Type]ParserFunc { + return map[reflect.Type]ParserFunc{ + reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) { + u, err := url.Parse(v) + if err != nil { + return nil, newParseValueError("unable to parse URL", err) + } + return *u, nil + }, + reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) { + s, err := time.ParseDuration(v) + if err != nil { + return nil, newParseValueError("unable to parse duration", err) + } + return s, err + }, + } +} + +// ParserFunc defines the signature of a function that can be used within `CustomParsers`. +type ParserFunc func(v string) (interface{}, error) + +// OnSetFn is a hook that can be run when a value is set. +type OnSetFn func(tag string, value interface{}, isDefault bool) + +// Options for the parser. +type Options struct { + // Environment keys and values that will be accessible for the service. + Environment map[string]string + + // TagName specifies another tagname to use rather than the default env. + TagName string + + // RequiredIfNoDef automatically sets all env as required if they do not + // declare 'envDefault'. + RequiredIfNoDef bool + + // OnSet allows to run a function when a value is set. + OnSet OnSetFn + + // Prefix define a prefix for each key. + Prefix string + + // UseFieldNameByDefault defines whether or not env should use the field + // name by default if the `env` key is missing. + UseFieldNameByDefault bool + + // Custom parse functions for different types. + FuncMap map[reflect.Type]ParserFunc +} + +func defaultOptions() Options { + return Options{ + TagName: "env", + Environment: toMap(os.Environ()), + FuncMap: defaultTypeParsers(), + } +} + +func customOptions(opt Options) Options { + defOpts := defaultOptions() + if opt.TagName == "" { + opt.TagName = defOpts.TagName + } + if opt.Environment == nil { + opt.Environment = defOpts.Environment + } + if opt.FuncMap == nil { + opt.FuncMap = map[reflect.Type]ParserFunc{} + } + for k, v := range defOpts.FuncMap { + opt.FuncMap[k] = v + } + return opt +} + +func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options { + return Options{ + Environment: opts.Environment, + TagName: opts.TagName, + RequiredIfNoDef: opts.RequiredIfNoDef, + OnSet: opts.OnSet, + Prefix: opts.Prefix + field.Tag.Get("envPrefix"), + UseFieldNameByDefault: opts.UseFieldNameByDefault, + FuncMap: opts.FuncMap, + } +} + +// Parse parses a struct containing `env` tags and loads its values from +// environment variables. +func Parse(v interface{}) error { + return parseInternal(v, defaultOptions()) +} + +// Parse parses a struct containing `env` tags and loads its values from +// environment variables. +func ParseWithOptions(v interface{}, opts Options) error { + return parseInternal(v, customOptions(opts)) +} + +func parseInternal(v interface{}, opts Options) error { + ptrRef := reflect.ValueOf(v) + if ptrRef.Kind() != reflect.Ptr { + return newAggregateError(NotStructPtrError{}) + } + ref := ptrRef.Elem() + if ref.Kind() != reflect.Struct { + return newAggregateError(NotStructPtrError{}) + } + return doParse(ref, opts) +} + +func doParse(ref reflect.Value, opts Options) error { + refType := ref.Type() + + var agrErr AggregateError + + for i := 0; i < refType.NumField(); i++ { + refField := ref.Field(i) + refTypeField := refType.Field(i) + + if err := doParseField(refField, refTypeField, opts); err != nil { + if val, ok := err.(AggregateError); ok { + agrErr.Errors = append(agrErr.Errors, val.Errors...) + } else { + agrErr.Errors = append(agrErr.Errors, err) + } + } + } + + if len(agrErr.Errors) == 0 { + return nil + } + + return agrErr +} + +func doParseField(refField reflect.Value, refTypeField reflect.StructField, opts Options) error { + if !refField.CanSet() { + return nil + } + if reflect.Ptr == refField.Kind() && !refField.IsNil() { + return parseInternal(refField.Interface(), optionsWithEnvPrefix(refTypeField, opts)) + } + if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" { + return parseInternal(refField.Addr().Interface(), optionsWithEnvPrefix(refTypeField, opts)) + } + value, err := get(refTypeField, opts) + if err != nil { + return err + } + + if value != "" { + return set(refField, refTypeField, value, opts.FuncMap) + } + + if reflect.Struct == refField.Kind() { + return doParse(refField, optionsWithEnvPrefix(refTypeField, opts)) + } + + return nil +} + +const underscore rune = '_' + +func toEnvName(input string) string { + var output []rune + for i, c := range input { + if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c { + output = append(output, underscore) + } + output = append(output, unicode.ToUpper(c)) + } + return string(output) +} + +func get(field reflect.StructField, opts Options) (val string, err error) { + var exists bool + var isDefault bool + var loadFile bool + var unset bool + var notEmpty bool + var expand bool + + required := opts.RequiredIfNoDef + ownKey, tags := parseKeyForOption(field.Tag.Get(opts.TagName)) + if ownKey == "" && opts.UseFieldNameByDefault { + ownKey = toEnvName(field.Name) + } + + for _, tag := range tags { + switch tag { + case "": + continue + case "file": + loadFile = true + case "required": + required = true + case "unset": + unset = true + case "notEmpty": + notEmpty = true + case "expand": + expand = true + default: + return "", newNoSupportedTagOptionError(tag) + } + } + + prefix := opts.Prefix + key := prefix + ownKey + defaultValue, defExists := field.Tag.Lookup("envDefault") + val, exists, isDefault = getOr(key, defaultValue, defExists, opts.Environment) + + if expand { + val = os.ExpandEnv(val) + } + + if unset { + defer os.Unsetenv(key) + } + + if required && !exists && len(ownKey) > 0 { + return "", newEnvVarIsNotSet(key) + } + + if notEmpty && val == "" { + return "", newEmptyEnvVarError(key) + } + + if loadFile && val != "" { + filename := val + val, err = getFromFile(filename) + if err != nil { + return "", newLoadFileContentError(filename, key, err) + } + } + + if opts.OnSet != nil { + if ownKey != "" { + opts.OnSet(key, val, isDefault) + } + } + return val, err +} + +// split the env tag's key into the expected key and desired option, if any. +func parseKeyForOption(key string) (string, []string) { + opts := strings.Split(key, ",") + return opts[0], opts[1:] +} + +func getFromFile(filename string) (value string, err error) { + b, err := os.ReadFile(filename) + return string(b), err +} + +func getOr(key, defaultValue string, defExists bool, envs map[string]string) (string, bool, bool) { + value, exists := envs[key] + switch { + case (!exists || key == "") && defExists: + return defaultValue, true, true + case exists && value == "" && defExists: + return defaultValue, true, true + case !exists: + return "", false, false + } + + return value, true, false +} + +func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error { + if tm := asTextUnmarshaler(field); tm != nil { + if err := tm.UnmarshalText([]byte(value)); err != nil { + return newParseError(sf, err) + } + return nil + } + + typee := sf.Type + fieldee := field + if typee.Kind() == reflect.Ptr { + typee = typee.Elem() + fieldee = field.Elem() + } + + parserFunc, ok := funcMap[typee] + if ok { + val, err := parserFunc(value) + if err != nil { + return newParseError(sf, err) + } + + fieldee.Set(reflect.ValueOf(val)) + return nil + } + + parserFunc, ok = defaultBuiltInParsers[typee.Kind()] + if ok { + val, err := parserFunc(value) + if err != nil { + return newParseError(sf, err) + } + + fieldee.Set(reflect.ValueOf(val).Convert(typee)) + return nil + } + + switch field.Kind() { + case reflect.Slice: + return handleSlice(field, value, sf, funcMap) + case reflect.Map: + return handleMap(field, value, sf, funcMap) + } + + return newNoParserError(sf) +} + +func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error { + separator := sf.Tag.Get("envSeparator") + if separator == "" { + separator = "," + } + parts := strings.Split(value, separator) + + typee := sf.Type.Elem() + if typee.Kind() == reflect.Ptr { + typee = typee.Elem() + } + + if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok { + return parseTextUnmarshalers(field, parts, sf) + } + + parserFunc, ok := funcMap[typee] + if !ok { + parserFunc, ok = defaultBuiltInParsers[typee.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + result := reflect.MakeSlice(sf.Type, 0, len(parts)) + for _, part := range parts { + r, err := parserFunc(part) + if err != nil { + return newParseError(sf, err) + } + v := reflect.ValueOf(r).Convert(typee) + if sf.Type.Elem().Kind() == reflect.Ptr { + v = reflect.New(typee) + v.Elem().Set(reflect.ValueOf(r).Convert(typee)) + } + result = reflect.Append(result, v) + } + field.Set(result) + return nil +} + +func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error { + keyType := sf.Type.Key() + keyParserFunc, ok := funcMap[keyType] + if !ok { + keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + elemType := sf.Type.Elem() + elemParserFunc, ok := funcMap[elemType] + if !ok { + elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + separator := sf.Tag.Get("envSeparator") + if separator == "" { + separator = "," + } + + result := reflect.MakeMap(sf.Type) + for _, part := range strings.Split(value, separator) { + pairs := strings.Split(part, ":") + if len(pairs) != 2 { + return newParseError(sf, fmt.Errorf(`%q should be in "key:value" format`, part)) + } + + key, err := keyParserFunc(pairs[0]) + if err != nil { + return newParseError(sf, err) + } + + elem, err := elemParserFunc(pairs[1]) + if err != nil { + return newParseError(sf, err) + } + + result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType)) + } + + field.Set(result) + return nil +} + +func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler { + if reflect.Ptr == field.Kind() { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + } else if field.CanAddr() { + field = field.Addr() + } + + tm, ok := field.Interface().(encoding.TextUnmarshaler) + if !ok { + return nil + } + return tm +} + +func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error { + s := len(data) + elemType := field.Type().Elem() + slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s) + for i, v := range data { + sv := slice.Index(i) + kind := sv.Kind() + if kind == reflect.Ptr { + sv = reflect.New(elemType.Elem()) + } else { + sv = sv.Addr() + } + tm := sv.Interface().(encoding.TextUnmarshaler) + if err := tm.UnmarshalText([]byte(v)); err != nil { + return newParseError(sf, err) + } + if kind == reflect.Ptr { + slice.Index(i).Set(sv) + } + } + + field.Set(slice) + + return nil +} diff --git a/vendor/github.com/caarlos0/env/v9/env_tomap.go b/vendor/github.com/caarlos0/env/v9/env_tomap.go new file mode 100644 index 0000000..aece2ae --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/env_tomap.go @@ -0,0 +1,16 @@ +//go:build !windows + +package env + +import "strings" + +func toMap(env []string) map[string]string { + r := map[string]string{} + for _, e := range env { + p := strings.SplitN(e, "=", 2) + if len(p) == 2 { + r[p[0]] = p[1] + } + } + return r +} diff --git a/vendor/github.com/caarlos0/env/v9/env_tomap_windows.go b/vendor/github.com/caarlos0/env/v9/env_tomap_windows.go new file mode 100644 index 0000000..04ce66f --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/env_tomap_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package env + +import "strings" + +func toMap(env []string) map[string]string { + r := map[string]string{} + for _, e := range env { + p := strings.SplitN(e, "=", 2) + + // On Windows, environment variables can start with '='. If so, Split at next character. + // See env_windows.go in the Go source: https://github.com/golang/go/blob/master/src/syscall/env_windows.go#L58 + prefixEqualSign := false + if len(e) > 0 && e[0] == '=' { + e = e[1:] + prefixEqualSign = true + } + p = strings.SplitN(e, "=", 2) + if prefixEqualSign { + p[0] = "=" + p[0] + } + + if len(p) == 2 { + r[p[0]] = p[1] + } + } + return r +} diff --git a/vendor/github.com/caarlos0/env/v9/error.go b/vendor/github.com/caarlos0/env/v9/error.go new file mode 100644 index 0000000..156ca3e --- /dev/null +++ b/vendor/github.com/caarlos0/env/v9/error.go @@ -0,0 +1,164 @@ +package env + +import ( + "fmt" + "reflect" + "strings" +) + +// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually +// List of the available errors +// ParseError +// NotStructPtrError +// NoParserError +// NoSupportedTagOptionError +// EnvVarIsNotSetError +// EmptyEnvVarError +// LoadFileContentError +// ParseValueError +type AggregateError struct { + Errors []error +} + +func newAggregateError(initErr error) error { + return AggregateError{ + []error{ + initErr, + }, + } +} + +func (e AggregateError) Error() string { + var sb strings.Builder + + sb.WriteString("env:") + + for _, err := range e.Errors { + sb.WriteString(fmt.Sprintf(" %v;", err.Error())) + } + + return strings.TrimRight(sb.String(), ";") +} + +// Is conforms with errors.Is. +func (e AggregateError) Is(err error) bool { + for _, ie := range e.Errors { + if reflect.TypeOf(ie) == reflect.TypeOf(err) { + return true + } + } + return false +} + +// The error occurs when it's impossible to convert the value for given type. +type ParseError struct { + Name string + Type reflect.Type + Err error +} + +func newParseError(sf reflect.StructField, err error) error { + return ParseError{sf.Name, sf.Type, err} +} + +func (e ParseError) Error() string { + return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err) +} + +// The error occurs when pass something that is not a pointer to a Struct to Parse +type NotStructPtrError struct{} + +func (e NotStructPtrError) Error() string { + return "expected a pointer to a Struct" +} + +// This error occurs when there is no parser provided for given type +// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults +// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs +type NoParserError struct { + Name string + Type reflect.Type +} + +func newNoParserError(sf reflect.StructField) error { + return NoParserError{sf.Name, sf.Type} +} + +func (e NoParserError) Error() string { + return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type) +} + +// This error occurs when the given tag is not supported +// In-built supported tags: "", "file", "required", "unset", "notEmpty", "expand", "envDefault", "envSeparator" +// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name +type NoSupportedTagOptionError struct { + Tag string +} + +func newNoSupportedTagOptionError(tag string) error { + return NoSupportedTagOptionError{tag} +} + +func (e NoSupportedTagOptionError) Error() string { + return fmt.Sprintf("tag option %q not supported", e.Tag) +} + +// This error occurs when the required variable is not set +// Read about required fields: https://github.com/caarlos0/env#required-fields +type EnvVarIsNotSetError struct { + Key string +} + +func newEnvVarIsNotSet(key string) error { + return EnvVarIsNotSetError{key} +} + +func (e EnvVarIsNotSetError) Error() string { + return fmt.Sprintf(`required environment variable %q is not set`, e.Key) +} + +// This error occurs when the variable which must be not empty is existing but has an empty value +// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields +type EmptyEnvVarError struct { + Key string +} + +func newEmptyEnvVarError(key string) error { + return EmptyEnvVarError{key} +} + +func (e EmptyEnvVarError) Error() string { + return fmt.Sprintf("environment variable %q should not be empty", e.Key) +} + +// This error occurs when it's impossible to load the value from the file +// Read about From file feature: https://github.com/caarlos0/env#from-file +type LoadFileContentError struct { + Filename string + Key string + Err error +} + +func newLoadFileContentError(filename, key string, err error) error { + return LoadFileContentError{filename, key, err} +} + +func (e LoadFileContentError) Error() string { + return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err) +} + +// This error occurs when it's impossible to convert value using given parser +// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults +// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs +type ParseValueError struct { + Msg string + Err error +} + +func newParseValueError(message string, err error) error { + return ParseValueError{message, err} +} + +func (e ParseValueError) Error() string { + return fmt.Sprintf("%s: %v", e.Msg, e.Err) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c6a6910..e35c0ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,6 @@ +# github.com/caarlos0/env/v9 v9.0.0 +## explicit; go 1.17 +github.com/caarlos0/env/v9 # github.com/google/go-cmp v0.5.9 ## explicit; go 1.13 github.com/google/go-cmp/cmp