diff options
Diffstat (limited to '')
-rw-r--r-- | dependencies/pkg/mod/github.com/goccy/go-yaml@v1.9.6/decode_test.go | 2734 |
1 files changed, 2734 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/github.com/goccy/go-yaml@v1.9.6/decode_test.go b/dependencies/pkg/mod/github.com/goccy/go-yaml@v1.9.6/decode_test.go new file mode 100644 index 0000000..af6e454 --- /dev/null +++ b/dependencies/pkg/mod/github.com/goccy/go-yaml@v1.9.6/decode_test.go @@ -0,0 +1,2734 @@ +package yaml_test + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "math" + "net" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/goccy/go-yaml" + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/internal/errors" + "github.com/goccy/go-yaml/parser" + "golang.org/x/xerrors" +) + +type Child struct { + B int + C int `yaml:"-"` +} + +func TestDecoder(t *testing.T) { + tests := []struct { + source string + value interface{} + }{ + { + "null\n", + (*struct{})(nil), + }, + { + "v: hi\n", + map[string]string{"v": "hi"}, + }, + { + "v: \"true\"\n", + map[string]string{"v": "true"}, + }, + { + "v: \"false\"\n", + map[string]string{"v": "false"}, + }, + { + "v: true\n", + map[string]interface{}{"v": true}, + }, + { + "v: true\n", + map[string]string{"v": "true"}, + }, + { + "v: 10\n", + map[string]string{"v": "10"}, + }, + { + "v: -10\n", + map[string]string{"v": "-10"}, + }, + { + "v: 1.234\n", + map[string]string{"v": "1.234"}, + }, + { + "v: false\n", + map[string]bool{"v": false}, + }, + { + "v: 10\n", + map[string]int{"v": 10}, + }, + { + "v: 10", + map[string]interface{}{"v": 10}, + }, + { + "v: 0b10", + map[string]interface{}{"v": 2}, + }, + { + "v: -0b101010", + map[string]interface{}{"v": -42}, + }, + { + "v: -0b1000000000000000000000000000000000000000000000000000000000000000", + map[string]interface{}{"v": int64(-9223372036854775808)}, + }, + { + "v: 0xA", + map[string]interface{}{"v": 10}, + }, + { + "v: .1", + map[string]interface{}{"v": 0.1}, + }, + { + "v: -.1", + map[string]interface{}{"v": -0.1}, + }, + { + "v: -10\n", + map[string]int{"v": -10}, + }, + { + "v: 4294967296\n", + map[string]int64{"v": int64(4294967296)}, + }, + { + "v: 0.1\n", + map[string]interface{}{"v": 0.1}, + }, + { + "v: 0.99\n", + map[string]float32{"v": 0.99}, + }, + { + "v: -0.1\n", + map[string]float64{"v": -0.1}, + }, + { + "v: 6.8523e+5", + map[string]interface{}{"v": 6.8523e+5}, + }, + { + "v: 685.230_15e+03", + map[string]interface{}{"v": 685.23015e+03}, + }, + { + "v: 685_230.15", + map[string]interface{}{"v": 685230.15}, + }, + { + "v: 685_230.15", + map[string]float64{"v": 685230.15}, + }, + { + "v: 685230", + map[string]interface{}{"v": 685230}, + }, + { + "v: +685_230", + map[string]interface{}{"v": 685230}, + }, + { + "v: 02472256", + map[string]interface{}{"v": 685230}, + }, + { + "v: 0x_0A_74_AE", + map[string]interface{}{"v": 685230}, + }, + { + "v: 0b1010_0111_0100_1010_1110", + map[string]interface{}{"v": 685230}, + }, + { + "v: +685_230", + map[string]int{"v": 685230}, + }, + + // Bools from spec + { + "v: True", + map[string]interface{}{"v": true}, + }, + { + "v: TRUE", + map[string]interface{}{"v": true}, + }, + { + "v: False", + map[string]interface{}{"v": false}, + }, + { + "v: FALSE", + map[string]interface{}{"v": false}, + }, + { + "v: y", + map[string]interface{}{"v": "y"}, // y or yes or Yes is string + }, + { + "v: NO", + map[string]interface{}{"v": "NO"}, // no or No or NO is string + }, + { + "v: on", + map[string]interface{}{"v": "on"}, // on is string + }, + + // Some cross type conversions + { + "v: 42", + map[string]uint{"v": 42}, + }, { + "v: 4294967296", + map[string]uint64{"v": uint64(4294967296)}, + }, + + // int + { + "v: 2147483647", + map[string]int{"v": math.MaxInt32}, + }, + { + "v: -2147483648", + map[string]int{"v": math.MinInt32}, + }, + + // int64 + { + "v: 9223372036854775807", + map[string]int64{"v": math.MaxInt64}, + }, + { + "v: 0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"v": math.MaxInt64}, + }, + { + "v: -9223372036854775808", + map[string]int64{"v": math.MinInt64}, + }, + { + "v: -0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"v": -math.MaxInt64}, + }, + + // uint + { + "v: 0", + map[string]uint{"v": 0}, + }, + { + "v: 4294967295", + map[string]uint{"v": math.MaxUint32}, + }, + + // uint64 + { + "v: 0", + map[string]uint{"v": 0}, + }, + { + "v: 18446744073709551615", + map[string]uint64{"v": math.MaxUint64}, + }, + { + "v: 0b1111111111111111111111111111111111111111111111111111111111111111", + map[string]uint64{"v": math.MaxUint64}, + }, + { + "v: 9223372036854775807", + map[string]uint64{"v": math.MaxInt64}, + }, + + // float32 + { + "v: 3.40282346638528859811704183484516925440e+38", + map[string]float32{"v": math.MaxFloat32}, + }, + { + "v: 1.401298464324817070923729583289916131280e-45", + map[string]float32{"v": math.SmallestNonzeroFloat32}, + }, + { + "v: 18446744073709551615", + map[string]float32{"v": float32(math.MaxUint64)}, + }, + { + "v: 18446744073709551616", + map[string]float32{"v": float32(math.MaxUint64 + 1)}, + }, + + // float64 + { + "v: 1.797693134862315708145274237317043567981e+308", + map[string]float64{"v": math.MaxFloat64}, + }, + { + "v: 4.940656458412465441765687928682213723651e-324", + map[string]float64{"v": math.SmallestNonzeroFloat64}, + }, + { + "v: 18446744073709551615", + map[string]float64{"v": float64(math.MaxUint64)}, + }, + { + "v: 18446744073709551616", + map[string]float64{"v": float64(math.MaxUint64 + 1)}, + }, + + // Timestamps + { + // Date only. + "v: 2015-01-01\n", + map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + { + // RFC3339 + "v: 2015-02-24T18:19:39.12Z\n", + map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, .12e9, time.UTC)}, + }, + { + // RFC3339 with short dates. + "v: 2015-2-3T3:4:5Z", + map[string]time.Time{"v": time.Date(2015, 2, 3, 3, 4, 5, 0, time.UTC)}, + }, + { + // ISO8601 lower case t + "v: 2015-02-24t18:19:39Z\n", + map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, + }, + { + // space separate, no time zone + "v: 2015-02-24 18:19:39\n", + map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, + }, + { + "v: 60s\n", + map[string]time.Duration{"v": time.Minute}, + }, + { + "v: -0.5h\n", + map[string]time.Duration{"v": -30 * time.Minute}, + }, + + // Single Quoted values. + { + `'1': '2'`, + map[interface{}]interface{}{"1": `2`}, + }, + { + `'1': '"2"'`, + map[interface{}]interface{}{"1": `"2"`}, + }, + { + `'1': ''''`, + map[interface{}]interface{}{"1": `'`}, + }, + { + `'1': '''2'''`, + map[interface{}]interface{}{"1": `'2'`}, + }, + { + `'1': 'B''z'`, + map[interface{}]interface{}{"1": `B'z`}, + }, + { + `'1': '\'`, + map[interface{}]interface{}{"1": `\`}, + }, + { + `'1': '\\'`, + map[interface{}]interface{}{"1": `\\`}, + }, + { + `'1': '\"2\"'`, + map[interface{}]interface{}{"1": `\"2\"`}, + }, + { + `'1': '\\"2\\"'`, + map[interface{}]interface{}{"1": `\\"2\\"`}, + }, + { + "'1': ' 1\n 2\n 3'", + map[interface{}]interface{}{"1": " 1 2 3"}, + }, + { + "'1': '\n 2\n 3'", + map[interface{}]interface{}{"1": " 2 3"}, + }, + + // Double Quoted values. + { + `"1": "2"`, + map[interface{}]interface{}{"1": `2`}, + }, + { + `"1": "\"2\""`, + map[interface{}]interface{}{"1": `"2"`}, + }, + { + `"1": "\""`, + map[interface{}]interface{}{"1": `"`}, + }, + { + `"1": "X\"z"`, + map[interface{}]interface{}{"1": `X"z`}, + }, + { + `"1": "\\"`, + map[interface{}]interface{}{"1": `\`}, + }, + { + `"1": "\\\\"`, + map[interface{}]interface{}{"1": `\\`}, + }, + { + `"1": "\\\"2\\\""`, + map[interface{}]interface{}{"1": `\"2\"`}, + }, + { + "'1': \" 1\n 2\n 3\"", + map[interface{}]interface{}{"1": " 1 2 3"}, + }, + { + "'1': \"\n 2\n 3\"", + map[interface{}]interface{}{"1": " 2 3"}, + }, + { + `"1": "a\x2Fb"`, + map[interface{}]interface{}{"1": `a/b`}, + }, + { + `"1": "a\u002Fb"`, + map[interface{}]interface{}{"1": `a/b`}, + }, + { + `"1": "a\x2Fb\u002Fc\U0000002Fd"`, + map[interface{}]interface{}{"1": `a/b/c/d`}, + }, + + { + "a: -b_c", + map[string]interface{}{"a": "-b_c"}, + }, + { + "a: +b_c", + map[string]interface{}{"a": "+b_c"}, + }, + { + "a: 50cent_of_dollar", + map[string]interface{}{"a": "50cent_of_dollar"}, + }, + + // Nulls + { + "v:", + map[string]interface{}{"v": nil}, + }, + { + "v: ~", + map[string]interface{}{"v": nil}, + }, + { + "~: null key", + map[interface{}]string{nil: "null key"}, + }, + { + "v:", + map[string]*bool{"v": nil}, + }, + { + "v: null", + map[string]*string{"v": nil}, + }, + { + "v: null", + map[string]string{"v": ""}, + }, + { + "v: null", + map[string]interface{}{"v": nil}, + }, + { + "v: Null", + map[string]interface{}{"v": nil}, + }, + { + "v: NULL", + map[string]interface{}{"v": nil}, + }, + { + "v: ~", + map[string]*string{"v": nil}, + }, + { + "v: ~", + map[string]string{"v": ""}, + }, + + { + "v: .inf\n", + map[string]interface{}{"v": math.Inf(0)}, + }, + { + "v: .Inf\n", + map[string]interface{}{"v": math.Inf(0)}, + }, + { + "v: .INF\n", + map[string]interface{}{"v": math.Inf(0)}, + }, + { + "v: -.inf\n", + map[string]interface{}{"v": math.Inf(-1)}, + }, + { + "v: -.Inf\n", + map[string]interface{}{"v": math.Inf(-1)}, + }, + { + "v: -.INF\n", + map[string]interface{}{"v": math.Inf(-1)}, + }, + { + "v: .nan\n", + map[string]interface{}{"v": math.NaN()}, + }, + { + "v: .NaN\n", + map[string]interface{}{"v": math.NaN()}, + }, + { + "v: .NAN\n", + map[string]interface{}{"v": math.NaN()}, + }, + + // Explicit tags. + { + "v: !!float '1.1'", + map[string]interface{}{"v": 1.1}, + }, + { + "v: !!float 0", + map[string]interface{}{"v": float64(0)}, + }, + { + "v: !!float -1", + map[string]interface{}{"v": float64(-1)}, + }, + { + "v: !!null ''", + map[string]interface{}{"v": nil}, + }, + { + "v: !!timestamp \"2015-01-01\"", + map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + { + "v: !!timestamp 2015-01-01", + map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + + // Flow sequence + { + "v: [A,B]", + map[string]interface{}{"v": []interface{}{"A", "B"}}, + }, + { + "v: [A,B,C,]", + map[string][]string{"v": []string{"A", "B", "C"}}, + }, + { + "v: [A,1,C]", + map[string][]string{"v": []string{"A", "1", "C"}}, + }, + { + "v: [A,1,C]", + map[string]interface{}{"v": []interface{}{"A", 1, "C"}}, + }, + + // Block sequence + { + "v:\n - A\n - B", + map[string]interface{}{"v": []interface{}{"A", "B"}}, + }, + { + "v:\n - A\n - B\n - C", + map[string][]string{"v": []string{"A", "B", "C"}}, + }, + { + "v:\n - A\n - 1\n - C", + map[string][]string{"v": []string{"A", "1", "C"}}, + }, + { + "v:\n - A\n - 1\n - C", + map[string]interface{}{"v": []interface{}{"A", 1, "C"}}, + }, + + // Map inside interface with no type hints. + { + "a: {b: c}", + map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, + }, + + { + "v: \"\"\n", + map[string]string{"v": ""}, + }, + { + "v:\n- A\n- B\n", + map[string][]string{"v": {"A", "B"}}, + }, + { + "a: '-'\n", + map[string]string{"a": "-"}, + }, + { + "123\n", + 123, + }, + { + "hello: world\n", + map[string]string{"hello": "world"}, + }, + { + "hello: world\r\n", + map[string]string{"hello": "world"}, + }, + { + "hello: world\rGo: Gopher", + map[string]string{"hello": "world", "Go": "Gopher"}, + }, + + // Structs and type conversions. + { + "hello: world", + struct{ Hello string }{"world"}, + }, + { + "a: {b: c}", + struct{ A struct{ B string } }{struct{ B string }{"c"}}, + }, + { + "a: {b: c}", + struct{ A map[string]string }{map[string]string{"b": "c"}}, + }, + { + "a:", + struct{ A map[string]string }{}, + }, + { + "a: 1", + struct{ A int }{1}, + }, + { + "a: 1", + struct{ A float64 }{1}, + }, + { + "a: 1.0", + struct{ A int }{1}, + }, + { + "a: 1.0", + struct{ A uint }{1}, + }, + { + "a: [1, 2]", + struct{ A []int }{[]int{1, 2}}, + }, + { + "a: [1, 2]", + struct{ A [2]int }{[2]int{1, 2}}, + }, + { + "a: 1", + struct{ B int }{0}, + }, + { + "a: 1", + struct { + B int `yaml:"a"` + }{1}, + }, + + { + "a: 1\n", + yaml.MapItem{Key: "a", Value: 1}, + }, + { + "a: 1\nb: 2\nc: 3\n", + yaml.MapSlice{ + {Key: "a", Value: 1}, + {Key: "b", Value: 2}, + {Key: "c", Value: 3}, + }, + }, + { + "v:\n- A\n- 1\n- B:\n - 2\n - 3\n", + map[string]interface{}{ + "v": []interface{}{ + "A", + 1, + map[string][]int{ + "B": {2, 3}, + }, + }, + }, + }, + { + "a:\n b: c\n", + map[string]interface{}{ + "a": map[string]string{ + "b": "c", + }, + }, + }, + { + "a: {x: 1}\n", + map[string]map[string]int{ + "a": { + "x": 1, + }, + }, + }, + { + "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n", + map[string]string{ + "t2": "2018-01-09T10:40:47Z", + "t4": "2098-01-09T10:40:47Z", + }, + }, + { + "a: [1, 2]\n", + map[string][]int{ + "a": {1, 2}, + }, + }, + { + "a: {b: c, d: e}\n", + map[string]interface{}{ + "a": map[string]string{ + "b": "c", + "d": "e", + }, + }, + }, + { + "a: 3s\n", + map[string]string{ + "a": "3s", + }, + }, + { + "a: <foo>\n", + map[string]string{"a": "<foo>"}, + }, + { + "a: \"1:1\"\n", + map[string]string{"a": "1:1"}, + }, + { + "a: 1.2.3.4\n", + map[string]string{"a": "1.2.3.4"}, + }, + { + "a: 'b: c'\n", + map[string]string{"a": "b: c"}, + }, + { + "a: 'Hello #comment'\n", + map[string]string{"a": "Hello #comment"}, + }, + { + "a: 100.5\n", + map[string]interface{}{ + "a": 100.5, + }, + }, + { + "a: \"\\0\"\n", + map[string]string{"a": "\\0"}, + }, + { + "b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n", + map[string]interface{}{ + "b": 2, + "a": 1, + "d": 4, + "c": 3, + "sub": map[string]int{ + "e": 5, + }, + }, + }, + { + " a : b \n", + map[string]string{"a": "b"}, + }, + { + "a: b # comment\nb: c\n", + map[string]string{ + "a": "b", + "b": "c", + }, + }, + { + "---\na: b\n", + map[string]string{"a": "b"}, + }, + { + "a: b\n...\n", + map[string]string{"a": "b"}, + }, + { + "%YAML 1.2\n---\n", + (*struct{})(nil), + }, + { + "---\n", + (*struct{})(nil), + }, + { + "...", + (*struct{})(nil), + }, + { + "v: go test ./...", + map[string]string{"v": "go test ./..."}, + }, + { + "v: echo ---", + map[string]string{"v": "echo ---"}, + }, + { + "v: |\n hello\n ...\n world\n", + map[string]string{"v": "hello\n...\nworld\n"}, + }, + { + "a: !!binary gIGC\n", + map[string]string{"a": "\x80\x81\x82"}, + }, + { + "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", + map[string]string{"a": strings.Repeat("\x90", 54)}, + }, + { + "v:\n- A\n- |-\n B\n C\n", + map[string][]string{ + "v": { + "A", "B\nC", + }, + }, + }, + { + "v:\n- A\n- >-\n B\n C\n", + map[string][]string{ + "v": { + "A", "B C", + }, + }, + }, + { + "a: b\nc: d\n", + struct { + A string + C string `yaml:"c"` + }{ + "b", "d", + }, + }, + { + "a: 1\nb: 2\n", + struct { + A int + B int `yaml:"-"` + }{ + 1, 0, + }, + }, + { + "a: 1\nb: 2\n", + struct { + A int + Child `yaml:",inline"` + }{ + 1, + Child{ + B: 2, + C: 0, + }, + }, + }, + + // Anchors and aliases. + { + "a: &x 1\nb: &y 2\nc: *x\nd: *y\n", + struct{ A, B, C, D int }{1, 2, 1, 2}, + }, + { + "a: &a {c: 1}\nb: *a\n", + struct { + A, B struct { + C int + } + }{struct{ C int }{1}, struct{ C int }{1}}, + }, + { + "a: &a [1, 2]\nb: *a\n", + struct{ B []int }{[]int{1, 2}}, + }, + + { + "tags:\n- hello-world\na: foo", + struct { + Tags []string + A string + }{Tags: []string{"hello-world"}, A: "foo"}, + }, + { + "", + (*struct{})(nil), + }, + { + "{}", struct{}{}, + }, + { + "v: /a/{b}", + map[string]string{"v": "/a/{b}"}, + }, + { + "v: 1[]{},!%?&*", + map[string]string{"v": "1[]{},!%?&*"}, + }, + { + "v: user's item", + map[string]string{"v": "user's item"}, + }, + { + "v: [1,[2,[3,[4,5],6],7],8]", + map[string]interface{}{ + "v": []interface{}{ + 1, + []interface{}{ + 2, + []interface{}{ + 3, + []int{4, 5}, + 6, + }, + 7, + }, + 8, + }, + }, + }, + { + "v: {a: {b: {c: {d: e},f: g},h: i},j: k}", + map[string]interface{}{ + "v": map[string]interface{}{ + "a": map[string]interface{}{ + "b": map[string]interface{}{ + "c": map[string]string{ + "d": "e", + }, + "f": "g", + }, + "h": "i", + }, + "j": "k", + }, + }, + }, + { + `--- +- a: + b: +- c: d +`, + []map[string]interface{}{ + { + "a": map[string]interface{}{ + "b": nil, + }, + }, + { + "c": "d", + }, + }, + }, + { + `--- +a: + b: +c: d +`, + map[string]interface{}{ + "a": map[string]interface{}{ + "b": nil, + }, + "c": "d", + }, + }, + { + `--- +a: +b: +c: +`, + map[string]interface{}{ + "a": nil, + "b": nil, + "c": nil, + }, + }, + { + `--- +a: go test ./... +b: +c: +`, + map[string]interface{}{ + "a": "go test ./...", + "b": nil, + "c": nil, + }, + }, + { + `--- +a: | + hello + ... + world +b: +c: +`, + map[string]interface{}{ + "a": "hello\n...\nworld\n", + "b": nil, + "c": nil, + }, + }, + + // Multi bytes + { + "v: あいうえお\nv2: かきくけこ", + map[string]string{"v": "あいうえお", "v2": "かきくけこ"}, + }, + } + for _, test := range tests { + t.Run(test.source, func(t *testing.T) { + buf := bytes.NewBufferString(test.source) + dec := yaml.NewDecoder(buf) + typ := reflect.ValueOf(test.value).Type() + value := reflect.New(typ) + if err := dec.Decode(value.Interface()); err != nil { + if err == io.EOF { + return + } + t.Fatalf("%s: %+v", test.source, err) + } + actual := fmt.Sprintf("%+v", value.Elem().Interface()) + expect := fmt.Sprintf("%+v", test.value) + if actual != expect { + t.Fatalf("failed to test [%s], actual=[%s], expect=[%s]", test.source, actual, expect) + } + }) + } +} + +func TestDecoder_TypeConversionError(t *testing.T) { + t.Run("type conversion for struct", func(t *testing.T) { + type T struct { + A int + B uint + C float32 + D bool + } + type U struct { + *T `yaml:",inline"` + } + t.Run("string to int", func(t *testing.T) { + var v T + err := yaml.Unmarshal([]byte(`a: str`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal string into Go struct field T.A of type int" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + t.Run("string to bool", func(t *testing.T) { + var v T + err := yaml.Unmarshal([]byte(`d: str`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal string into Go struct field T.D of type bool" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + t.Run("string to int at inline", func(t *testing.T) { + var v U + err := yaml.Unmarshal([]byte(`a: str`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal string into Go struct field U.T.A of type int" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + }) + t.Run("type conversion for array", func(t *testing.T) { + t.Run("string to int", func(t *testing.T) { + var v map[string][]int + err := yaml.Unmarshal([]byte(`v: [A,1,C]`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal string into Go value of type int" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if len(v) == 0 || len(v["v"]) == 0 { + t.Fatal("failed to decode value") + } + if v["v"][0] != 1 { + t.Fatal("failed to decode value") + } + }) + t.Run("string to int", func(t *testing.T) { + var v map[string][]int + err := yaml.Unmarshal([]byte("v:\n - A\n - 1\n - C"), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal string into Go value of type int" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if len(v) == 0 || len(v["v"]) == 0 { + t.Fatal("failed to decode value") + } + if v["v"][0] != 1 { + t.Fatal("failed to decode value") + } + }) + }) + t.Run("overflow error", func(t *testing.T) { + t.Run("negative number to uint", func(t *testing.T) { + var v map[string]uint + err := yaml.Unmarshal([]byte("v: -42"), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal -42 into Go value of type uint ( overflow )" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if v["v"] != 0 { + t.Fatal("failed to decode value") + } + }) + t.Run("negative number to uint64", func(t *testing.T) { + var v map[string]uint64 + err := yaml.Unmarshal([]byte("v: -4294967296"), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal -4294967296 into Go value of type uint64 ( overflow )" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if v["v"] != 0 { + t.Fatal("failed to decode value") + } + }) + t.Run("larger number for int32", func(t *testing.T) { + var v map[string]int32 + err := yaml.Unmarshal([]byte("v: 4294967297"), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal 4294967297 into Go value of type int32 ( overflow )" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if v["v"] != 0 { + t.Fatal("failed to decode value") + } + }) + t.Run("larger number for int8", func(t *testing.T) { + var v map[string]int8 + err := yaml.Unmarshal([]byte("v: 128"), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal 128 into Go value of type int8 ( overflow )" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + if v["v"] != 0 { + t.Fatal("failed to decode value") + } + }) + }) + t.Run("type conversion for time", func(t *testing.T) { + type T struct { + A time.Time + B time.Duration + } + t.Run("int to time", func(t *testing.T) { + var v T + err := yaml.Unmarshal([]byte(`a: 123`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal uint64 into Go struct field T.A of type time.Time" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + t.Run("string to duration", func(t *testing.T) { + var v T + err := yaml.Unmarshal([]byte(`b: str`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := `time: invalid duration "str"` + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + t.Run("int to duration", func(t *testing.T) { + var v T + err := yaml.Unmarshal([]byte(`b: 10`), &v) + if err == nil { + t.Fatal("expected to error") + } + msg := "cannot unmarshal uint64 into Go struct field T.B of type time.Duration" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg) + } + }) + }) +} + +func TestDecoder_AnchorReferenceDirs(t *testing.T) { + buf := bytes.NewBufferString("a: *a\n") + dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata")) + var v struct { + A struct { + B int + C string + } + } + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.A.B != 1 { + t.Fatal("failed to decode by reference dirs") + } + if v.A.C != "hello" { + t.Fatal("failed to decode by reference dirs") + } +} + +func TestDecoder_AnchorReferenceDirsRecursive(t *testing.T) { + buf := bytes.NewBufferString("a: *a\n") + dec := yaml.NewDecoder( + buf, + yaml.RecursiveDir(true), + yaml.ReferenceDirs("testdata"), + ) + var v struct { + A struct { + B int + C string + } + } + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.A.B != 1 { + t.Fatal("failed to decode by reference dirs") + } + if v.A.C != "hello" { + t.Fatal("failed to decode by reference dirs") + } +} + +func TestDecoder_AnchorFiles(t *testing.T) { + buf := bytes.NewBufferString("a: *a\n") + dec := yaml.NewDecoder(buf, yaml.ReferenceFiles("testdata/anchor.yml")) + var v struct { + A struct { + B int + C string + } + } + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.A.B != 1 { + t.Fatal("failed to decode by reference dirs") + } + if v.A.C != "hello" { + t.Fatal("failed to decode by reference dirs") + } +} + +func TestDecodeWithMergeKey(t *testing.T) { + yml := ` +a: &a + b: 1 + c: hello +items: +- <<: *a +- <<: *a + c: world +` + type Item struct { + B int + C string + } + type T struct { + Items []*Item + } + buf := bytes.NewBufferString(yml) + dec := yaml.NewDecoder(buf) + var v T + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if len(v.Items) != 2 { + t.Fatal("failed to decode with merge key") + } + if v.Items[0].B != 1 || v.Items[0].C != "hello" { + t.Fatal("failed to decode with merge key") + } + if v.Items[1].B != 1 || v.Items[1].C != "world" { + t.Fatal("failed to decode with merge key") + } + t.Run("decode with interface{}", func(t *testing.T) { + buf := bytes.NewBufferString(yml) + dec := yaml.NewDecoder(buf) + var v interface{} + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + items := v.(map[string]interface{})["items"].([]interface{}) + if len(items) != 2 { + t.Fatal("failed to decode with merge key") + } + b0 := items[0].(map[string]interface{})["b"] + if _, ok := b0.(uint64); !ok { + t.Fatal("failed to decode with merge key") + } + if b0.(uint64) != 1 { + t.Fatal("failed to decode with merge key") + } + c0 := items[0].(map[string]interface{})["c"] + if _, ok := c0.(string); !ok { + t.Fatal("failed to decode with merge key") + } + if c0.(string) != "hello" { + t.Fatal("failed to decode with merge key") + } + b1 := items[1].(map[string]interface{})["b"] + if _, ok := b1.(uint64); !ok { + t.Fatal("failed to decode with merge key") + } + if b1.(uint64) != 1 { + t.Fatal("failed to decode with merge key") + } + c1 := items[1].(map[string]interface{})["c"] + if _, ok := c1.(string); !ok { + t.Fatal("failed to decode with merge key") + } + if c1.(string) != "world" { + t.Fatal("failed to decode with merge key") + } + }) + t.Run("decode with map", func(t *testing.T) { + var v struct { + Items []map[string]interface{} + } + buf := bytes.NewBufferString(yml) + dec := yaml.NewDecoder(buf) + if err := dec.Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if len(v.Items) != 2 { + t.Fatal("failed to decode with merge key") + } + b0 := v.Items[0]["b"] + if _, ok := b0.(uint64); !ok { + t.Fatal("failed to decode with merge key") + } + if b0.(uint64) != 1 { + t.Fatal("failed to decode with merge key") + } + c0 := v.Items[0]["c"] + if _, ok := c0.(string); !ok { + t.Fatal("failed to decode with merge key") + } + if c0.(string) != "hello" { + t.Fatal("failed to decode with merge key") + } + b1 := v.Items[1]["b"] + if _, ok := b1.(uint64); !ok { + t.Fatal("failed to decode with merge key") + } + if b1.(uint64) != 1 { + t.Fatal("failed to decode with merge key") + } + c1 := v.Items[1]["c"] + if _, ok := c1.(string); !ok { + t.Fatal("failed to decode with merge key") + } + if c1.(string) != "world" { + t.Fatal("failed to decode with merge key") + } + }) +} + +func TestDecoder_Inline(t *testing.T) { + type Base struct { + A int + B string + } + yml := `--- +a: 1 +b: hello +c: true +` + var v struct { + *Base `yaml:",inline"` + C bool + } + if err := yaml.NewDecoder(strings.NewReader(yml)).Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.A != 1 { + t.Fatal("failed to decode with inline key") + } + if v.B != "hello" { + t.Fatal("failed to decode with inline key") + } + if !v.C { + t.Fatal("failed to decode with inline key") + } + + t.Run("multiple inline with strict", func(t *testing.T) { + type Base struct { + A int + B string + } + type Base2 struct { + Base *Base `yaml:",inline"` + } + yml := `--- +a: 1 +b: hello +` + var v struct { + Base2 *Base2 `yaml:",inline"` + } + if err := yaml.NewDecoder(strings.NewReader(yml), yaml.Strict()).Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.Base2.Base.A != 1 { + t.Fatal("failed to decode with inline key") + } + if v.Base2.Base.B != "hello" { + t.Fatal("failed to decode with inline key") + } + }) +} + +func TestDecoder_InlineAndConflictKey(t *testing.T) { + type Base struct { + A int + B string + } + yml := `--- +a: 1 +b: hello +c: true +` + var v struct { + *Base `yaml:",inline"` + A int + C bool + } + if err := yaml.NewDecoder(strings.NewReader(yml)).Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if v.A != 1 { + t.Fatal("failed to decode with inline key") + } + if v.B != "hello" { + t.Fatal("failed to decode with inline key") + } + if !v.C { + t.Fatal("failed to decode with inline key") + } + if v.Base.A != 0 { + t.Fatal("failed to decode with inline key") + } +} + +func TestDecoder_InlineAndWrongTypeStrict(t *testing.T) { + type Base struct { + A int + B string + } + yml := `--- +a: notanint +b: hello +c: true +` + var v struct { + *Base `yaml:",inline"` + C bool + } + err := yaml.NewDecoder(strings.NewReader(yml), yaml.Strict()).Decode(&v) + if err == nil { + t.Fatalf("expected error") + } + + //TODO: properly check if errors are colored/have source + t.Logf("%s", err) + t.Logf("%s", yaml.FormatError(err, true, false)) + t.Logf("%s", yaml.FormatError(err, false, true)) + t.Logf("%s", yaml.FormatError(err, true, true)) +} + +func TestDecoder_InvalidCases(t *testing.T) { + const src = `--- +a: +- b + c: d +` + var v struct { + A []string + } + err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v) + if err == nil { + t.Fatalf("expected error") + } + + if err.Error() != yaml.FormatError(err, false, true) { + t.Logf("err.Error() = %s", err.Error()) + t.Logf("yaml.FormatError(err, false, true) = %s", yaml.FormatError(err, false, true)) + t.Fatal(`err.Error() should match yaml.FormatError(err, false, true)`) + } + + //TODO: properly check if errors are colored/have source + t.Logf("%s", err) + t.Logf("%s", yaml.FormatError(err, true, false)) + t.Logf("%s", yaml.FormatError(err, false, true)) + t.Logf("%s", yaml.FormatError(err, true, true)) +} + +func TestDecoder_JSONTags(t *testing.T) { + var v struct { + A string `json:"a_json"` // no YAML tag + B string `json:"b_json" yaml:"b_yaml"` // both tags + } + + const src = `--- +a_json: a_json_value +b_json: b_json_value +b_yaml: b_yaml_value +` + if err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v); err != nil { + t.Fatalf(`parsing should succeed: %s`, err) + } + + if v.A != "a_json_value" { + t.Fatalf("v.A should be `a_json_value`, got `%s`", v.A) + } + + if v.B != "b_yaml_value" { + t.Fatalf("v.B should be `b_yaml_value`, got `%s`", v.B) + } +} + +func TestDecoder_DisallowUnknownField(t *testing.T) { + t.Run("different level keys with same name", func(t *testing.T) { + var v struct { + C Child `yaml:"c"` + } + yml := `--- +b: 1 +c: + b: 1 +` + + err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v) + if err == nil { + t.Fatalf("error expected") + } + }) + t.Run("inline", func(t *testing.T) { + var v struct { + *Child `yaml:",inline"` + A string `yaml:"a"` + } + yml := `--- +a: a +b: 1 +` + + if err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v); err != nil { + t.Fatalf(`parsing should succeed: %s`, err) + } + if v.A != "a" { + t.Fatalf("v.A should be `a`, got `%s`", v.A) + } + if v.B != 1 { + t.Fatalf("v.B should be 1, got %d", v.B) + } + if v.C != 0 { + t.Fatalf("v.C should be 0, got %d", v.C) + } + }) + t.Run("list", func(t *testing.T) { + type C struct { + Child `yaml:",inline"` + } + + var v struct { + Children []C `yaml:"children"` + } + + yml := `--- +children: +- b: 1 +- b: 2 +` + + if err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v); err != nil { + t.Fatalf(`parsing should succeed: %s`, err) + } + + if len(v.Children) != 2 { + t.Fatalf(`len(v.Children) should be 2, got %d`, len(v.Children)) + } + + if v.Children[0].B != 1 { + t.Fatalf(`v.Children[0].B should be 1, got %d`, v.Children[0].B) + } + + if v.Children[1].B != 2 { + t.Fatalf(`v.Children[1].B should be 2, got %d`, v.Children[1].B) + } + }) +} + +func TestDecoder_DisallowDuplicateKey(t *testing.T) { + yml := ` +a: b +a: c +` + expected := ` +[3:1] duplicate key "a" + 2 | a: b +> 3 | a: c + ^ +` + t.Run("map", func(t *testing.T) { + var v map[string]string + err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowDuplicateKey()).Decode(&v) + if err == nil { + t.Fatal("decoding should fail") + } + actual := "\n" + err.Error() + if expected != actual { + t.Fatalf("expected:[%s] actual:[%s]", expected, actual) + } + }) + t.Run("struct", func(t *testing.T) { + var v struct { + A string + } + err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowDuplicateKey()).Decode(&v) + if err == nil { + t.Fatal("decoding should fail") + } + actual := "\n" + err.Error() + if expected != actual { + t.Fatalf("expected:[%s] actual:[%s]", expected, actual) + } + }) +} + +func TestDecoder_DefaultValues(t *testing.T) { + v := struct { + A string `yaml:"a"` + B string `yaml:"b"` + c string // private + D struct { + E string `yaml:"e"` + F struct { + G string `yaml:"g"` + } `yaml:"f"` + H struct { + I string `yaml:"i"` + } `yaml:",inline"` + } `yaml:"d"` + J struct { + K string `yaml:"k"` + L struct { + M string `yaml:"m"` + } `yaml:"l"` + N struct { + O string `yaml:"o"` + } `yaml:",inline"` + } `yaml:",inline"` + P struct { + Q string `yaml:"q"` + R struct { + S string `yaml:"s"` + } `yaml:"r"` + T struct { + U string `yaml:"u"` + } `yaml:",inline"` + } `yaml:"p"` + V struct { + W string `yaml:"w"` + X struct { + Y string `yaml:"y"` + } `yaml:"x"` + Z struct { + Ä string `yaml:"ä"` + } `yaml:",inline"` + } `yaml:",inline"` + }{ + B: "defaultBValue", + c: "defaultCValue", + } + + v.D.E = "defaultEValue" + v.D.F.G = "defaultGValue" + v.D.H.I = "defaultIValue" + v.J.K = "defaultKValue" + v.J.L.M = "defaultMValue" + v.J.N.O = "defaultOValue" + v.P.R.S = "defaultSValue" + v.P.T.U = "defaultUValue" + v.V.X.Y = "defaultYValue" + v.V.Z.Ä = "defaultÄValue" + + const src = `--- +a: a_value +p: + q: q_value +w: w_value +` + if err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v); err != nil { + t.Fatalf(`parsing should succeed: %s`, err) + } + if v.A != "a_value" { + t.Fatalf("v.A should be `a_value`, got `%s`", v.A) + } + + if v.B != "defaultBValue" { + t.Fatalf("v.B should be `defaultValue`, got `%s`", v.B) + } + + if v.c != "defaultCValue" { + t.Fatalf("v.c should be `defaultCValue`, got `%s`", v.c) + } + + if v.D.E != "defaultEValue" { + t.Fatalf("v.D.E should be `defaultEValue`, got `%s`", v.D.E) + } + + if v.D.F.G != "defaultGValue" { + t.Fatalf("v.D.F.G should be `defaultGValue`, got `%s`", v.D.F.G) + } + + if v.D.H.I != "defaultIValue" { + t.Fatalf("v.D.H.I should be `defaultIValue`, got `%s`", v.D.H.I) + } + + if v.J.K != "defaultKValue" { + t.Fatalf("v.J.K should be `defaultKValue`, got `%s`", v.J.K) + } + + if v.J.L.M != "defaultMValue" { + t.Fatalf("v.J.L.M should be `defaultMValue`, got `%s`", v.J.L.M) + } + + if v.J.N.O != "defaultOValue" { + t.Fatalf("v.J.N.O should be `defaultOValue`, got `%s`", v.J.N.O) + } + + if v.P.Q != "q_value" { + t.Fatalf("v.P.Q should be `q_value`, got `%s`", v.P.Q) + } + + if v.P.R.S != "defaultSValue" { + t.Fatalf("v.P.R.S should be `defaultSValue`, got `%s`", v.P.R.S) + } + + if v.P.T.U != "defaultUValue" { + t.Fatalf("v.P.T.U should be `defaultUValue`, got `%s`", v.P.T.U) + } + + if v.V.W != "w_value" { + t.Fatalf("v.V.W should be `w_value`, got `%s`", v.V.W) + } + + if v.V.X.Y != "defaultYValue" { + t.Fatalf("v.V.X.Y should be `defaultYValue`, got `%s`", v.V.X.Y) + } + + if v.V.Z.Ä != "defaultÄValue" { + t.Fatalf("v.V.Z.Ä should be `defaultÄValue`, got `%s`", v.V.Z.Ä) + } +} + +func Example_YAMLTags() { + yml := `--- +foo: 1 +bar: c +A: 2 +B: d +` + var v struct { + A int `yaml:"foo" json:"A"` + B string `yaml:"bar" json:"B"` + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + log.Fatal(err) + } + fmt.Println(v.A) + fmt.Println(v.B) + // OUTPUT: + // 1 + // c +} + +type useJSONUnmarshalerTest struct { + s string +} + +func (t *useJSONUnmarshalerTest) UnmarshalJSON(b []byte) error { + s, err := strconv.Unquote(string(b)) + if err != nil { + return err + } + t.s = s + return nil +} + +func TestDecoder_UseJSONUnmarshaler(t *testing.T) { + var v useJSONUnmarshalerTest + if err := yaml.UnmarshalWithOptions([]byte(`"a"`), &v, yaml.UseJSONUnmarshaler()); err != nil { + t.Fatal(err) + } + if v.s != "a" { + t.Fatalf("unexpected decoded value: %s", v.s) + } +} + +type unmarshalContext struct { + v int +} + +func (c *unmarshalContext) UnmarshalYAML(ctx context.Context, b []byte) error { + v, ok := ctx.Value("k").(int) + if !ok { + return fmt.Errorf("cannot get valid context") + } + if v != 1 { + return fmt.Errorf("cannot get valid context") + } + if string(b) != "1" { + return fmt.Errorf("cannot get valid bytes") + } + c.v = v + return nil +} + +func Test_UnmarshalerContext(t *testing.T) { + ctx := context.WithValue(context.Background(), "k", 1) + var v unmarshalContext + if err := yaml.UnmarshalContext(ctx, []byte(`1`), &v); err != nil { + t.Fatalf("%+v", err) + } + if v.v != 1 { + t.Fatal("cannot call UnmarshalYAML") + } +} + +func TestDecoder_DecodeFromNode(t *testing.T) { + t.Run("has reference", func(t *testing.T) { + str := ` +anchor: &map + text: hello +map: *map` + var buf bytes.Buffer + dec := yaml.NewDecoder(&buf) + f, err := parser.ParseBytes([]byte(str), 0) + if err != nil { + t.Fatalf("failed to parse: %s", err) + } + type T struct { + Map map[string]string + } + var v T + if err := dec.DecodeFromNode(f.Docs[0].Body, &v); err != nil { + t.Fatalf("failed to decode: %s", err) + } + actual := fmt.Sprintf("%+v", v) + expect := fmt.Sprintf("%+v", T{map[string]string{"text": "hello"}}) + if actual != expect { + t.Fatalf("actual=[%s], expect=[%s]", actual, expect) + } + }) + t.Run("with reference option", func(t *testing.T) { + anchor := strings.NewReader(` +map: &map + text: hello`) + var buf bytes.Buffer + dec := yaml.NewDecoder(&buf, yaml.ReferenceReaders(anchor)) + f, err := parser.ParseBytes([]byte("map: *map"), 0) + if err != nil { + t.Fatalf("failed to parse: %s", err) + } + type T struct { + Map map[string]string + } + var v T + if err := dec.DecodeFromNode(f.Docs[0].Body, &v); err != nil { + t.Fatalf("failed to decode: %s", err) + } + actual := fmt.Sprintf("%+v", v) + expect := fmt.Sprintf("%+v", T{map[string]string{"text": "hello"}}) + if actual != expect { + t.Fatalf("actual=[%s], expect=[%s]", actual, expect) + } + }) + t.Run("value is not pointer", func(t *testing.T) { + var buf bytes.Buffer + var v bool + err := yaml.NewDecoder(&buf).DecodeFromNode(nil, v) + if !xerrors.Is(err, errors.ErrDecodeRequiredPointerType) { + t.Fatalf("unexpected error: %s", err) + } + }) +} + +func Example_JSONTags() { + yml := `--- +foo: 1 +bar: c +` + var v struct { + A int `json:"foo"` + B string `json:"bar"` + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + log.Fatal(err) + } + fmt.Println(v.A) + fmt.Println(v.B) + // OUTPUT: + // 1 + // c +} + +func Example_DisallowUnknownField() { + var v struct { + A string `yaml:"simple"` + C string `yaml:"complicated"` + } + + const src = `--- +simple: string +complecated: string +` + err := yaml.NewDecoder(strings.NewReader(src), yaml.DisallowUnknownField()).Decode(&v) + fmt.Printf("%v\n", err) + + // OUTPUT: + // [3:1] unknown field "complecated" + // 1 | --- + // 2 | simple: string + // > 3 | complecated: string + // ^ +} + +func Example_Unmarshal_Node() { + f, err := parser.ParseBytes([]byte("text: node example"), 0) + if err != nil { + panic(err) + } + var v struct { + Text string `yaml:"text"` + } + if err := yaml.NodeToValue(f.Docs[0].Body, &v); err != nil { + panic(err) + } + fmt.Println(v.Text) + // OUTPUT: + // node example +} + +type unmarshalableYAMLStringValue string + +func (v *unmarshalableYAMLStringValue) UnmarshalYAML(b []byte) error { + var s string + if err := yaml.Unmarshal(b, &s); err != nil { + return err + } + *v = unmarshalableYAMLStringValue(s) + return nil +} + +type unmarshalableTextStringValue string + +func (v *unmarshalableTextStringValue) UnmarshalText(b []byte) error { + *v = unmarshalableTextStringValue(string(b)) + return nil +} + +type unmarshalableStringContainer struct { + A unmarshalableYAMLStringValue `yaml:"a"` + B unmarshalableTextStringValue `yaml:"b"` +} + +func TestUnmarshalableString(t *testing.T) { + t.Run("empty string", func(t *testing.T) { + t.Parallel() + yml := ` +a: "" +b: "" +` + var container unmarshalableStringContainer + if err := yaml.Unmarshal([]byte(yml), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.A != "" { + t.Fatalf("expected empty string, but %q is set", container.A) + } + if container.B != "" { + t.Fatalf("expected empty string, but %q is set", container.B) + } + }) + t.Run("filled string", func(t *testing.T) { + t.Parallel() + yml := ` +a: "aaa" +b: "bbb" +` + var container unmarshalableStringContainer + if err := yaml.Unmarshal([]byte(yml), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.A != "aaa" { + t.Fatalf("expected \"aaa\", but %q is set", container.A) + } + if container.B != "bbb" { + t.Fatalf("expected \"bbb\", but %q is set", container.B) + } + }) + t.Run("single-quoted string", func(t *testing.T) { + t.Parallel() + yml := ` +a: 'aaa' +b: 'bbb' +` + var container unmarshalableStringContainer + if err := yaml.Unmarshal([]byte(yml), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.A != "aaa" { + t.Fatalf("expected \"aaa\", but %q is set", container.A) + } + if container.B != "bbb" { + t.Fatalf("expected \"aaa\", but %q is set", container.B) + } + }) + t.Run("literal", func(t *testing.T) { + t.Parallel() + yml := ` +a: | + a + b + c +b: | + a + b + c +` + var container unmarshalableStringContainer + if err := yaml.Unmarshal([]byte(yml), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.A != "a\nb\nc\n" { + t.Fatalf("expected \"a\nb\nc\n\", but %q is set", container.A) + } + if container.B != "a\nb\nc\n" { + t.Fatalf("expected \"a\nb\nc\n\", but %q is set", container.B) + } + }) + t.Run("anchor/alias", func(t *testing.T) { + yml := ` +a: &x 1 +b: *x +c: &y hello +d: *y +` + var v struct { + A, B, C, D unmarshalableTextStringValue + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatal(err) + } + if v.A != "1" { + t.Fatal("failed to unmarshal") + } + if v.B != "1" { + t.Fatal("failed to unmarshal") + } + if v.C != "hello" { + t.Fatal("failed to unmarshal") + } + if v.D != "hello" { + t.Fatal("failed to unmarshal") + } + }) + t.Run("net.IP", func(t *testing.T) { + yml := ` +a: &a 127.0.0.1 +b: *a +` + var v struct { + A, B net.IP + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatal(err) + } + if v.A.String() != net.IPv4(127, 0, 0, 1).String() { + t.Fatal("failed to unmarshal") + } + if v.B.String() != net.IPv4(127, 0, 0, 1).String() { + t.Fatal("failed to unmarshal") + } + }) +} + +type unmarshalablePtrStringContainer struct { + V *string `yaml:"value"` +} + +func TestUnmarshalablePtrString(t *testing.T) { + t.Run("empty string", func(t *testing.T) { + t.Parallel() + var container unmarshalablePtrStringContainer + if err := yaml.Unmarshal([]byte(`value: ""`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V == nil || *container.V != "" { + t.Fatalf("expected empty string, but %q is set", *container.V) + } + }) + + t.Run("null", func(t *testing.T) { + t.Parallel() + var container unmarshalablePtrStringContainer + if err := yaml.Unmarshal([]byte(`value: null`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V != (*string)(nil) { + t.Fatalf("expected nil, but %q is set", *container.V) + } + }) +} + +type unmarshalableIntValue int + +func (v *unmarshalableIntValue) UnmarshalYAML(raw []byte) error { + i, err := strconv.Atoi(string(raw)) + if err != nil { + return err + } + *v = unmarshalableIntValue(i) + return nil +} + +type unmarshalableIntContainer struct { + V unmarshalableIntValue `yaml:"value"` +} + +func TestUnmarshalableInt(t *testing.T) { + t.Run("empty int", func(t *testing.T) { + t.Parallel() + var container unmarshalableIntContainer + if err := yaml.Unmarshal([]byte(``), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V != 0 { + t.Fatalf("expected empty int, but %d is set", container.V) + } + }) + t.Run("filled int", func(t *testing.T) { + t.Parallel() + var container unmarshalableIntContainer + if err := yaml.Unmarshal([]byte(`value: 9`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V != 9 { + t.Fatalf("expected 9, but %d is set", container.V) + } + }) + t.Run("filled number", func(t *testing.T) { + t.Parallel() + var container unmarshalableIntContainer + if err := yaml.Unmarshal([]byte(`value: 9`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V != 9 { + t.Fatalf("expected 9, but %d is set", container.V) + } + }) +} + +type unmarshalablePtrIntContainer struct { + V *int `yaml:"value"` +} + +func TestUnmarshalablePtrInt(t *testing.T) { + t.Run("empty int", func(t *testing.T) { + t.Parallel() + var container unmarshalablePtrIntContainer + if err := yaml.Unmarshal([]byte(`value: 0`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V == nil || *container.V != 0 { + t.Fatalf("expected 0, but %q is set", *container.V) + } + }) + + t.Run("null", func(t *testing.T) { + t.Parallel() + var container unmarshalablePtrIntContainer + if err := yaml.Unmarshal([]byte(`value: null`), &container); err != nil { + t.Fatalf("failed to unmarshal %v", err) + } + if container.V != (*int)(nil) { + t.Fatalf("expected nil, but %q is set", *container.V) + } + }) +} + +type literalContainer struct { + v string +} + +func (c *literalContainer) UnmarshalYAML(v []byte) error { + var lit string + if err := yaml.Unmarshal(v, &lit); err != nil { + return err + } + c.v = lit + return nil +} + +func TestDecode_Literal(t *testing.T) { + yml := `--- +value: | + { + "key": "value" + } +` + var v map[string]*literalContainer + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatalf("failed to unmarshal %+v", err) + } + if v["value"] == nil { + t.Fatal("failed to unmarshal literal with bytes unmarshaler") + } + if v["value"].v == "" { + t.Fatal("failed to unmarshal literal with bytes unmarshaler") + } +} + +func TestDecoder_UseOrderedMap(t *testing.T) { + yml := ` +a: b +c: d +e: + f: g + h: i +j: k +` + var v interface{} + if err := yaml.NewDecoder(strings.NewReader(yml), yaml.UseOrderedMap()).Decode(&v); err != nil { + t.Fatalf("%+v", err) + } + if _, ok := v.(yaml.MapSlice); !ok { + t.Fatalf("failed to convert to ordered map: %T", v) + } + bytes, err := yaml.Marshal(v) + if err != nil { + t.Fatalf("%+v", err) + } + if string(yml) != "\n"+string(bytes) { + t.Fatalf("expected:[%s] actual:[%s]", string(yml), "\n"+string(bytes)) + } +} + +func TestDecoder_Stream(t *testing.T) { + yml := ` +--- +a: b +c: d +--- +e: f +g: h +--- +i: j +k: l +` + dec := yaml.NewDecoder(strings.NewReader(yml)) + values := []map[string]string{} + for { + var v map[string]string + if err := dec.Decode(&v); err != nil { + if err == io.EOF { + break + } + t.Fatalf("%+v", err) + } + values = append(values, v) + } + if len(values) != 3 { + t.Fatal("failed to stream decoding") + } + if values[0]["a"] != "b" { + t.Fatal("failed to stream decoding") + } + if values[1]["e"] != "f" { + t.Fatal("failed to stream decoding") + } + if values[2]["i"] != "j" { + t.Fatal("failed to stream decoding") + } +} + +type unmarshalYAMLWithAliasString string + +func (v *unmarshalYAMLWithAliasString) UnmarshalYAML(b []byte) error { + var s string + if err := yaml.Unmarshal(b, &s); err != nil { + return err + } + *v = unmarshalYAMLWithAliasString(s) + return nil +} + +type unmarshalYAMLWithAliasMap map[string]interface{} + +func (v *unmarshalYAMLWithAliasMap) UnmarshalYAML(b []byte) error { + var m map[string]interface{} + if err := yaml.Unmarshal(b, &m); err != nil { + return err + } + *v = unmarshalYAMLWithAliasMap(m) + return nil +} + +func TestDecoder_UnmarshalYAMLWithAlias(t *testing.T) { + type value struct { + String unmarshalYAMLWithAliasString + Map unmarshalYAMLWithAliasMap + } + tests := []struct { + name string + yaml string + expectedValue value + err string + }{ + { + name: "ok", + yaml: ` +anchors: + w: &w "\"hello\" \"world\"" + map: &x + a: b + c: d + d: *w +string: *w +map: + <<: *x + e: f +`, + expectedValue: value{ + String: unmarshalYAMLWithAliasString(`"hello" "world"`), + Map: unmarshalYAMLWithAliasMap(map[string]interface{}{ + "a": "b", + "c": "d", + "d": `"hello" "world"`, + "e": "f", + }), + }, + }, + { + name: "unknown alias", + yaml: ` +anchors: + w: &w "\"hello\" \"world\"" + map: &x + a: b + c: d + d: *w +string: *y +map: + <<: *z + e: f +`, + err: "cannot find anchor by alias name y", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var v value + err := yaml.Unmarshal([]byte(test.yaml), &v) + + if test.err != "" { + if err == nil { + t.Fatal("expected to error") + } + if !strings.Contains(err.Error(), test.err) { + t.Fatalf("expected error message: %s to contain: %s", err.Error(), test.err) + } + } else { + if err != nil { + t.Fatalf("%+v", err) + } + if !reflect.DeepEqual(test.expectedValue, v) { + t.Fatalf("non matching values:\nexpected[%s]\ngot [%s]", test.expectedValue, v) + } + } + }) + } +} + +type unmarshalString string + +func (u *unmarshalString) UnmarshalYAML(b []byte) error { + *u = unmarshalString(string(b)) + return nil +} + +type unmarshalList struct { + v []map[string]unmarshalString +} + +func (u *unmarshalList) UnmarshalYAML(b []byte) error { + expected := ` + - b: c + d: | + hello + + hello + f: g + - h: i` + actual := "\n" + string(b) + if expected != actual { + return xerrors.Errorf("unexpected bytes: expected [%q] but got [%q]", expected, actual) + } + var v []map[string]unmarshalString + if err := yaml.Unmarshal(b, &v); err != nil { + return err + } + u.v = v + return nil +} + +func TestDecoder_UnmarshalBytesWithSeparatedList(t *testing.T) { + yml := ` +a: + - b: c + d: | + hello + + hello + f: g + - h: i +` + var v struct { + A unmarshalList + } + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatal(err) + } + if len(v.A.v) != 2 { + t.Fatalf("failed to unmarshal %+v", v) + } + if len(v.A.v[0]) != 3 { + t.Fatalf("failed to unmarshal %+v", v.A.v[0]) + } + if len(v.A.v[1]) != 1 { + t.Fatalf("failed to unmarshal %+v", v.A.v[1]) + } +} + +func TestDecoder_LiteralWithNewLine(t *testing.T) { + type A struct { + Node string `yaml:"b"` + LastNode string `yaml:"last"` + } + tests := []A{ + A{ + Node: "hello\nworld", + }, + A{ + Node: "hello\nworld\n", + }, + A{ + Node: "hello\nworld\n\n", + }, + A{ + LastNode: "hello\nworld", + }, + A{ + LastNode: "hello\nworld\n", + }, + A{ + LastNode: "hello\nworld\n\n", + }, + } + // struct(want) -> Marshal -> Unmarchal -> struct(got) + for _, want := range tests { + bytes, _ := yaml.Marshal(want) + got := A{} + if err := yaml.Unmarshal(bytes, &got); err != nil { + t.Fatal(err) + } + if want.Node != got.Node { + t.Fatalf("expected:%q but got %q", want.Node, got.Node) + } + if want.LastNode != got.LastNode { + t.Fatalf("expected:%q but got %q", want.LastNode, got.LastNode) + } + } +} + +func TestDecoder_TabCharacterAtRight(t *testing.T) { + yml := ` +- a: [2 , 2] + b: [2 , 2] + c: [2 , 2]` + var v []map[string][]int + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatal(err) + } + if len(v) != 1 { + t.Fatalf("failed to unmarshal %+v", v) + } + if len(v[0]) != 3 { + t.Fatalf("failed to unmarshal %+v", v) + } +} + +func TestDecoder_Canonical(t *testing.T) { + yml := ` +!!map { + ? !!str "explicit":!!str "entry", + ? !!str "implicit" : !!str "entry", + ? !!null "" : !!null "", +} +` + var v interface{} + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatalf("%+v", err) + } + m, ok := v.(map[string]interface{}) + if !ok { + t.Fatalf("failed to decode canonical yaml: %+v", v) + } + if m["explicit"] != "entry" { + t.Fatalf("failed to decode canonical yaml: %+v", m) + } + if m["implicit"] != "entry" { + t.Fatalf("failed to decode canonical yaml: %+v", m) + } + if m["null"] != nil { + t.Fatalf("failed to decode canonical yaml: %+v", m) + } +} + +func TestDecoder_DecodeFromFile(t *testing.T) { + yml := ` +a: b +c: d +` + file, err := parser.ParseBytes([]byte(yml), 0) + if err != nil { + t.Fatal(err) + } + var v map[string]string + if err := yaml.NewDecoder(file).Decode(&v); err != nil { + t.Fatal(err) + } + if len(v) != 2 { + t.Fatal("failed to decode from ast.File") + } + if v["a"] != "b" { + t.Fatal("failed to decode from ast.File") + } + if v["c"] != "d" { + t.Fatal("failed to decode from ast.File") + } +} + +func TestDecoder_DecodeWithNode(t *testing.T) { + t.Run("abstract node", func(t *testing.T) { + type T struct { + Text ast.Node `yaml:"text"` + } + var v T + if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil { + t.Fatalf("%+v", err) + } + expected := "hello" + got := v.Text.String() + if expected != got { + t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got) + } + }) + t.Run("concrete node", func(t *testing.T) { + type T struct { + Text *ast.StringNode `yaml:"text"` + } + var v T + if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil { + t.Fatalf("%+v", err) + } + expected := "hello" + got := v.Text.String() + if expected != got { + t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got) + } + }) +} + +func TestRoundtripAnchorAlias(t *testing.T) { + t.Run("irreversible", func(t *testing.T) { + type foo struct { + K1 string + K2 string + } + + type bar struct { + K1 string + K3 string + } + + type doc struct { + Foo foo + Bar bar + } + yml := ` +foo: + <<: &test-anchor + k1: "One" + k2: "Two" + +bar: + <<: *test-anchor + k3: "Three" +` + var v doc + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatalf("%+v", err) + } + bytes, err := yaml.Marshal(v) + if err != nil { + t.Fatalf("%+v", err) + } + expected := ` +foo: + k1: One + k2: Two +bar: + k1: One + k3: Three +` + got := "\n" + string(bytes) + if expected != got { + t.Fatalf("expected:[%s] but got [%s]", expected, got) + } + }) + t.Run("reversible", func(t *testing.T) { + type TestAnchor struct { + K1 string + } + type foo struct { + *TestAnchor `yaml:",inline,alias"` + K2 string + } + type bar struct { + *TestAnchor `yaml:",inline,alias"` + K3 string + } + type doc struct { + TestAnchor *TestAnchor `yaml:"test-anchor,anchor"` + Foo foo + Bar bar + } + yml := ` +test-anchor: &test-anchor + k1: One +foo: + <<: *test-anchor + k2: Two +bar: + <<: *test-anchor + k3: Three +` + var v doc + if err := yaml.Unmarshal([]byte(yml), &v); err != nil { + t.Fatalf("%+v", err) + } + bytes, err := yaml.Marshal(v) + if err != nil { + t.Fatalf("%+v", err) + } + got := "\n" + string(bytes) + if yml != got { + t.Fatalf("expected:[%s] but got [%s]", yml, got) + } + }) +} |