From b6a316a1166ed23c2d41f06e7867f01e430ac977 Mon Sep 17 00:00:00 2001 From: Ariel Torti <6711122+arieltorti@users.noreply.github.com> Date: Sat, 25 Mar 2023 14:14:46 -0300 Subject: [PATCH] Add strict parsing option (#19) It's useful to fail when extra keys are present in the config file. Strict parsing is akin to json.DisallowUnknownFields and yaml.UnmarshalStrict and returns an error upon encountering an unknown field. --- doc.go | 5 +++++ fig.go | 2 ++ fig_test.go | 34 ++++++++++++++++++++++++++++++++++ option.go | 13 +++++++++++++ 4 files changed, 54 insertions(+) diff --git a/doc.go b/doc.go index 73ae2bb..afd9f67 100644 --- a/doc.go +++ b/doc.go @@ -160,6 +160,11 @@ Change the layout fig uses to parse times using `TimeLayout()`. By default fig parses time using the `RFC.3339` layout (`2006-01-02T15:04:05Z07:00`). +# Strict Parsing + +By default fig ignores any fields in the config file that are not present in the struct. This behaviour can be changed using `UseStrict()` to achieve strict parsing. +When strict parsing is enabled, extra fields in the config file will cause an error. + Required A validate key with a required value in the field's struct tag makes fig check if the field has been set after it's been loaded. Required fields that are not set are returned as an error. diff --git a/fig.go b/fig.go index 077838b..3fada33 100644 --- a/fig.go +++ b/fig.go @@ -76,6 +76,7 @@ type fig struct { tag string timeLayout string useEnv bool + useStrict bool ignoreFile bool envPrefix string } @@ -156,6 +157,7 @@ func (f *fig) decodeMap(m map[string]interface{}, result interface{}) error { WeaklyTypedInput: true, Result: result, TagName: f.tag, + ErrorUnused: f.useStrict, DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToTimeHookFunc(f.timeLayout), diff --git a/fig_test.go b/fig_test.go index 3efd65c..fec130e 100644 --- a/fig_test.go +++ b/fig_test.go @@ -10,6 +10,8 @@ import ( "strings" "testing" "time" + + "github.com/mitchellh/mapstructure" ) type Pod struct { @@ -344,6 +346,38 @@ func Test_fig_Load_RequiredAndDefaults(t *testing.T) { } } +func Test_fig_Load_UseStrict(t *testing.T) { + for _, f := range []string{"server.yaml", "server.json", "server.toml"} { + t.Run(f, func(t *testing.T) { + type Server struct { + Host string `fig:"host"` + } + + var cfg Server + err := Load(&cfg, UseStrict(), File(f), Dirs(filepath.Join("testdata", "valid"))) + if err == nil { + t.Fatalf("expected err") + } + + want := []string{ + "has invalid keys: logger", + } + + fieldErrs := err.(*mapstructure.Error) + + if len(want) != len(fieldErrs.Errors) { + t.Fatalf("\nlen(fieldErrs) != %d\ngot %+v\n", len(want), fieldErrs) + } + + for i, err := range fieldErrs.Errors { + if !strings.Contains(err, want[i]) { + t.Errorf("want %s in fieldErrs, got %+v", want[i], err) + } + } + }) + } +} + func Test_fig_Load_WithOptions(t *testing.T) { for _, f := range []string{"server.yaml", "server.json", "server.toml"} { t.Run(f, func(t *testing.T) { diff --git a/option.go b/option.go index d0fceb8..1fd9ea7 100644 --- a/option.go +++ b/option.go @@ -105,3 +105,16 @@ func UseEnv(prefix string) Option { f.envPrefix = prefix } } + +// UseStrict returns an option that configures fig to return an error if +// there exists additional fields in the config file that are not defined +// in the config struct. +// +// fig.Load(&cfg, fig.UseStrict()) +// +// If this option is not used then fig ignores any additional fields in the config file. +func UseStrict() Option { + return func(f *fig) { + f.useStrict = true + } +}