Skip to content

Commit

Permalink
Merge pull request jinzhu#37 from reuben453/error_on_unmatched_keys_u…
Browse files Browse the repository at this point in the history
…pstream_pr

Add support for using the ErrorOnUnmatchedKeys setting with json files
  • Loading branch information
jinzhu authored Jun 14, 2018
2 parents ec34f32 + 66c7d1b commit 4edaf76
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 13 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ configor.Load(&Config, "application.yml", "database.json")
Return an error on finding keys in the config file that do not match any fields in the config struct.
In the example below, an error will be returned if config.toml contains keys that do not match any fields in the ConfigStruct struct.
If ErrorOnUnmatchedKeys is not set, it defaults to false.
Note that this is currently only supported for toml and yaml files. ErrorOnUnmatchedKeys will be ignored for json files. This may change in the future when the json library adds support for this [https://github.com/golang/go/issues/15314].

Note that for json files, setting ErrorOnUnmatchedKeys to true will have an effect only if using go 1.10 or later.

```go
err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&ConfigStruct, "config.toml")
Expand Down
6 changes: 3 additions & 3 deletions configor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ type Config struct {
Debug bool
Verbose bool

// Supported only for toml and yaml files.
// json does not currently support this: https://github.com/golang/go/issues/15314
// This setting will be ignored for json files.
// In case of json files, this field will be used only when compiled with
// go 1.10 or later.
// This field will be ignored when compiled with go versions lower than 1.10.
ErrorOnUnmatchedKeys bool
}

Expand Down
95 changes: 89 additions & 6 deletions configor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,22 +194,72 @@ func TestUnmatchedKeyInTomlConfigFile(t *testing.T) {
defer os.Remove(file.Name())
defer file.Close()

filename := file.Name()

if err := toml.NewEncoder(file).Encode(config); err == nil {

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, file.Name()); err != nil {
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys")
}

if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, file.Name()); err == nil {
// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename)
if err == nil {
t.Errorf("Should get error when loading configuration with extra keys")
}

// The error should be of type UnmatchedTomlKeysError
tomlErr, ok := err.(*configor.UnmatchedTomlKeysError)
if !ok {
t.Errorf("Should get UnmatchedTomlKeysError error when loading configuration with extra keys")
}

// The error.Keys() function should return the "Test" key
keys := configor.GetStringTomlKeys(tomlErr.Keys)
if len(keys) != 1 || keys[0] != "Test" {
t.Errorf("The UnmatchedTomlKeysError should contain the Test key")
}

} else {
t.Errorf("failed to marshal config")
}

// Add .toml to the file name and test again
err = os.Rename(filename, filename+".toml")
if err != nil {
t.Errorf("Could not add suffix to file")
}
filename = filename + ".toml"
defer os.Remove(filename)

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys. Error: %v", err)
}

// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
err = configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename)
if err == nil {
t.Errorf("Should get error when loading configuration with extra keys")
}

// The error should be of type UnmatchedTomlKeysError
tomlErr, ok := err.(*configor.UnmatchedTomlKeysError)
if !ok {
t.Errorf("Should get UnmatchedTomlKeysError error when loading configuration with extra keys")
}

// The error.Keys() function should return the "Test" key
keys := configor.GetStringTomlKeys(tomlErr.Keys)
if len(keys) != 1 || keys[0] != "Test" {
t.Errorf("The UnmatchedTomlKeysError should contain the Test key")
}

}

func TestUnmatchedKeyInYamlConfigFile(t *testing.T) {
Expand All @@ -230,23 +280,56 @@ func TestUnmatchedKeyInYamlConfigFile(t *testing.T) {
defer os.Remove(file.Name())
defer file.Close()

filename := file.Name()

if data, err := yaml.Marshal(config); err == nil {
file.WriteString(string(data))

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, file.Name()); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys")
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys. Error: %v", err)
}

if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, file.Name()); err == nil {
// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename); err == nil {
t.Errorf("Should get error when loading configuration with extra keys")

// The error should be of type *yaml.TypeError
} else if _, ok := err.(*yaml.TypeError); !ok {
// || !strings.Contains(err.Error(), "not found in struct") {
t.Errorf("Error should be of type yaml.TypeError. Instead error is %v", err)
}

} else {
t.Errorf("failed to marshal config")
}

// Add .yaml to the file name and test again
err = os.Rename(filename, filename+".yaml")
if err != nil {
t.Errorf("Could not add suffix to file")
}
filename = filename + ".yaml"
defer os.Remove(filename)

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys. Error: %v", err)
}

// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename); err == nil {
t.Errorf("Should get error when loading configuration with extra keys")

// The error should be of type *yaml.TypeError
} else if _, ok := err.(*yaml.TypeError); !ok {
// || !strings.Contains(err.Error(), "not found in struct") {
t.Errorf("Error should be of type yaml.TypeError. Instead error is %v", err)
}
}

func TestLoadConfigurationByEnvironment(t *testing.T) {
Expand Down Expand Up @@ -500,7 +583,7 @@ func TestAnonymousStruct(t *testing.T) {

func TestENV(t *testing.T) {
if configor.ENV() != "test" {
t.Errorf("Env should be test when running `go test`")
t.Errorf("Env should be test when running `go test`, instead env is %v", configor.ENV())
}

os.Setenv("CONFIGOR_ENV", "production")
Expand Down
28 changes: 28 additions & 0 deletions unmarshal_json_1_10.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// +build go1.10

package configor

import (
"encoding/json"
"io"
"strings"
)

// unmarshalJSON unmarshals the given data into the config interface.
// If the errorOnUnmatchedKeys boolean is true, an error will be returned if there
// are keys in the data that do not match fields in the config interface.
func unmarshalJSON(data []byte, config interface{}, errorOnUnmatchedKeys bool) error {
reader := strings.NewReader(string(data))
decoder := json.NewDecoder(reader)

if errorOnUnmatchedKeys {
decoder.DisallowUnknownFields()
}

err := decoder.Decode(config)
if err != nil && err != io.EOF {
return err
}
return nil

}
74 changes: 74 additions & 0 deletions unmarshal_json_1_10_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// +build go1.10

package configor_test

import (
"encoding/json"
"io/ioutil"
"os"
"strings"
"testing"

"github.com/jinzhu/configor"
)

func TestUnmatchedKeyInJsonConfigFile(t *testing.T) {
type configStruct struct {
Name string
}
type configFile struct {
Name string
Test string
}
config := configFile{Name: "test", Test: "ATest"}

file, err := ioutil.TempFile("/tmp", "configor")
if err != nil {
t.Fatal("Could not create temp file")
}
defer os.Remove(file.Name())
defer file.Close()

filename := file.Name()

if err := json.NewEncoder(file).Encode(config); err == nil {

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys. Error: %v", err)
}

// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename); err == nil || !strings.Contains(err.Error(), "json: unknown field") {

t.Errorf("Should get unknown field error when loading configuration with extra keys. Instead got error: %v", err)
}

} else {
t.Errorf("failed to marshal config")
}

// Add .json to the file name and test
err = os.Rename(file.Name(), file.Name()+".json")
if err != nil {
t.Errorf("Could not add suffix to file")
}
filename = file.Name() + ".json"
defer os.Remove(filename)

var result configStruct

// Do not return error when there are unmatched keys but ErrorOnUnmatchedKeys is false
if err := configor.New(&configor.Config{}).Load(&result, filename); err != nil {
t.Errorf("Should NOT get error when loading configuration with extra keys. Error: %v", err)
}

// Return an error when there are unmatched keys and ErrorOnUnmatchedKeys is true
if err := configor.New(&configor.Config{ErrorOnUnmatchedKeys: true}).Load(&result, filename); err == nil || !strings.Contains(err.Error(), "json: unknown field") {

t.Errorf("Should get unknown field error when loading configuration with extra keys. Instead got error: %v", err)
}

}
18 changes: 18 additions & 0 deletions unmarshal_json_1_9.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build !go1.10

package configor

import (
"encoding/json"
)

// unmarshalJSON unmarshals the given data into the config interface.
// The errorOnUnmatchedKeys boolean is ignored since the json library only has
// support for that feature from go 1.10 onwards.
// If there are any keys in the data that do not match fields in the config
// interface, they will be silently ignored.
func unmarshalJSON(data []byte, config interface{}, errorOnUnmatchedKeys bool) error {

return json.Unmarshal(data, &config)

}
7 changes: 4 additions & 3 deletions utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package configor

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -105,7 +104,7 @@ func processFile(config interface{}, file string, errorOnUnmatchedKeys bool) err
case strings.HasSuffix(file, ".toml"):
return unmarshalToml(data, config, errorOnUnmatchedKeys)
case strings.HasSuffix(file, ".json"):
return json.Unmarshal(data, config)
return unmarshalJSON(data, config, errorOnUnmatchedKeys)
default:

if err := unmarshalToml(data, config, errorOnUnmatchedKeys); err == nil {
Expand All @@ -114,8 +113,10 @@ func processFile(config interface{}, file string, errorOnUnmatchedKeys bool) err
return errUnmatchedKeys
}

if json.Unmarshal(data, config) == nil {
if err := unmarshalJSON(data, config, errorOnUnmatchedKeys); err == nil {
return nil
} else if strings.Contains(err.Error(), "json: unknown field") {
return err
}

var yamlError error
Expand Down

0 comments on commit 4edaf76

Please sign in to comment.