Skip to content

Commit

Permalink
feat: Introduce ErrYAMLMergeKey
Browse files Browse the repository at this point in the history
  • Loading branch information
romshark committed Jun 29, 2024
1 parent c7fef5a commit 98ba4ab
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ an error annotated with line and column in the YAML file if necessary.
the [`encoding.TextUnmarshaler`](https://pkg.go.dev/encoding#TextUnmarshaler) interface.
- 🚫 Forbids empty array items ([see rationale](#why-are-empty-array-items-forbidden)).
- 🚫 Forbids multi-document files.
- 🚫 Forbids [YAML merge keys](https://yaml.org/type/merge.html).
- Features:
- 🪄 If any type within your configuration struct implements the `Validate` interface,
then its validation method will be called using reflection
Expand Down
7 changes: 7 additions & 0 deletions yamagiconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var (
"any other variants of null are not supported")
ErrYAMLNonStrOnTextUnmarsh = errors.New("value must be a string because the " +
"target type implements encoding.TextUnmarshaler")
ErrYAMLMergeKey = errors.New("avoid using YAML merge keys")

// ErrYAMLEmptyArrayItem applies to both Go arrays and slices even though
// an empty item would be parsed correctly as zero-value in case of Go arrays
Expand Down Expand Up @@ -730,6 +731,12 @@ func validateYAMLValues(
return fmt.Errorf("at %s (as %q): %w",
path, yamlTag, ErrYAMLMissingConfig)
}
for _, n := range contentNode.Content {
if n.Tag == "!!merge" {
return fmt.Errorf("at %d:%d: %w",
n.Line, n.Column, ErrYAMLMergeKey)
}
}
err := validateYAMLValues(anchors, yamlTag, path, f.Type, contentNode)
if err != nil {
return err
Expand Down
77 changes: 74 additions & 3 deletions yamagiconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,67 @@ func TestValidateTypeErrYAMLTagRedefined(t *testing.T) {
require.Equal(t, err, yamagiconf.Validate(TestConfig{}))
}

func TestErrYAMLMergeKey(t *testing.T) {
t.Run("in_struct", func(t *testing.T) {
type Server struct {
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
}
type TestConfig struct {
ServerDefault Server `yaml:"server-default"`
ServerFallback Server `yaml:"server-fallback"`
}
var c TestConfig
err := yamagiconf.Load[TestConfig](`
server-default: &default
host: default.server
port: 12345
server-fallback:
port: 54321
<<: *default
`, &c)
require.ErrorIs(t, err, yamagiconf.ErrYAMLMergeKey)
require.Equal(t, `at 7:3: `+yamagiconf.ErrYAMLMergeKey.Error(), err.Error())
})

t.Run("in_map", func(t *testing.T) {
type TestConfig struct {
Map1 map[string]string `yaml:"map1"`
Map2 map[string]string `yaml:"map2"`
}
var c TestConfig
err := yamagiconf.Load[TestConfig](`
map1: &map1
foo: bar
bazz: fazz
map2:
<<: *map1
mar: tar
`, &c)
require.ErrorIs(t, err, yamagiconf.ErrYAMLMergeKey)
require.Equal(t, `at 6:3: `+yamagiconf.ErrYAMLMergeKey.Error(), err.Error())
})

t.Run("map_multi_merge", func(t *testing.T) {
type TestConfig struct {
Map1 map[string]string `yaml:"map1"`
Map2 map[string]string `yaml:"map2"`
Map3 map[string]string `yaml:"map3"`
}
var c TestConfig
err := yamagiconf.Load[TestConfig](`
map1: &map1
foo: bar
map2: &map2
bazz: fazz
map3:
<<: [*map1,*map2]
`, &c)
require.ErrorIs(t, err, yamagiconf.ErrYAMLMergeKey)
require.Equal(t, `at 7:3: `+yamagiconf.ErrYAMLMergeKey.Error(), err.Error())
})
}

func TestValidateTypeErrTypeEnvTagOnUnexported(t *testing.T) {
type TestConfig struct {
Ok string `yaml:"okay"`
Expand Down Expand Up @@ -1484,10 +1545,20 @@ foo: 1
type TestConfig struct {
Boolean bool `yaml:"boolean"`
}
_, err := LoadSrc[TestConfig]("\nboolean: yes")
_, err := LoadSrc[TestConfig]("boolean: yes")
require.ErrorIs(t, err, yamagiconf.ErrYAMLBadBoolLiteral)
require.Equal(t, `at 1:10: "boolean" (TestConfig.Boolean): `+
yamagiconf.ErrYAMLBadBoolLiteral.Error(), err.Error())
})

t.Run("unsupported_boolean_literal_in_array", func(t *testing.T) {
type TestConfig struct {
Booleans []bool `yaml:"booleans"`
}
_, err := LoadSrc[TestConfig]("booleans:\n - false\n - no")
require.ErrorIs(t, err, yamagiconf.ErrYAMLBadBoolLiteral)
require.True(t, strings.HasPrefix(err.Error(),
`at 2:10: "boolean" (TestConfig.Boolean): must be either false or true`))
require.Equal(t, `at 3:5: "booleans" (TestConfig.Booleans[1]): `+
yamagiconf.ErrYAMLBadBoolLiteral.Error(), err.Error())
})

t.Run("unsupported_null_literal_tilde", func(t *testing.T) {
Expand Down

0 comments on commit 98ba4ab

Please sign in to comment.