Skip to content

Commit

Permalink
This upgrade ghodss/yaml to use go-yaml v3
Browse files Browse the repository at this point in the history
This is a breaking change because:

- Strict mode is now the default for yaml.v3
- String-valued boolean support has been dropped for YAML 1.2 spec
compliance
- Default indentation changes (we could set to previous value but since
already breaking might make more sense to use base library default)

As such I have appended the /v2 suffix to the github.com/ghodss/yaml
package name.

Signed-off-by: Silas Davis <[email protected]>
  • Loading branch information
Silas Davis committed Apr 22, 2020
1 parent 25d852a commit 5f682c5
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 137 deletions.
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
module github.com/ghodss/yaml
module github.com/ghodss/yaml/v2

require gopkg.in/yaml.v2 v2.2.2
go 1.14

require (
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
)
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 changes: 1 addition & 14 deletions yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"reflect"
"strconv"

"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

// Marshals the object into JSON then converts JSON to YAML and returns the
Expand Down Expand Up @@ -46,13 +46,6 @@ func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
return unmarshal(yaml.Unmarshal, y, o, opts)
}

// UnmarshalStrict is like Unmarshal except that any mapping keys that are
// duplicates will result in an error.
// To also be strict about unknown fields, add the DisallowUnknownFields option.
func UnmarshalStrict(y []byte, o interface{}, opts ...JSONOpt) error {
return unmarshal(yaml.UnmarshalStrict, y, o, opts)
}

func unmarshal(f func(in []byte, out interface{}) (err error), y []byte, o interface{}, opts []JSONOpt) error {
vo := reflect.ValueOf(o)
j, err := yamlToJSON(y, &vo, f)
Expand Down Expand Up @@ -117,12 +110,6 @@ func YAMLToJSON(y []byte) ([]byte, error) {
return yamlToJSON(y, nil, yaml.Unmarshal)
}

// YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
// returning an error on any duplicate field names.
func YAMLToJSONStrict(y []byte) ([]byte, error) {
return yamlToJSON(y, nil, yaml.UnmarshalStrict)
}

func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte, interface{}) error) ([]byte, error) {
// Convert the YAML to an object.
var yamlObj interface{}
Expand Down
18 changes: 9 additions & 9 deletions yaml_go110_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,26 @@ func TestUnmarshalWithTags(t *testing.T) {
// duplicate fields in the YAML input.
func TestUnmarshalStrictWithJSONOpts(t *testing.T) {
for _, tc := range []struct {
yaml []byte
opts []JSONOpt
want UnmarshalString
wantErr string
yaml []byte
opts []JSONOpt
want UnmarshalPrimitives
wantErr string
}{
{
// By default, unknown field is ignored.
yaml: []byte("a: 1\nunknownField: 2"),
want: UnmarshalString{A: "1"},
yaml: []byte("bool: true\nunknownField: 2"),
want: UnmarshalPrimitives{Bool: true},
},
{
// Unknown field produces an error with `DisallowUnknownFields` option.
yaml: []byte("a: 1\nunknownField: 2"),
yaml: []byte("bool: true\nunknownField: 2"),
opts: []JSONOpt{DisallowUnknownFields},
wantErr: `unknown field "unknownField"`,
},
} {
po := prettyFunctionName(tc.opts)
s := UnmarshalString{}
err := UnmarshalStrict(tc.yaml, &s, tc.opts...)
s := UnmarshalPrimitives{}
err := Unmarshal(tc.yaml, &s, tc.opts...)
if tc.wantErr != "" && err == nil {
t.Errorf("UnmarshalStrict(%#q, &s, %v) = nil; want error", string(tc.yaml), po)
continue
Expand Down
166 changes: 56 additions & 110 deletions yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,61 +34,62 @@ func TestMarshal(t *testing.T) {
}
}

type UnmarshalString struct {
A string
True string
type UnmarshalPrimitives struct {
Number int
String string
Bool bool
}

type UnmarshalStringMap struct {
A map[string]string
Dict map[string]string
}

type UnmarshalNestedString struct {
A NestedString
NestedString NestedString
}

type NestedString struct {
A string
String string
}

type UnmarshalSlice struct {
A []NestedSlice
Slice []NestedStrings
}

type NestedSlice struct {
B string
C *string
type NestedStrings struct {
String string
StringPtr *string
}

func TestUnmarshal(t *testing.T) {
y := []byte("a: 1")
s1 := UnmarshalString{}
e1 := UnmarshalString{A: "1"}
y := []byte("string: \"1\"")
s1 := UnmarshalPrimitives{}
e1 := UnmarshalPrimitives{String: "1"}
unmarshalEqual(t, y, &s1, &e1)

y = []byte("a: true")
s1 = UnmarshalString{}
e1 = UnmarshalString{A: "true"}
y = []byte("bool: true")
s1 = UnmarshalPrimitives{}
e1 = UnmarshalPrimitives{Bool: true}
unmarshalEqual(t, y, &s1, &e1)

y = []byte("true: 1")
s1 = UnmarshalString{}
e1 = UnmarshalString{True: "1"}
y = []byte("bool: true")
s1 = UnmarshalPrimitives{}
e1 = UnmarshalPrimitives{Bool: true}
unmarshalEqual(t, y, &s1, &e1)

y = []byte("a:\n a: 1")
y = []byte("nestedString:\n string: hello")
s2 := UnmarshalNestedString{}
e2 := UnmarshalNestedString{NestedString{"1"}}
e2 := UnmarshalNestedString{NestedString{"hello"}}
unmarshalEqual(t, y, &s2, &e2)

y = []byte("a:\n - b: abc\n c: def\n - b: 123\n c: 456\n")
y = []byte("slice:\n - string: abc\n stringPtr: def\n - string: \"123\"\n stringPtr: \"456\"\n")
s3 := UnmarshalSlice{}
e3 := UnmarshalSlice{[]NestedSlice{NestedSlice{"abc", strPtr("def")}, NestedSlice{"123", strPtr("456")}}}
e3 := UnmarshalSlice{[]NestedStrings{{"abc", strPtr("def")}, {"123", strPtr("456")}}}
unmarshalEqual(t, y, &s3, &e3)

y = []byte("a:\n b: 1")
y = []byte("dict:\n b: balloon")
s4 := UnmarshalStringMap{}
e4 := UnmarshalStringMap{map[string]string{"b": "1"}}
e4 := UnmarshalStringMap{map[string]string{"b": "balloon"}}
unmarshalEqual(t, y, &s4, &e4)

y = []byte(`
Expand All @@ -102,58 +103,12 @@ b:
}
s5 := map[string]*NamedThing{}
e5 := map[string]*NamedThing{
"a": &NamedThing{Name: "TestA"},
"b": &NamedThing{Name: "TestB"},
"a": {Name: "TestA"},
"b": {Name: "TestB"},
}
unmarshalEqual(t, y, &s5, &e5)
}

// TestUnmarshalNonStrict tests that we parse ambiguous YAML without error.
func TestUnmarshalNonStrict(t *testing.T) {
for _, tc := range []struct {
yaml []byte
want UnmarshalString
}{
{
yaml: []byte("a: 1"),
want: UnmarshalString{A: "1"},
},
{
// Unknown field get ignored.
yaml: []byte("a: 1\nunknownField: 2"),
want: UnmarshalString{A: "1"},
},
{
// Unknown fields get ignored.
yaml: []byte("unknownOne: 2\na: 1\nunknownTwo: 2"),
want: UnmarshalString{A: "1"},
},
{
// Last declaration of `a` wins.
yaml: []byte("a: 1\na: 2"),
want: UnmarshalString{A: "2"},
},
{
// Even ignore first declaration of `a` with wrong type.
yaml: []byte("a: [1,2,3]\na: value-of-a"),
want: UnmarshalString{A: "value-of-a"},
},
{
// Last value of `a` and first and only mention of `true` are parsed.
yaml: []byte("true: string-value-of-yes\na: 1\na: [1,2,3]\na: value-of-a"),
want: UnmarshalString{A: "value-of-a", True: "string-value-of-yes"},
},
{
// In YAML, `YES` is a Boolean true.
yaml: []byte("true: YES"),
want: UnmarshalString{True: "true"},
},
} {
s := UnmarshalString{}
unmarshalEqual(t, tc.yaml, &s, &tc.want)
}
}

// prettyFunctionName converts a slice of JSONOpt function pointers to a human
// readable string representation.
func prettyFunctionName(opts []JSONOpt) []string {
Expand All @@ -177,50 +132,45 @@ func unmarshalEqual(t *testing.T, y []byte, s, e interface{}, opts ...JSONOpt) {
}
}

// TestUnmarshalStrict tests that we return an error on ambiguous YAML.
func TestUnmarshalStrict(t *testing.T) {
// TestUnmarshalErrors tests that we return an error on ambiguous YAML.
func TestUnmarshalErrors(t *testing.T) {
for _, tc := range []struct {
yaml []byte
want UnmarshalString
wantErr string
yaml []byte
want UnmarshalPrimitives
wantErr string
}{
{
yaml: []byte("a: 1"),
want: UnmarshalString{A: "1"},
yaml: []byte("number: 1"),
want: UnmarshalPrimitives{Number: 1},
},
{
// Order does not matter.
yaml: []byte("true: 1\na: 2"),
want: UnmarshalString{A: "2", True: "1"},
yaml: []byte("bool: true\nnumber: 2"),
want: UnmarshalPrimitives{Number: 2, Bool: true},
},
{
// By default, unknown field is ignored.
yaml: []byte("a: 1\nunknownField: 2"),
want: UnmarshalString{A: "1"},
yaml: []byte("string: foo\nunknownField: 2"),
want: UnmarshalPrimitives{String: "foo"},
},
{
// Declaring `a` twice produces an error.
yaml: []byte("a: 1\na: 2"),
wantErr: `key "a" already set in map`,
},
{
// Not ignoring first declaration of A with wrong type.
yaml: []byte("a: [1,2,3]\na: value-of-a"),
wantErr: `key "a" already set in map`,
yaml: []byte("number: 1\nnumber: 2"),
wantErr: `mapping key "number" already defined at line 1`,
},
{
// Declaring field `true` twice.
yaml: []byte("true: string-value-of-yes\ntrue: 1"),
wantErr: `key true already set in map`,
// Not ignoring first declaration of String with wrong type.
yaml: []byte("a: [1,2,3]\na: value-of-a"),
wantErr: `mapping key "a" already defined at line 1`,
},
{
// In YAML, `YES` is a Boolean true.
yaml: []byte("true: YES"),
want: UnmarshalString{True: "true"},
// Declaring field `bool` twice.
yaml: []byte("bool: true\nbool: false"),
wantErr: `mapping key "bool" already defined at line 1`,
},
} {
s := UnmarshalString{}
err := UnmarshalStrict(tc.yaml, &s)
s := UnmarshalPrimitives{}
err := Unmarshal(tc.yaml, &s)
if tc.wantErr != "" && err == nil {
t.Errorf("UnmarshalStrict(%#q, &s) = nil; want error", string(tc.yaml))
continue
Expand All @@ -239,7 +189,7 @@ func TestUnmarshalStrict(t *testing.T) {

// Even if there was an error, we continue the test: We expect that all
// errors occur during YAML unmarshalling. Such errors leaves `s` unmodified
// and the following check will compare default values of `UnmarshalString`.
// and the following check will compare default values of `UnmarshalPrimitives`.

if !reflect.DeepEqual(s, tc.want) {
t.Errorf("UnmarshalStrict(%#q, &s) = %+#v; want %+#v", string(tc.yaml), s, tc.want)
Expand Down Expand Up @@ -319,17 +269,17 @@ func TestYAMLToJSON(t *testing.T) {
}, {
"- t: a\n" +
"- t:\n" +
" b: 1\n" +
" c: 2\n",
" b: 1\n" +
" c: 2\n",
`[{"t":"a"},{"t":{"b":1,"c":2}}]`,
nil,
}, {
`[{t: a}, {t: {b: 1, c: 2}}]`,
`[{"t":"a"},{"t":{"b":1,"c":2}}]`,
strPtr("- t: a\n" +
"- t:\n" +
" b: 1\n" +
" c: 2\n"),
" b: 1\n" +
" c: 2\n"),
}, {
"- t: \n",
`[{"t":null}]`,
Expand Down Expand Up @@ -407,23 +357,19 @@ func runCases(t *testing.T, runType RunType, cases []Case) {
invMsg, string(output), reverse, string(input))
}
}

}

// To be able to easily fill in the *Case.reverse string above.
func strPtr(s string) *string {
return &s
}

func TestYAMLToJSONStrict(t *testing.T) {
func TestYAMLToJSONDuplicateFields(t *testing.T) {
const data = `
foo: bar
foo: baz
`
if _, err := YAMLToJSON([]byte(data)); err != nil {
t.Error("expected YAMLtoJSON to pass on duplicate field names")
}
if _, err := YAMLToJSONStrict([]byte(data)); err == nil {
if _, err := YAMLToJSON([]byte(data)); err == nil {
t.Error("expected YAMLtoJSONStrict to fail on duplicate field names")
}
}

0 comments on commit 5f682c5

Please sign in to comment.