diff --git a/go.mod b/go.mod index 71fcd35..91cac22 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 ) -replace gopkg.in/yaml.v3 => github.com/amurant/go-yaml v0.0.0-20211021125301-a69ce44590ee +replace gopkg.in/yaml.v3 => github.com/amurant/go-yaml v0.0.0-20211023093135-e364698f17de diff --git a/go.sum b/go.sum index 72d32af..fb30b3c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/amurant/go-yaml v0.0.0-20211021125301-a69ce44590ee h1:hh4GlzhK0sFfqNroFo5dgDa4NylwldjzlweQzm85sdI= -github.com/amurant/go-yaml v0.0.0-20211021125301-a69ce44590ee/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/amurant/go-yaml v0.0.0-20211023093135-e364698f17de h1:9CsNtWchSqdRcj0ehywK8mKnZS2Z5A9U0rL0/tdgtX8= +github.com/amurant/go-yaml v0.0.0-20211023093135-e364698f17de/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= diff --git a/marshal.go b/marshal.go index 08f0131..ec50328 100644 --- a/marshal.go +++ b/marshal.go @@ -1,9 +1,10 @@ package yaml import ( + "encoding/json" "fmt" - "encoding/json" + kubejson "sigs.k8s.io/json" "gopkg.in/yaml.v3" ) @@ -13,12 +14,7 @@ func JSONToYAML(jsonBytes []byte) ([]byte, error) { // Convert the JSON to an object. var jsonObj interface{} - // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the - // Go JSON library doesn't try to pick the right number type (int, float, - // etc.) when unmarshalling to interface{}, it just picks float64 - // universally. go-yaml does go through the effort of picking the right - // number type, so we can preserve number type throughout this process. - if err := yaml.Unmarshal(jsonBytes, &jsonObj); err != nil { + if err := kubejson.UnmarshalCaseSensitivePreserveInts(jsonBytes, &jsonObj); err != nil { return nil, fmt.Errorf("error converting JSON to YAML: %v", err) } diff --git a/unmarshal.go b/unmarshal.go index 34da0aa..1458281 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -42,14 +42,18 @@ const ( func yamlUnmarshal(yamlBytes []byte, v interface{}, options yamlToTargetOption) (strictErrors []error, err error) { dec := yaml.NewDecoder(bytes.NewReader(yamlBytes)) dec.KnownFields(options&disallowUnknownFields != 0) - dec.DisableUniqueKeys(options&disallowDuplicateFields != 0) + dec.DisableUniqueKeys(options&disallowDuplicateFields == 0) err = dec.Decode(v) if e, ok := err.(*yaml.TypeError); ok { errs := make([]error, 0, len(e.StrictErrors)) for _, err := range e.StrictErrors { errs = append(errs, errors.New(err)) } - return errs, &yaml.TypeError{e.Errors, nil} + if len(e.Errors) == 0 { + return errs, nil + } else { + return errs, &yaml.TypeError{e.Errors, nil} + } } return nil, err } @@ -108,13 +112,13 @@ func YAMLToJSONStrict(yamlBytes []byte, strictOptions ...StrictOption) (json []b case DisallowDuplicateFields: options = options | disallowDuplicateFields case DisallowUnknownFields: - options = options | disallowUnknownFields + return nil, nil, fmt.Errorf("strict option `DisallowUnknownFields` is not supported for YAMLToJSONStrict") default: return nil, nil, fmt.Errorf("unknown strict option %d", strictOpt) } } } else { - options = none | disallowDuplicateFields | disallowUnknownFields + options = none | disallowDuplicateFields } return yamlToJSONTarget(yamlBytes, nil, options) } diff --git a/unmarshal_test.go b/unmarshal_test.go index 3986a98..f0ce09a 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -1,10 +1,168 @@ package yaml import ( + "fmt" "reflect" "testing" ) +/* Test helper functions */ + +type errorType int + +const ( + noErrorsType errorType = 0 + strictErrorsType errorType = 1 << iota + fatalErrorsType + strictAndFatalErrorsType errorType = strictErrorsType | fatalErrorsType +) + +type unmarshalTestCase struct { + encoded []byte + decodeInto interface{} + decoded interface{} + err errorType +} + +type testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) (strictErrors []error, err error) + +var ( + funcUnmarshal testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) (strictErrors []error, err error) { + err = Unmarshal(yamlBytes, obj) + return []error{}, err + } + + funcUnmarshalStrict testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) (strictErrors []error, err error) { + return UnmarshalStrict(yamlBytes, obj) + } + + funcUnmarshalDisallowDuplicateFields testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) (strictErrors []error, err error) { + return UnmarshalStrict(yamlBytes, obj, DisallowDuplicateFields) + } + + funcUnmarshalDisallowUnknownFields testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) (strictErrors []error, err error) { + return UnmarshalStrict(yamlBytes, obj, DisallowUnknownFields) + } +) + +func testUnmarshal(t *testing.T, f testUnmarshalFunc) func(test unmarshalTestCase) error { + return func(test unmarshalTestCase) error { + typ := reflect.TypeOf(test.decodeInto) + if typ.Kind() != reflect.Ptr { + return fmt.Errorf("unmarshalTest.ptr %T is not a pointer type", test.decodeInto) + } + + value := reflect.New(typ.Elem()) + + if !reflect.DeepEqual(test.decodeInto, value.Interface()) { + // There's no reason for ptr to point to non-zero data, + // as we decode into new(right-type), so the data is + // discarded. + // This can easily mean tests that silently don't test + // what they should. To test decoding into existing + // data, see TestPrefilled. + return fmt.Errorf("unmarshalTest.ptr %#v is not a pointer to a zero value", test.decodeInto) + } + + strictErrors, err := f(test.encoded, value.Interface()) + if (err != nil || len(strictErrors) > 0) && test.err == noErrorsType { + return fmt.Errorf("error unmarshaling YAML: %v %v", err, strictErrors) + } + if len(strictErrors) == 0 && test.err&strictErrorsType != 0 { + return fmt.Errorf("expected strict errors, but no strict error was returned, yaml: `%s`", test.encoded) + } + if err == nil && test.err&fatalErrorsType != 0 { + return fmt.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.encoded) + } + + if test.err&fatalErrorsType != 0 { + // Don't check output if error is fatal + return nil + } + + if !reflect.DeepEqual(value.Elem().Interface(), test.decoded) { + return fmt.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v", test.decoded, value.Elem().Interface()) + } + + return nil + } +} + +type yamlToJSONTestcase struct { + yaml string + json string + // By default we test that reversing the output == input. But if there is a + // difference in the reversed output, you can optionally specify it here. + yaml_reverse_overwrite *string + err errorType +} + +type testYAMLToJSONFunc = func(yamlBytes []byte) (json []byte, strictErrors []error, err error) + +var ( + funcYAMLToJSON testYAMLToJSONFunc = func(yamlBytes []byte) (json []byte, strictErrors []error, err error) { + json, err = YAMLToJSON(yamlBytes) + return json, []error{}, err + } + + funcYAMLToJSONStrict testYAMLToJSONFunc = func(yamlBytes []byte) (json []byte, strictErrors []error, err error) { + return YAMLToJSONStrict(yamlBytes) + } +) + +func testYAMLToJSON(t *testing.T, f testYAMLToJSONFunc) func(test yamlToJSONTestcase) error { + return func(test yamlToJSONTestcase) error { + { + // Convert Yaml to Json + genJson, strictErrors, err := f([]byte(test.yaml)) + if (err != nil || len(strictErrors) > 0) && test.err == noErrorsType { + return fmt.Errorf("Failed to convert YAML to JSON, yaml: `%s`, err: %v %v", test.yaml, err, strictErrors) + } + if len(strictErrors) == 0 && test.err&strictErrorsType != 0 { + return fmt.Errorf("expected strict errors, but no strict error was returned, yaml: `%s`", test.yaml) + } + if err == nil && test.err&fatalErrorsType != 0 { + return fmt.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.yaml) + } + + if test.err&fatalErrorsType != 0 { + // Don't check output if error is fatal + return nil + } + + // Check it against the expected output. + if string(genJson) != test.json { + return fmt.Errorf("Failed to convert YAML to JSON, yaml: `%s`, expected json `%s`, got `%s`", test.yaml, test.json, string(genJson)) + } + } + + { + // Convert JSON to YAML + genYaml, err := JSONToYAML([]byte(test.json)) + if err != nil { + return fmt.Errorf("Failed to convert JSON to YAML, json: `%s`, err: %v", test.json, err) + } + + // Set the string that we will compare the reversed output to. + yaml := test.yaml + + // If a special reverse string was specified, use that instead. + if test.yaml_reverse_overwrite != nil { + yaml = *test.yaml_reverse_overwrite + } + + // Check the reverse is equal to the input (or to *c.reverse). + if string(genYaml) != yaml { + return fmt.Errorf("Failed to convert JSON to YAML, json: `%s`, expected yaml `%s`, got `%s`", test.json, yaml, string(genYaml)) + } + } + + return nil + } +} + +/* Start tests */ + type NestedString struct { A string } @@ -63,328 +221,489 @@ type WithTaggedField struct { Field string `json:"field"` } -type unmarshalTestCase struct { - encoded []byte - decodeInto interface{} - decoded interface{} +func TestUnmarshalStrict(t *testing.T) { + tester := testUnmarshal(t, funcUnmarshalStrict) + + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "1"}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("A: true"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "true"}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("true: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{True: "1"}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("True: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{True: "1"}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("A:\n A: 1"), + decodeInto: new(UnmarshalNestedString), + decoded: UnmarshalNestedString{NestedString{"1"}}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("A:\n b: 1"), + decodeInto: new(UnmarshalStringMap), + decoded: UnmarshalStringMap{map[string]string{"b": "1"}}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("A:\n - B: abc\n C: def\n - B: 123\n C: 456\n"), + decodeInto: new(UnmarshalSlice), + decoded: UnmarshalSlice{[]NestedSlice{{"abc", strPtr("def")}, {"123", strPtr("456")}}}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n name: TestA\nb:\n name: TestB"), + decodeInto: new(map[string]*NamedThing), + decoded: map[string]*NamedThing{ + "a": {Name: "TestA"}, + "b": {Name: "TestB"}, + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n name: TestA\nb:\n name: TestB"), + decodeInto: new(map[string]*NamedThing2), + decoded: map[string]*NamedThing2{ + "a": {Name: "TestA"}, + "b": {Name: "TestB"}, + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n name: TestA\n id: ID-1"), + decodeInto: new(map[string]*NamedThing2), + decoded: map[string]*NamedThing2{ + "a": {Name: "TestA", ID: "ID-1"}, + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: true\na: false"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("A: true\nA: false"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "false"}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n - b: abc\n C: 32\n B: 123"), + decodeInto: new(UnmarshalSlice), + decoded: nil, + err: fatalErrorsType, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n b: 1\n c: 3"), + decodeInto: new(UnmarshalStringMap), + decoded: nil, + err: fatalErrorsType, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a:\n name: TestA\n id: ID-A\n id: ID-1"), + decodeInto: new(NamedThing), + decoded: NamedThing{}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 10\nb: 11\n"), + decodeInto: new(Simple), + decoded: Simple{ + A: "10", + B: "11", + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 10\nb: 11\n"), + decodeInto: new(WithEmbed), + decoded: WithEmbed{ + Inner: Inner{A: "10"}, + B: "11", + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 10\nb: 11\n"), + decodeInto: new(WithPtrEmbed), + decoded: WithPtrEmbed{ + Inner: &Inner{A: "10"}, + B: "11", + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 3.0\nb: 3\n"), + decodeInto: new(interface{}), + decoded: map[string]interface{}{ + "a": float64(3), + "b": int(3), + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 344444444.0\nb: 333\n"), + decodeInto: new(interface{}), + decoded: map[string]interface{}{ + "a": float64(344444444), + "b": int(333), + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: 344444444\nb: 333.0\n"), + decodeInto: new(interface{}), + decoded: map[string]interface{}{ + "a": int(344444444), + "b": float64(333), + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte("a: \"\"\nb: \n"), + decodeInto: new(interface{}), + decoded: map[string]interface{}{ + "a": "", + "b": nil, + }, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte(`field: "hello"`), + decodeInto: new(WithTaggedField), + decoded: WithTaggedField{Field: "hello"}, + }); err != nil { + t.Error(err) + } + + if err := tester(unmarshalTestCase{ + encoded: []byte(`unknown: "hello"`), + decodeInto: new(WithTaggedField), + decoded: WithTaggedField{}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } + + // Duplicate fields are not allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nA: 3"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "3"}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } + + // Unknown fields are not allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nUNKNOWN_FIELD: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "1"}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } } func TestUnmarshal(t *testing.T) { - tests := []unmarshalTestCase{ - { - encoded: []byte("A: 1"), - decodeInto: new(UnmarshalString), - decoded: UnmarshalString{A: "1"}, - }, - { - encoded: []byte("A: true"), - decodeInto: new(UnmarshalString), - decoded: UnmarshalString{A: "true"}, - }, - { - encoded: []byte("true: 1"), - decodeInto: new(UnmarshalString), - decoded: UnmarshalString{True: "1"}, - }, - { - encoded: []byte("True: 1"), - decodeInto: new(UnmarshalString), - decoded: UnmarshalString{True: "1"}, - }, - { - encoded: []byte("A:\n A: 1"), - decodeInto: new(UnmarshalNestedString), - decoded: UnmarshalNestedString{NestedString{"1"}}, - }, - { - encoded: []byte("A:\n b: 1"), - decodeInto: new(UnmarshalStringMap), - decoded: UnmarshalStringMap{map[string]string{"b": "1"}}, - }, - { - encoded: []byte("A:\n - B: abc\n C: def\n - B: 123\n C: 456\n"), - decodeInto: new(UnmarshalSlice), - decoded: UnmarshalSlice{[]NestedSlice{{"abc", strPtr("def")}, {"123", strPtr("456")}}}, - }, - { - encoded: []byte("a:\n name: TestA\nb:\n name: TestB"), - decodeInto: new(map[string]*NamedThing), - decoded: map[string]*NamedThing{ - "a": {Name: "TestA"}, - "b": {Name: "TestB"}, - }, - }, - { - encoded: []byte("a:\n name: TestA\nb:\n name: TestB"), - decodeInto: new(map[string]*NamedThing2), - decoded: map[string]*NamedThing2{ - "a": {Name: "TestA"}, - "b": {Name: "TestB"}, - }, - }, - { - encoded: []byte("a:\n name: TestA\n id: ID-1"), - decodeInto: new(map[string]*NamedThing2), - decoded: map[string]*NamedThing2{ - "a": {Name: "TestA", ID: "ID-1"}, - }, - }, - { - encoded: []byte("a: true\na: false"), - decodeInto: new(UnmarshalString), - decoded: nil, - }, - { - encoded: []byte("a:\n - b: abc\n c: 32\n b: 123"), - decodeInto: new(UnmarshalSlice), - decoded: nil, - }, - { - encoded: []byte("a:\n b: 1\n c: 3"), - decodeInto: new(UnmarshalStringMap), - decoded: nil, - }, - { - encoded: []byte("a:\n name: TestA\n id: ID-A\n id: ID-1"), - decodeInto: new(NamedThing), - decoded: nil, - }, - { - encoded: []byte("a: 10\nb: 11\n"), - decodeInto: new(Simple), - decoded: Simple{ - A: "10", - B: "11", - }, - }, - { - encoded: []byte("a: 10\nb: 11\n"), - decodeInto: new(WithEmbed), - decoded: WithEmbed{ - Inner: Inner{A: "10"}, - B: "11", - }, - }, - { - encoded: []byte("a: 10\nb: 11\n"), - decodeInto: new(WithPtrEmbed), - decoded: WithPtrEmbed{ - Inner: &Inner{A: "10"}, - B: "11", - }, - }, - { - encoded: []byte("a: 3.0\nb: 3\n"), - decodeInto: new(interface{}), - decoded: map[string]interface{}{ - "a": float64(3), - "b": int(3), - }, - }, - { - encoded: []byte("a: 344444444.0\nb: 333\n"), - decodeInto: new(interface{}), - decoded: map[string]interface{}{ - "a": float64(344444444), - "b": int(333), - }, - }, - { - encoded: []byte("a: 344444444\nb: 333.0\n"), - decodeInto: new(interface{}), - decoded: map[string]interface{}{ - "a": int(344444444), - "b": float64(333), - }, - }, - { - encoded: []byte("a: \"\"\nb: \n"), - decodeInto: new(interface{}), - decoded: map[string]interface{}{ - "a": "", - "b": nil, - }, - }, - { - encoded: []byte(`field: "hello"`), - decodeInto: new(WithTaggedField), - decoded: WithTaggedField{Field: "hello"}, - }, - { - encoded: []byte(`unknown: "hello"`), - decodeInto: new(WithTaggedField), - decoded: nil, - }, + tester := testUnmarshal(t, funcUnmarshal) + + // Duplicate fields are allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nA: 3"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "3"}, + }); err != nil { + t.Error(err) } - for _, test := range tests { - typ := reflect.TypeOf(test.decodeInto) - if typ.Kind() != reflect.Ptr { - t.Errorf("unmarshalTest.ptr %T is not a pointer type", test.decodeInto) - continue - } + // Unknown fields are allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nUNKNOWN_FIELD: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "1"}, + }); err != nil { + t.Error(err) + } +} - value := reflect.New(typ.Elem()) +func TestUnmarshalDisallowDuplicateFields(t *testing.T) { + tester := testUnmarshal(t, funcUnmarshalDisallowDuplicateFields) - if !reflect.DeepEqual(test.decodeInto, value.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Errorf("unmarshalTest.ptr %#v is not a pointer to a zero value", test.decodeInto) - continue - } - - strictErrors, err := UnmarshalStrict(test.encoded, value.Interface()) - if err != nil || len(strictErrors) > 0 { - if test.decoded == nil { - continue - } - t.Errorf("error unmarshaling YAML: %v %v", err, strictErrors) - } + // Duplicate fields are not allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nA: 3"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "3"}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } - if !reflect.DeepEqual(value.Elem().Interface(), test.decoded) { - t.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v", test.decoded, value.Elem().Interface()) - } + // Unknown fields are allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nUNKNOWN_FIELD: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "1"}, + }); err != nil { + t.Error(err) } } -type YAMLToJSONTestcase struct { - input string - output string - // By default we test that reversing the output == input. But if there is a - // difference in the reversed output, you can optionally specify it here. - reverse *string +func TestUnmarshalDisallowUnknownFields(t *testing.T) { + tester := testUnmarshal(t, funcUnmarshalDisallowUnknownFields) + + // Duplicate fields are allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nA: 3"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "3"}, + }); err != nil { + t.Error(err) + } + + // Unknown fields are not allowed + if err := tester(unmarshalTestCase{ + encoded: []byte("A: 1\nUNKNOWN_FIELD: 1"), + decodeInto: new(UnmarshalString), + decoded: UnmarshalString{A: "1"}, + err: strictErrorsType, + }); err != nil { + t.Error(err) + } } -func TestYAMLToJSON(t *testing.T) { - tests := []YAMLToJSONTestcase{ - { - "t: a\n", - `{"t":"a"}`, - nil, - }, - { - "t: null\n", - `{"t":null}`, - nil, - }, { - `{"t":"this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80"}`, - "t: this is very long line with spaces and it must be longer than 80 so we will repeat\n that it must be longer that 80\n", - nil, - }, - { - "t: a\n", - `{"t":"a"}`, - nil, - }, - { - "t: \n", - `{"t":null}`, - strPtr("t: null\n"), - }, - { - "t: null\n", - `{"t":null}`, - nil, - }, - { - "1: a\n", - `{"1":"a"}`, - strPtr("\"1\": a\n"), - }, - { - "1000000000000000000000000000000000000: a\n", - `{"1e+36":"a"}`, - strPtr("\"1e+36\": a\n"), - }, - { - "1e+36: a\n", - `{"1e+36":"a"}`, - strPtr("\"1e+36\": a\n"), - }, - { - "\"1e+36\": a\n", - `{"1e+36":"a"}`, - nil, - }, - { - "\"1.2\": a\n", - `{"1.2":"a"}`, - nil, - }, - { - "- t: a\n", - `[{"t":"a"}]`, - nil, - }, - { - "- t: a\n" + - "- t:\n" + - " b: 1\n" + - " c: 2\n", - `[{"t":"a"},{"t":{"b":1,"c":2}}]`, - nil, - }, - { - `[{t: a}, +func TestYAMLToJSONStrict(t *testing.T) { + tester := testYAMLToJSON(t, funcYAMLToJSONStrict) + + if err := tester(yamlToJSONTestcase{ + yaml: "t: a\n", + json: `{"t":"a"}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "t: null\n", + json: `{"t":null}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "t: this is very long line with spaces and it must be longer than 80 so we will repeat\n that it must be longer that 80\n", + json: `{"t":"this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80"}`, + yaml_reverse_overwrite: strPtr("t: this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: `{"t":"json is also valid yaml"}`, + json: `{"t":"json is also valid yaml"}`, + yaml_reverse_overwrite: strPtr("t: json is also valid yaml\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "t: a\n", + json: `{"t":"a"}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "t: \n", + json: `{"t":null}`, + yaml_reverse_overwrite: strPtr("t: null\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "t: null\n", + json: `{"t":null}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "1: a\n", + json: `{"1":"a"}`, + yaml_reverse_overwrite: strPtr("\"1\": a\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "1000000000000000000000000000000000000: a\n", + json: `{"1e+36":"a"}`, + yaml_reverse_overwrite: strPtr("\"1e+36\": a\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "1e+36: a\n", + json: `{"1e+36":"a"}`, + yaml_reverse_overwrite: strPtr("\"1e+36\": a\n"), + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "\"1e+36\": a\n", + json: `{"1e+36":"a"}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "\"1.2\": a\n", + json: `{"1.2":"a"}`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "- t: a\n", + json: `[{"t":"a"}]`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: "- t: a\n" + + "- t:\n" + + " b: 1\n" + + " c: 2\n", + json: `[{"t":"a"},{"t":{"b":1,"c":2}}]`, + }); err != nil { + t.Error(err) + } + + if err := tester(yamlToJSONTestcase{ + yaml: `[{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"), - }, - { - "- t: \n", - `[{"t":null}]`, - strPtr("- t: null\n"), - }, - { - "- t: null\n", - `[{"t":null}]`, - nil, - }, - { - "node-role.kubernetes.io/worker: \"\"\n", - `{"node-role.kubernetes.io/worker":""}`, - nil, - }, + json: `[{"t":"a"},{"t":{"b":1,"c":2}}]`, + yaml_reverse_overwrite: strPtr("- t: a\n" + + "- t:\n" + + " b: 1\n" + + " c: 2\n"), + }); err != nil { + t.Error(err) } - for _, test := range tests { - // Convert Yaml to Json - output, err := YAMLToJSON([]byte(test.input)) - if err != nil { - t.Errorf("Failed to convert YAML to JSON, input: `%s`, err: %v", test.input, err) - } + if err := tester(yamlToJSONTestcase{ + yaml: "- t: \n", + json: `[{"t":null}]`, + yaml_reverse_overwrite: strPtr("- t: null\n"), + }); err != nil { + t.Error(err) + } - // Check it against the expected output. - if string(output) != test.output { - t.Errorf("Failed to convert YAML to JSON, input: `%s`, expected `%s`, got `%s`", test.input, test.output, string(output)) - } + if err := tester(yamlToJSONTestcase{ + yaml: "- t: null\n", + json: `[{"t":null}]`, + }); err != nil { + t.Error(err) } - for _, test := range tests { - // Convert JSON to YAML - input, err := JSONToYAML([]byte(test.output)) - if err != nil { - t.Errorf("Failed to convert JSON to YAML, input: `%s`, err: %v", test.output, err) - } + if err := tester(yamlToJSONTestcase{ + yaml: "node-role.kubernetes.io/worker: \"\"\n", + json: `{"node-role.kubernetes.io/worker":""}`, + }); err != nil { + t.Error(err) + } - // Set the string that we will compare the reversed output to. - reverse := test.input + // Duplicate fields are not allowed + if err := tester(yamlToJSONTestcase{ + yaml: "t: a\nt: c", + json: `{"t":"c"}`, + yaml_reverse_overwrite: strPtr("t: c\n"), + err: strictErrorsType, + }); err != nil { + t.Error(err) + } +} - // If a special reverse string was specified, use that instead. - if test.reverse != nil { - reverse = *test.reverse - } +func TestYAMLToJSON(t *testing.T) { + tester := testYAMLToJSON(t, funcYAMLToJSON) - // Check the reverse is equal to the input (or to *c.reverse). - if string(input) != reverse { - t.Errorf("Failed to convert JSON to YAML, input: `%s`, expected `%s`, got `%s`", test.output, reverse, string(input)) - } + // Duplicate fields are allowed + if err := tester(yamlToJSONTestcase{ + yaml: "t: a\nt: c", + json: `{"t":"c"}`, + yaml_reverse_overwrite: strPtr("t: c\n"), + }); err != nil { + t.Error(err) } }