Giter VIP home page Giter VIP logo

aconfig's Introduction

aconfig

build-img pkg-img version-img

Simple, useful and opinionated config loader.

Rationale

There are many solutions regarding configuration loading in Go. I was looking for a simple loader that is as easy to use and understand as possible. The goal was to load config from 4 places: defaults (in the code), files, environment variables, command-line flags. This library works with all of these sources.

Features

  • Simple API.
  • Clean and tested code.
  • Automatic fields mapping.
  • Supports different sources:
    • defaults in the code
    • files (JSON, YAML, TOML, DotENV, HCL)
    • environment variables
    • command-line flags
  • Dependency-free (file parsers are optional).
  • Ability to walk over configuration fields.

Install

Go version 1.14+

go get github.com/cristalhq/aconfig

Example

type MyConfig struct {
	Port int `default:"1111" usage:"just give a number"`
	Auth struct {
		User string `required:"true"`
		Pass string `required:"true"`
	}
	Pass string `default:"" env:"SECRET" flag:"sec_ret"`
}

var cfg MyConfig
loader := aconfig.LoaderFor(&cfg, aconfig.Config{
	// feel free to skip some steps :)
	// SkipDefaults: true,
	// SkipFiles:    true,
	// SkipEnv:      true,
	// SkipFlags:    true,
	EnvPrefix:       "APP",
	FlagPrefix:      "app",
	Files:           []string{"/var/opt/myapp/config.json", "ouch.yaml"},
	FileDecoders: map[string]aconfig.FileDecoder{
		// from `aconfigyaml` submodule
		// see submodules in repo for more formats
		".yaml": aconfigyaml.New(),
	},
})

// IMPORTANT: define your own flags with `flagSet`
flagSet := loader.Flags()

if err := loader.Load(); err != nil {
	panic(err)
}

// configuration fields will be loaded from (in order):
//
// 1. defaults set in structure tags (see MyConfig defenition)
// 2. loaded from files `file.json` if not `ouch.yaml` will be used
// 3. from corresponding environment variables with the prefix `APP_`
// 4. command-line flags with the prefix `app.` if they are

Also see examples: examples_test.go.

Integration with spf13/cobra playground.

Documentation

See these docs.

License

MIT License.

aconfig's People

Contributors

aohoyd avatar asafalima avatar cristaloleg avatar dependabot[bot] avatar emilijuss avatar ericgreene avatar fzambia avatar jakubdyszkiewicz avatar mef13 avatar michaelcurrin avatar rid-lin avatar tetafro avatar tmzane avatar wweir avatar xielongdragon avatar zhezhel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aconfig's Issues

Description of configs

Problem

Having an option to generate default flags is really nice! but it's not that useful if I cannot write help for this. So for example:

❯❯❯ ./samplego --help
Usage:
  -myauth.lease string
    	 (default "3s")
  -myauth.multiple string

  -myauth.mypass string
    	 (default "def-pass")
  -myauth.myuser string
    	 (default "def-user")
  -port string

I want to communicate to a user what are those properties.

Proposal

Ideally, I'd like to put a comment like this

type MyAuth struct {
	// user that will use the server
	MyUser string `yaml:"myUser" default:"def-user"`
}

but I don't think it's technically possible to then use it in the code (unless some generator that will parse it?)

so the other option would be to introduce a tag for it.

type MyAuth struct {
	MyUser string `yaml:"myUser" default:"def-user" description:"user that will user server"`
}

This way I could even reuse it and autogenerate generate configuration for yaml and envvars

More examples and documentation

The library itself is great and powerful, but it's hard to understand all of the options and possibilities without reading the sources.

Please add more readable documentation about its features - tags, flags, integration options, etc.

Add Cobra example

Cobra seems to be a default way to do CLI in Go. The first thing I checked is whether I can integrate it with Cobra while adding some other flags outside of the config.

It turned out it works really well!

type MyConfig struct {
	Port int
}

func main() {
	loader := aconfig.LoaderFor(&MyConfig{}).
		WithEnvPrefix("app").
		WithFiles([]string{"ouch.yaml"}).
		Build()
	args := struct {
		logLevel string
	}{}
	cmd := &cobra.Command{
		Use:   "hello",
		RunE: func(cmd *cobra.Command, _ []string) error {
			var cfg MyConfig
			if err := loader.Load(&cfg); err != nil {
				return err
			}

			cmd.Printf("Hello World!")
			return nil
		},
	}
	cmd.PersistentFlags().AddGoFlagSet(loader.Flags())
	cmd.PersistentFlags().StringVar(&args.logLevel, "log-level", "info", "log level: debug, info, warning")

	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
}

I would create a PR, but I'm not sure how (or if 😄) would you like to add it, I don't want to put Cobra in go.mod and this example might be too big for README.md.

Only pre-set file extensions are supported

This does not work:

aconfig.LoaderFor(dest, aconfig.Config{
	Files:            files,
	FileDecoders: map[string]aconfig.FileDecoder{
		".yaml": aconfigyaml.New(),
		".yml":  aconfigyaml.New(),
	},
})

.yml files cannot be loaded and fail with cannot load config: unknown field in file error, because the struct tags are only auto-generated for the pre-set formats. The file format is then recognized via filepath.Ext, which is yml and not yaml.

IMHO the "format" should be returned by the file decoder, maybe as another interface method. I can try to prepare a PR if you'd like to go that way.

EDIT: example of how it might look like without breaking backwards compatibility: avast@fbab766

Add HCL file support

It would be great to have support for HCL. If there are no objections, I will submit a PR.

Затруднение при запуске тестов из-под VSCode

Добрый день. Очень нравиться этот пакет, но возникли сложности.
При попытке использовать библиотеку при запуске тестов выходит ошибка о неизвестном(not defined) флаге.
запуск осуществляется через VSCode контекстным пунктом или "run test" или "debug test"
Вывод:

Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestAliasType_send$ package

flag provided but not defined: -test.testlogfile
Usage:

Далее идет вывод использования программы как будто указан неверный флаг или указан "-help"
И возвращается ошибка с текстом:
flag provided but not defined: -test.testlogfile
параметр в конфиге AllowUnknownFlags: true, стоит,
версия github.com/cristalhq/aconfig v0.14.1
Я что-то делаю не так?

No support for native map syntax in files

Given the following toy.json:

{
  "options": {
    "foo": 0.4,
    "bar": 0.25
  }
}

and the following program:

package main

import (
	"fmt"

	"github.com/cristalhq/aconfig"
)

func main() {
	var cfg struct {
		Options map[string]float64
	}
	loader := aconfig.LoaderFor(&cfg, aconfig.Config{
		Files: []string{"toy.json"},
	})
	err := loader.Load()
	fmt.Printf("%v\n%+v\n", err, cfg)
}

I expect the program to print:

<nil>
{Options:map[bar:0.25 foo:0.4]}

But instead, it prints:

aconfig: cannot load config: loading files: incorrect map value "0.25 foo:0.4]": strconv.ParseFloat: parsing "0.25 foo:0.4]": invalid syntax
{Options:map[]}

The desired output is achieved if I replace toy.json with:

{
  "options": "foo:0.4,bar:0.25"
}

It would be much more convenient to be able to specify maps with the config language’s native map syntax.

Deprecations

It's not common to deprecate fields to remove them in the next release or move them to some other place. It would be nice to have support for it in aconfig. Maybe something like

type MyConfig struct {
	Hostname int `default:"xyz.internal" usage:"hostname" deprecated:"this will be gone in the next release, the internet is a dangerous place, localhost will be a default"`
}

and have a function to collect all deprecations if the value is set.

Panic on map of slices

Given the following code:

var cfg struct {
	Params url.Values
}
os.Setenv("PARAMS", "foo:bar")
err := aconfig.LoaderFor(&cfg, aconfig.Config{}).Load()

I would expect:

  • either cfg.Params = url.Values{"foo": {"bar"}}
  • or err != nil

Instead, the program panics with the following traceback:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x48 pc=0x4e0fe7]

goroutine 1 [running]:
github.com/cristalhq/aconfig.(*Loader).setFieldData(0xc0000a2f70, 0xc0000ca160, 0x4f38c0, 0xc00009e710, 0x0, 0x0)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:178 +0x847
github.com/cristalhq/aconfig.(*Loader).setMap(0xc0000a2f70, 0xc0000ca000, 0xc0000b60b0, 0x7, 0x7, 0xc0000b607a)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:304 +0x439
github.com/cristalhq/aconfig.(*Loader).setFieldData(0xc0000a2f70, 0xc0000ca000, 0x4f38c0, 0xc00009e6b0, 0xc0000cc098, 0x539b01)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:199 +0xbfe
github.com/cristalhq/aconfig.(*Loader).setField(0xc0000a2f70, 0xc0000ca000, 0xc0000b607a, 0x6, 0xc0000a0240, 0xc00009ace0, 0xc0000b607a, 0x6)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:433 +0x97
github.com/cristalhq/aconfig.(*Loader).loadEnvironment(0xc0000a2f70, 0x0, 0x0)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:364 +0x165
github.com/cristalhq/aconfig.(*Loader).loadSources(0xc0000a2f70, 0xc00009e1f0, 0x0)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:230 +0xf0
github.com/cristalhq/aconfig.(*Loader).loadConfig(0xc0000a2f70, 0x0, 0xc0000ba020)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:201 +0x4e
github.com/cristalhq/aconfig.(*Loader).Load(0xc0000a2f70, 0xc0000ba018, 0x0)
	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:191 +0x2f
main.main()
	/home/vasiliy/tmp/toy/toy.go:16 +0xc9
exit status 2

If the panic is intended behavior, it should be documented.

Does aconfig support short and long versions of fields?

I'm trying to find out if it's possible that aconfig is able to parse a field that has 2 different uses, a short version (string) and a long version (struct) which provides more detailed version of the overall use.

For example

  dependencies:
    - name: example-plugin
      url: github://someuser/somerepo
      branch: master
      release: v1.0.0

    - name: plugin-repo-without-package-file
      url: gitlab://someuser/somerepo
      branch: dev
      release:
        name: my_plugin_{{.OS}}
        tag: v2.1.3
        type: archive
        path: plugin/plugin

Would I be able to do this, where I can parse it as an interface and then handle extracting it based on it's type?

Panic on parsing YAML configuration with empty lists

Hi,

I encounter panic when a list in the YAML configuration is empty:

package config_test

import (
	"testing"

	"github.com/cristalhq/aconfig"
	"github.com/cristalhq/aconfig/aconfigyaml"
)

type (
	ResourcesConfiguration struct {
		ResourcesA []ResourceA `yaml:"resources_a"`
		ResourcesB []ResourceB `yaml:"resources_b"`
	}

	ResourceA struct {
		Field string `yaml:"field"`
	}

	ResourceB struct {
		Field int `yaml:"field"`
	}
)

func TestLoadResources(t *testing.T) {
	resourcesConfiguration := new(ResourcesConfiguration)

	resourcesLoader := aconfig.LoaderFor(resourcesConfiguration,
		aconfig.Config{
			SkipFlags:          true,
			Files:              []string{"asset_resources.yaml"},
			FailOnFileNotFound: true,
			FileDecoders: map[string]aconfig.FileDecoder{
				".yaml": aconfigyaml.New(),
			},
		})
	if err := resourcesLoader.Load(); err != nil {
		t.Errorf("failed to load resources configurations [err=%s]", err)
	}

	t.Logf("%v", *resourcesConfiguration)
}

Test file: asset_resources.yaml

resources_a:

resources_b:

Panic:

=== RUN   TestLoadResources
--- FAIL: TestLoadResources (0.00s)
panic: <nil> <nil> [recovered]
	panic: <nil> <nil>

goroutine 20 [running]:
testing.tRunner.func1.2({0x1147b60, 0xc00009e680})
	/usr/local/go/src/testing/testing.go:1209 +0x24e
testing.tRunner.func1()
	/usr/local/go/src/testing/testing.go:1212 +0x218
panic({0x1147b60, 0xc00009e680})
	/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/cristalhq/aconfig.(*Loader).setFieldData(0x11496c0, 0xc0000ce210, {0x0, 0x0})
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:187 +0x9a5
github.com/cristalhq/aconfig.(*Loader).loadFile(0xc000102000, {0x116b3c9, 0x14})
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:330 +0x2f1
github.com/cristalhq/aconfig.(*Loader).loadFiles(0xc000102000)
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:290 +0xea
github.com/cristalhq/aconfig.(*Loader).loadSources(0xc000102000)
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:235 +0x7f
github.com/cristalhq/aconfig.(*Loader).loadConfig(0xc000102000)
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:211 +0x65
github.com/cristalhq/aconfig.(*Loader).Load(0x1000000)
	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:201 +0x76
github.com/ic2hrmk/project/subdir/test/config_test.TestLoadResources(0xc000083860)
	/Users/user/go/src/github.com/ic2hrmk/project/subdir/test/config/load_resources_test.go:37 +0x176
testing.tRunner(0xc000083860, 0x11741d0)
	/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
	/usr/local/go/src/testing/testing.go:1306 +0x35a

Expected:

=== RUN   TestLoadResources
    load_resources_test.go:38: {[] []}
--- PASS: TestLoadResources (0.00s)
PASS

Thanks!

Unable to parse slice of nested struct from JSON

Hi,

Issue somewhat similar to #117 #120
Trying to load included JSON and got strange error 'load config: load files: no such field "Url" in struct'

package main

import (
	"errors"
	"fmt"
	"os"

	"github.com/cristalhq/aconfig"
	"github.com/davecgh/go-spew/spew"
)

// Application config
type config struct {
	APIBind   string       `json:"bind_api" required:"true"`      // address to bind HTTP API
	UDPBind   string       `json:"bind_udp" required:"true"`      // address to bind UDP event receiver
	Env       string       `json:"env" default:"prod"`            // execution environment [prod|local|dev]
	Store     string       `json:"store" default:"pg"`
	TLS       TLS          `json:"tls"`                           // mTLS config
	DB        DB           `json:"database" required:"true"`      // database component
	Validator Validator    `json:"validator" required:"true"`     // validator component
	Registrar []Registrar  `json:"registrar" required:"true"`     // registrar component(s)
	Notify    NotifyClient `json:"notify_client" required:"true"` // send notify events to
}

// DB configuration.
type DB struct {
	Host     string `json:"host" required:"true"`                 // db host
	User     string `json:"user" required:"true"`                 // db auth login
	Password string `json:"password" required:"true"`             // db auth passwd
	DB       string `json:"name" required:"true"`                 // db name
	Vtable   string `json:"validator_table" required:"true"`      // validator DB table
	Rtable   string `json:"registrar_table" default:"registrant"` // registrar DB table
	TLS      TLS    `json:"tls"`                                  // mTLS config
	Port     uint16 `json:"port" default:"5432"`                  // db port
}

type TLS struct {
	CACert string `json:"cacert"`      // CA cert
	Cert   string `json:"client_cert"` // mTLS cert
	Key    string `json:"client_key"`  // mTLS key
}

// validator registrar component configuration.
type Validator struct {
	URL         string `json:"url" required:"true"`          // management URL
	ContactHost string `json:"contact_host" required:"true"` // host in contact
}

// main registrar configuration.
type Registrar struct {
	URL string `json:"url" required:"true"` // management URL
}

type NotifyClient struct {
	URL string `json:"url" required:"true"` // URL send to
}

var ErrDoMigrate = errors.New("db migrate requested")
var CFG config

func main() {
	if err := Load(); err != nil {
		fmt.Println(err)
	}

	spew.Dump(CFG)
}

func Load() error {
	loader := aconfig.LoaderFor(&CFG, aconfig.Config{
		//		FlagPrefix: "trm",
		FileFlag: "config",
		Files:    []string{"config/trunkmanager.json"},
	})
	flags := loader.Flags() // <- IMPORTANT: use this to define your non-config flags
	migrate := flags.Bool("migrate", false, "do db migrations")
	if err := flags.Parse(os.Args[1:]); err != nil {
		return err
	}
	if err := loader.Load(); err != nil {
		return err
	}
	if *migrate {
		return ErrDoMigrate
	}
	return nil
}

trunkmanager.json

feature: show environment variable names in usage

It would be handy to have some way to print environment variable names either in the -h usage or from some other help flag. This would help make it more obvious to someone running an application configured by aconfig what their config options are.

Make composite environment variable name optional

There are some cases when you can't change the environment variable name to the desired format.

For example, Heroku provides a PORT environment variable for you, you can't change the name.

But with the current library implementation, I can't structure my config files like I want to. Example:

type Config struct {
    Server ConfigServer `yaml:"server"`
}

type ConfigServer struct {
    Host string `yaml:"host"`
    Port string `yaml:"port" env:"PORT"`
}

The variable name library is looking for will be SERVER_PORT, not PORT, and I can't change that without restructuring my config structure (and config files!).

Please provide an option to turn off automatically provided env prefixes, to enable exact env names.

Panic when parse toml slice

POC:

func main() {
	type MyConfig struct {
		Slice []struct {
			A int
		} `json:"slice" toml:"slice"`
	}

	var cfg MyConfig
	loader := aconfig.LoaderFor(&cfg, aconfig.Config{
		// feel free to skip some steps :)
		// SkipDefaults: true,
		// SkipFiles:    true,
		// SkipEnv:      true,
		// SkipFlags:    true,
		EnvPrefix:  "APP",
		FlagPrefix: "app",
		Files:      []string{"config.toml"},
		FileDecoders: map[string]aconfig.FileDecoder{
			// from `aconfigyaml` submodule
			// see submodules in repo for more formats
			".toml": aconfigtoml.New(),
		},
	})

	if err := loader.Load(); err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", cfg)
}

config.toml file:

[[slice]]
A = 5

Result:

panic: []map[string]interface {} [map[A:5]]
github.com/cristalhq/[email protected]/reflection.go:190 

YAML arrays can not be mapped to []string

YAML:

WriteRoles: 
  - Writer
ReadRoles:
  - Writer
  - Reader
ListRoles:
  - Writer
  - Reader

Struct:

type TestConfig struct {
	ReadRoles  []Role `yaml:"ReadRoles"`
	WriteRoles []Role `yaml:"WriteRoles"`
	ListRoles  []Role `yaml:"ListRoles"`
}
type Role string

Error:

    suite.go:63: test panicked: string Writer
        goroutine 26 [running]:
        runtime/debug.Stack()
        	/usr/local/opt/go/libexec/src/runtime/debug/stack.go:24 +0x7a
        github.com/stretchr/testify/suite.failOnPanic(0xc000545040)
        	/Users/sivanov/go/pkg/mod/github.com/stretchr/[email protected]/suite/suite.go:63 +0x54
        panic({0x255a9c0, 0xc000088130})
        	/usr/local/opt/go/libexec/src/runtime/panic.go:844 +0x25a
        github.com/cristalhq/aconfig.mii({0x255a9c0, 0xc000191a50})
        	/Users/sivanov/go/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:389 +0x36e

seems that array is just unexpected here:
image

Override flag and env

Problem

Having such a config

type MyConfig struct {
	Port int
	MyAuth MyAuth `yaml:"myAuth"`
}

type MyAuth struct {
	MyUser string `yaml:"myUser" default:"def-user"`
}

I can parametrize it with

APP_MYAUTH_MYUSER=jakub
--myauth.myuser jakub

What I would like to do is to handle CamelCase config properly. In such cases I'd rather have something like this:

APP_MY_AUTH_MY_USER=jakub
--my-auth-my-user=jakub

In other words, I'd like to override default env and flag.

Note: With yaml I had to put myauth.myuser, but yaml tag is taken into account.

Proposal

How about introducing new tag

type MyConfig struct {
	Port int
	MyAuth MyAuth `yaml:"myAuth"`
}

type MyAuth struct {
	MyUser string `yaml:"myUser" env:"my_auth_my_user" flag:"my-auth-my-user" default:"def-user"`
}

env could also be hierarchical like this:

type MyConfig struct {
	Port int
	MyAuth MyAuth `yaml:"myAuth" env:"my_auth"`
}

type MyAuth struct {
	MyUser string `yaml:"myUser" env:"my_user" default:"def-user"`
}

But I've got bad experience with it from envconfig. because you then don't know if you need parse app_myauth_my_user or just app_my_user if env:"my_auth" is missing.

Check that env is not empty

We have a required flag that ensures that env is set. But it doesn't check if it's just listed or really set to any value.
It's crucial for most apps I'm working with and can help with throwing away ugly validators like this

...
if cfg.URL == "" {
    return errors.New("URL can't be empty")
}
...

This feature is also present in some other libs as notEmpty.

I suggest we add it here as well so providing a notEmpty flag would make a comparison of the env to its zero type value and throw parsing error as a result

Support for AllowUnknownFlags

Hi!

I've been using aconfig for a while now and I love it! Thank you for all the hard work. 😊👍

I'm trying to load the same flags using different structs in two places in my program and it always complains about the flag not being defined. I've tried using AllowUnknownFlags without success.

The gist of it is I create a struct with the field A and load it during the initialization of the program, then later on I load a struct with the field B. I then start the program like this: go run main.go --a foo --b bar

When loading the first struct with only A, it says -b is not defined.

Any ideas? I'm on the phone now, but can paste a test I built to debug tomorrow if you'd like.

Thank you again!

Reflection on slice kind ignores tags for field name

The reflection process does not consider struct field tags within a slice. Therefore, the field name must match the tag name, starting with an uppercase letter.

func (l *Loader) m2s(m map[string]interface{}, structValue reflect.Value) error {
	for name, value := range m {
		name = strings.Title(name)
		structFieldValue := structValue.FieldByName(name)
                ...

https://github.com/cristalhq/aconfig/blob/main/reflection.go#L351

It appears that this issue is main reason of #147

Config loading doesn't work for struct pointers

This small example will fail with error cannot load config: type kind "struct" isn't supported:

type s1 struct {
	A *s2
}

type s2 struct {
        B string
}

var cfg s1
loader := aconfig.LoaderFor(&cfg, aconfig.Config{
	Files:           []string{"/var/opt/myapp/config.json", "ouch.yaml"},
	FileDecoders: map[string]aconfig.FileDecoder{
		// from `aconfigyaml` submodule
		// see submodules in repo for more formats
		".yaml": aconfigyaml.New(),
	},
})

flagSet := loader.Flags()

if err := loader.Load(); err != nil {
	panic(err)
}

I prepared quick fix and additional test for this case in my fork, but I don't know your ideology for pointers in configs. I guess some note about this limitation should be added to Readme at least If you don't want to fix this behavior

flagSet type is always string

I find this module a great alternative to viper when using Cobra, but miss the help output that identifies the type of the flag. Right now, all flags return as string

type Config struct {
	Home     string `env:"HOME" required:"true" usage:"Set home"`
	Port     int64  `default:"1111" usage:"Set int"`
	WantBool bool   `usage:"Set bool" flag:"wantbool"`
}
...
Produces:
Flags:
  -h, --help              help for cli
      --home string       Set home
      --port string       Set int (default "1111")
      --wantbool string   Set bool

Want:
Flags:
  -h, --help          help for cli
      --home string   Set home
      --port int      Set int (default 1111)
      --wantbool      Set bool

In the new-parser branch, parser.go 144 where it says TODO, is this where you'd expect to do something like:?

switch field.value.Type().Kind() {
	case reflect.Bool:
		boolVal, _ := strconv.ParseBool(field.Tag("default"))
		l.flagSet.Bool(flagName, boolVal, field.Tag("usage"))
        ...
}

It's something I'd like to do but am not sure if you'll be merging new-parser in to main.

Error while parsing nested structs in slice in yaml file

There is an error while parsing structs inside a slice. The source is yaml file, but probably will work the same with other types.

The problem is that the library tries to set string representation of nested struct to a slice (but string representation nested struct looks like "map[field1:a slice_of_struct2:[map[field2:b field3:c]]]") and fails.

To reproduce an error:

package main

import (
	"io/ioutil"
	"os"

	"github.com/cristalhq/aconfig"
	"github.com/cristalhq/aconfig/aconfigyaml"
)

const testStruct = `
struct1:
  slice_of_struct1:
    - field1: a
      slice_of_struct2:
        - field2: b
          field3: c`

type TestStruct struct {
	Struct1 struct {
		SliceOfStruct1 []struct {
			Field1         string `json:"field1"`
			SliceOfStruct2 []struct {
				Field2 string `json:"field2"`
				Field3 string `json:"field3"`
			} `yaml:"slice_of_struct2"`
		} `yaml:"slice_of_struct1"`
	} `yaml:"struct1"`
}

func cfgError() {

	tmpfile, err := ioutil.TempFile("", "test.*.yaml")
	if err != nil {
		panic(err)
	}

	defer os.Remove(tmpfile.Name())

	if _, err := tmpfile.Write([]byte(testStruct)); err != nil {
		panic(err)
	}
	if err := tmpfile.Close(); err != nil {
		panic(err)
	}

	var cfg TestStruct

	loader := aconfig.LoaderFor(&cfg, aconfig.Config{
		SkipFlags: true,
		Files:     []string{tmpfile.Name()},
		FileDecoders: map[string]aconfig.FileDecoder{
			".yaml": aconfigyaml.New(),
		},
	})
	if err := loader.Load(); err != nil {
		panic(err) // aconfig: cannot load config: loading files: incorrect slice item "map[field1:a slice_of_struct2:[map[field2:b field3:c]]]": type kind "struct" isn't supported
	}
}

yaml decorder can't open file in embed file system

in the aconfigyaml package, DecodeFile func use os.Open to open file, when in embed file system, it can't find file then throw an err: "no such file or directory"。
but when using default json decorder, jsonDecoder has a fsys field, which can open file in embed file system.

hope you can fix this bug in aconfigyaml package.

Feature Request: support any type that implements Setter (or any other similar) interface

Hello, thank you for a great library!

Sometimes there is a need to pass as an ENV variable, not a single value, but a whole struct to override all the fields inside the struct at once. This might probably be needed in highly configurable applications, where the number of exposed config params is big enough.
To solve this issue generically, the lib can give developers the ability to set a custom Unmarshaler/Setter for a type.

A typical interface to support this feature might look like this:

type Setter interface {
	SetValue(string) error
}

Examples from other libs:


Also, please add a README.md section with information about supported data types/struct tags. Like in the: https://github.com/kelseyhightower/envconfig#supported-struct-field-types

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.