From 201d5dd422220e157a53633b139fdc8401b4dd2b Mon Sep 17 00:00:00 2001 From: Vincent Serpoul Date: Wed, 28 Apr 2021 08:29:00 +0800 Subject: [PATCH] golangci-lint: misc (#529) --- marshaler.go | 2 + marshaler_test.go | 6 +++ parser.go | 13 +++--- scanner.go | 92 +++++++++++++++++++++---------------- unmarshaler.go | 107 ++++++++++++++++++++++++++++++++++---------- unmarshaler_test.go | 99 ++++++++++++++++++++++++++++++++++------ 6 files changed, 235 insertions(+), 84 deletions(-) diff --git a/marshaler.go b/marshaler.go index b302893..172c033 100644 --- a/marshaler.go +++ b/marshaler.go @@ -566,6 +566,7 @@ func fieldBoolTag(field reflect.StructField, tag string) bool { return ok && x == "true" } +//nolint:cyclop func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { var err error @@ -580,6 +581,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro if err != nil { return nil, err } + if enc.indentTables && len(ctx.parentKey) > 0 { ctx.indent++ } diff --git a/marshaler_test.go b/marshaler_test.go index 78d452e..470bead 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -394,7 +394,10 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) { assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset)) } +//nolint:funlen func TestMarshalIndentTables(t *testing.T) { + t.Parallel() + examples := []struct { desc string v interface{} @@ -443,7 +446,10 @@ root = 'value0' } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + var buf strings.Builder enc := toml.NewEncoder(&buf) enc.SetIndentTables(true) diff --git a/parser.go b/parser.go index 7191336..1ff2272 100644 --- a/parser.go +++ b/parser.go @@ -98,9 +98,9 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } if b[0] == '#' { - _, rest, err := scanComment(b) + _, rest := scanComment(b) - return ref, rest, err + return ref, rest, nil } if b[0] == '\n' || b[0] == '\r' { @@ -121,9 +121,9 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, rest, err := scanComment(b) + _, rest := scanComment(b) - return ref, rest, err + return ref, rest, nil } return ref, b, nil @@ -456,10 +456,7 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) - if err != nil { - return nil, err - } + _, b = scanComment(b) } if len(b) == 0 { diff --git a/scanner.go b/scanner.go index 8ba6f99..b63ccb4 100644 --- a/scanner.go +++ b/scanner.go @@ -1,9 +1,12 @@ package toml -import "fmt" +import ( + "errors" +) func scanFollows(b []byte, pattern string) bool { n := len(pattern) + return len(b) >= n && string(b[:n]) == pattern } @@ -38,6 +41,7 @@ func scanUnquotedKey(b []byte) ([]byte, []byte, error) { return b[:i], b[i:], nil } } + return b, b[len(b):], nil } @@ -57,38 +61,44 @@ func scanLiteralString(b []byte) ([]byte, []byte, error) { return nil, nil, newDecodeError(b[i:i+1], "literal strings cannot have new lines") } } + return nil, nil, newDecodeError(b[len(b):], "unterminated literal string") } func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { - //ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body - //ml-literal-string-delim - //ml-literal-string-delim = 3apostrophe - //ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + // ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + // ml-literal-string-delim + // ml-literal-string-delim = 3apostrophe + // ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] // - //mll-content = mll-char / newline - //mll-char = %x09 / %x20-26 / %x28-7E / non-ascii - //mll-quotes = 1*2apostrophe + // mll-content = mll-char / newline + // mll-char = %x09 / %x20-26 / %x28-7E / non-ascii + // mll-quotes = 1*2apostrophe for i := 3; i < len(b); i++ { - switch b[i] { - case '\'': - if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { - return b[:i+3], b[i+3:], nil - } + if b[i] == '\'' && scanFollowsMultilineLiteralStringDelimiter(b[i:]) { + return b[:i+3], b[i+3:], nil } } return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) } +var ( + errWindowsNewLineMissing = errors.New(`windows new line missing \n`) + errWindowsNewLineCRLF = errors.New(`windows new line should be \r\n`) +) + func scanWindowsNewline(b []byte) ([]byte, []byte, error) { - if len(b) < 2 { - return nil, nil, fmt.Errorf(`windows new line missing \n`) + const lenLF = 2 + if len(b) < lenLF { + return nil, nil, errWindowsNewLineMissing } + if b[1] != '\n' { - return nil, nil, fmt.Errorf(`windows new line should be \r\n`) + return nil, nil, errWindowsNewLineCRLF } - return b[:2], b[2:], nil + + return b[:lenLF], b[lenLF:], nil } func scanWhitespace(b []byte) ([]byte, []byte) { @@ -100,27 +110,31 @@ func scanWhitespace(b []byte) ([]byte, []byte) { return b[:i], b[i:] } } + return b, b[len(b):] } -func scanComment(b []byte) ([]byte, []byte, error) { - //;; Comment +//nolint:unparam +func scanComment(b []byte) ([]byte, []byte) { + // ;; Comment // - //comment-start-symbol = %x23 ; # - //non-ascii = %x80-D7FF / %xE000-10FFFF - //non-eol = %x09 / %x20-7F / non-ascii + // comment-start-symbol = %x23 ; # + // non-ascii = %x80-D7FF / %xE000-10FFFF + // non-eol = %x09 / %x20-7F / non-ascii // - //comment = comment-start-symbol *non-eol - + // comment = comment-start-symbol *non-eol for i := 1; i < len(b); i++ { - switch b[i] { - case '\n': - return b[:i], b[i:], nil + if b[i] == '\n' { + return b[:i], b[i:] } } - return b, nil, nil + + return b, nil } +var errBasicLineNotTerminatedByQuote = errors.New(`basic string not terminated by "`) + +//nolint:godox // TODO perform validation on the string? func scanBasicString(b []byte) ([]byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark @@ -142,22 +156,22 @@ func scanBasicString(b []byte) ([]byte, []byte, error) { } } - return nil, nil, fmt.Errorf(`basic string not terminated by "`) + return nil, nil, errBasicLineNotTerminatedByQuote } +//nolint:godox // TODO perform validation on the string? func scanMultilineBasicString(b []byte) ([]byte, []byte, error) { - //ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body - //ml-basic-string-delim - //ml-basic-string-delim = 3quotation-mark - //ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + // ml-basic-string-delim + // ml-basic-string-delim = 3quotation-mark + // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] // - //mlb-content = mlb-char / newline / mlb-escaped-nl - //mlb-char = mlb-unescaped / escaped - //mlb-quotes = 1*2quotation-mark - //mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii - //mlb-escaped-nl = escape ws newline *( wschar / newline ) - + // mlb-content = mlb-char / newline / mlb-escaped-nl + // mlb-char = mlb-unescaped / escaped + // mlb-quotes = 1*2quotation-mark + // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // mlb-escaped-nl = escape ws newline *( wschar / newline ) for i := 3; i < len(b); i++ { switch b[i] { case '"': diff --git a/unmarshaler.go b/unmarshaler.go index b4d5954..5460457 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -2,6 +2,7 @@ package toml import ( "encoding" + "errors" "fmt" "io" "io/ioutil" @@ -17,6 +18,7 @@ func Unmarshal(data []byte, v interface{}) error { p := parser{} p.Reset(data) d := decoder{} + return d.FromParser(&p, v) } @@ -54,8 +56,9 @@ func (d *Decoder) SetStrict(strict bool) { func (d *Decoder) Decode(v interface{}) error { b, err := ioutil.ReadAll(d.r) if err != nil { - return err + return fmt.Errorf("Decode: %w", err) } + p := parser{} p.Reset(b) dec := decoder{ @@ -63,6 +66,7 @@ func (d *Decoder) Decode(v interface{}) error { Enabled: d.strict, }, } + return dec.FromParser(&p, v) } @@ -90,19 +94,19 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { idx++ d.arrayIndexes[v] = idx } + return idx } func (d *decoder) FromParser(p *parser, v interface{}) error { err := d.fromParser(p, v) - if err != nil { - de, ok := err.(*decodeError) - if ok { - err = wrapDecodeError(p.data, de) - } - } if err == nil { - err = d.strict.Error(p.data) + return d.strict.Error(p.data) + } + + var e *decodeError + if errors.As(err, &e) { + return wrapDecodeError(p.data, e) } return err @@ -110,29 +114,43 @@ func (d *decoder) FromParser(p *parser, v interface{}) error { func keyLocation(node ast.Node) []byte { k := node.Key() + hasOne := k.Next() if !hasOne { panic("should not be called with empty key") } + start := k.Node().Data end := k.Node().Data + for k.Next() { end = k.Node().Data } + return unsafe.BytesRange(start, end) } +var ( + errFromParserExpectingPointer = errors.New("expecting a pointer as target") + errFromParserExpectingNonNilPointer = errors.New("expecting non nil pointer as target") +) + +//nolint:funlen,cyclop func (d *decoder) fromParser(p *parser, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { - return fmt.Errorf("need to target a pointer, not %s", r.Kind()) - } - if r.IsNil() { - return fmt.Errorf("target pointer must be non-nil") + return fmt.Errorf("fromParser: %w, not %s", errFromParserExpectingPointer, r.Kind()) } - var skipUntilTable bool - var root target = valueTarget(r.Elem()) + if r.IsNil() { + return errFromParserExpectingNonNilPointer + } + + var ( + skipUntilTable bool + root target = valueTarget(r.Elem()) + ) + current := root for p.NextExpression() { @@ -144,10 +162,11 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { err := d.seen.CheckExpression(node) if err != nil { - return err + return fmt.Errorf("fromParser: %w", err) } var found bool + switch node.Kind { case ast.KeyValue: err = d.unmarshalKeyValue(current, node) @@ -167,7 +186,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { d.strict.EnterArrayTable(node) current, found, err = d.scopeWithArrayTable(root, node.Key()) default: - panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + panic(fmt.Sprintf("fromParser: this should not be a top level node type: %s", node.Kind)) } if err != nil { @@ -176,6 +195,7 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { if !found { skipUntilTable = true + d.strict.MissingTable(node) } } @@ -192,38 +212,49 @@ func (d *decoder) fromParser(p *parser, v interface{}) error { // When encountering slices, it should always use its last element, and error // if the slice does not have any. func (d *decoder) scopeWithKey(x target, key ast.Iterator) (target, bool, error) { - var err error - found := true + var ( + err error + found bool + ) for key.Next() { n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } } + return x, true, nil } +//nolint:cyclop // scopeWithArrayTable performs target scoping when unmarshaling an // ast.ArrayTable node. // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, error) { - var err error - found := true + var ( + err error + found bool + ) + for key.Next() { n := key.Node() if !n.Next().Valid() { // want to stop at one before last break } + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err } } + n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return x, found, err @@ -236,6 +267,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, if err != nil { return x, false, err } + v = x.get() } @@ -244,6 +276,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, if err != nil { return x, found, err } + v = x.get() } @@ -252,6 +285,7 @@ func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, x, err = scopeSlice(true, x) case reflect.Array: x, err = d.scopeArray(true, x) + default: } return x, found, err @@ -295,22 +329,28 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { if v.Type().Implements(textUnmarshalerType) { return true, v.Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) } + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) } + return false, nil } +//nolint:cyclop func (d *decoder) unmarshalValue(x target, node ast.Node) error { v := x.get() + if v.Kind() == reflect.Ptr { if !v.Elem().IsValid() { err := x.set(reflect.New(v.Type().Elem())) if err != nil { return err } + v = x.get() } + return d.unmarshalValue(valueTarget(v.Elem()), node) } @@ -339,37 +379,44 @@ func (d *decoder) unmarshalValue(x target, node ast.Node) error { case ast.LocalDate: return unmarshalLocalDate(x, node) default: - panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) + panic(fmt.Sprintf("unmarshalValue: unhandled unmarshalValue kind %s", node.Kind)) } } func unmarshalLocalDate(x target, node ast.Node) error { assertNode(ast.LocalDate, node) + v, err := parseLocalDate(node.Data) if err != nil { return err } + return setDate(x, v) } func unmarshalLocalDateTime(x target, node ast.Node) error { assertNode(ast.LocalDateTime, node) + v, rest, err := parseLocalDateTime(node.Data) if err != nil { return err } + if len(rest) > 0 { return newDecodeError(rest, "extra characters at the end of a local date time") } + return setLocalDateTime(x, v) } func unmarshalDateTime(x target, node ast.Node) error { assertNode(ast.DateTime, node) + v, err := parseDateTime(node.Data) if err != nil { return err } + return setDateTime(x, v) } @@ -378,6 +425,7 @@ func setLocalDateTime(x target, v LocalDateTime) error { cast := v.In(time.Local) return setDateTime(x, cast) } + return x.set(reflect.ValueOf(v)) } @@ -398,21 +446,25 @@ func setDate(x target, v LocalDate) error { func unmarshalString(x target, node ast.Node) error { assertNode(ast.String, node) + return setString(x, string(node.Data)) } func unmarshalBool(x target, node ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' + return setBool(x, v) } func unmarshalInteger(x target, node ast.Node) error { assertNode(ast.Integer, node) + v, err := parseInteger(node.Data) if err != nil { return err } + return setInt64(x, v) } @@ -422,6 +474,7 @@ func unmarshalFloat(x target, node ast.Node) error { if err != nil { return err } + return setFloat64(x, v) } @@ -433,11 +486,13 @@ func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { it := node.Children() for it.Next() { n := it.Node() + err := d.unmarshalKeyValue(x, n) if err != nil { return err } } + return nil } @@ -449,30 +504,36 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error { return err } - it := node.Children() idx := 0 + + it := node.Children() for it.Next() { n := it.Node() + v, err := elementAt(x, idx) if err != nil { return err } + if v == nil { // when we go out of bound for an array just stop processing it to // mimic encoding/json break } + err = d.unmarshalValue(v, n) if err != nil { return err } + idx++ } + return nil } func assertNode(expected ast.Kind, node ast.Node) { if node.Kind != expected { - panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) + panic(fmt.Sprintf("expected node of kind %s, not %s", expected, node.Kind)) } } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index b0f62d2..45f6e58 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1,6 +1,7 @@ package toml_test import ( + "errors" "fmt" "math" "strconv" @@ -14,6 +15,8 @@ import ( ) func TestUnmarshal_Integers(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -62,7 +65,10 @@ func TestUnmarshal_Integers(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) @@ -71,7 +77,10 @@ func TestUnmarshal_Integers(t *testing.T) { } } +//nolint:funlen func TestUnmarshal_Floats(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -161,7 +170,10 @@ func TestUnmarshal_Floats(t *testing.T) { } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + doc := doc{} err := toml.Unmarshal([]byte(`A = `+e.input), &doc) require.NoError(t, err) @@ -174,7 +186,10 @@ func TestUnmarshal_Floats(t *testing.T) { } } +//nolint:funlen func TestUnmarshal(t *testing.T) { + t.Parallel() + type test struct { target interface{} expected interface{} @@ -193,6 +208,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A string } + return test{ target: &doc{}, expected: &doc{A: "foo"}, @@ -205,6 +221,7 @@ func TestUnmarshal(t *testing.T) { fruit . flavor = "banana"`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, expected: &map[string]interface{}{ @@ -222,6 +239,7 @@ func TestUnmarshal(t *testing.T) { "\"b\"" = 2`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, expected: &map[string]interface{}{ @@ -239,6 +257,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A string } + return test{ target: &doc{}, expected: &doc{A: "Test"}, @@ -252,6 +271,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A bool } + return test{ target: &doc{}, expected: &doc{A: true}, @@ -265,6 +285,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A bool } + return test{ target: &doc{A: true}, expected: &doc{A: false}, @@ -278,6 +299,7 @@ func TestUnmarshal(t *testing.T) { type doc struct { A []string } + return test{ target: &doc{}, expected: &doc{A: []string{"foo", "bar"}}, @@ -295,6 +317,7 @@ B = "data"`, type doc struct { A A } + return test{ target: &doc{}, expected: &doc{A: A{B: "data"}}, @@ -306,6 +329,7 @@ B = "data"`, input: `[A]`, gen: func() test { var v map[string]interface{} + return test{ target: &v, expected: &map[string]interface{}{`A`: map[string]interface{}{}}, @@ -323,6 +347,7 @@ B = "data"`, type doc struct { Name name } + return test{ target: &doc{}, expected: &doc{Name: name{ @@ -337,6 +362,7 @@ B = "data"`, input: `A = {}`, gen: func() test { var v map[string]interface{} + return test{ target: &v, expected: &map[string]interface{}{`A`: map[string]interface{}{}}, @@ -354,6 +380,7 @@ B = "data"`, type doc struct { Names []name } + return test{ target: &doc{}, expected: &doc{ @@ -376,6 +403,7 @@ B = "data"`, input: `A = "foo"`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -390,6 +418,7 @@ B = "data"`, B = 42`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -404,6 +433,7 @@ B = "data"`, input: `A = ["foo", "bar"]`, gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -417,6 +447,7 @@ B = "data"`, input: `A = "foo"`, gen: func() test { doc := map[string]string{} + return test{ target: &doc, expected: &map[string]string{ @@ -430,6 +461,7 @@ B = "data"`, input: `A = 42.0`, gen: func() test { doc := map[string]string{} + return test{ target: &doc, err: true, @@ -447,6 +479,7 @@ B = "data"`, type Doc struct { First []First } + return test{ target: &Doc{}, expected: &Doc{ @@ -464,13 +497,13 @@ B = "data"`, input: `[[Products]] Name = "Hammer" Sku = 738594937 - + [[Products]] # empty table within the array - + [[Products]] Name = "Nail" Sku = 284758393 - + Color = "gray"`, gen: func() test { type Product struct { @@ -481,6 +514,7 @@ B = "data"`, type Doc struct { Products []Product } + return test{ target: &Doc{}, expected: &Doc{ @@ -498,13 +532,13 @@ B = "data"`, input: `[[Products]] Name = "Hammer" Sku = 738594937 - + [[Products]] # empty table within the array - + [[Products]] Name = "Nail" Sku = 284758393 - + Color = "gray"`, gen: func() test { return test{ @@ -654,6 +688,7 @@ B = "data"`, A *[]*string } hello := "Hello" + return test{ target: &doc{}, expected: &doc{ @@ -673,6 +708,7 @@ B = "data"`, type doc struct { A interface{} } + return test{ target: &doc{ A: inner{ @@ -700,6 +736,7 @@ B = "data"`, type doc struct { A [4]inner } + return test{ target: &doc{}, expected: &doc{ @@ -716,6 +753,7 @@ B = "data"`, input: "A = 1\r\n\r\nB = 2", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, expected: &map[string]interface{}{ @@ -730,6 +768,7 @@ B = "data"`, input: "A = 1\r", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, err: true, @@ -741,6 +780,7 @@ B = "data"`, input: "A = 1\rB = 2", gen: func() test { doc := map[string]interface{}{} + return test{ target: &doc, err: true, @@ -752,6 +792,7 @@ B = "data"`, input: `a = 1z = 2`, gen: func() test { m := map[string]interface{}{} + return test{ target: &m, err: true, @@ -761,7 +802,10 @@ B = "data"`, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + if e.skip { t.Skip() } @@ -791,7 +835,7 @@ func (i Integer484) MarshalText() ([]byte, error) { func (i *Integer484) UnmarshalText(data []byte) error { conv, err := strconv.Atoi(string(data)) if err != nil { - return err + return fmt.Errorf("UnmarshalText: %w", err) } i.Value = conv return nil @@ -803,6 +847,7 @@ type Config484 struct { func TestIssue484(t *testing.T) { raw := []byte(`integers = ["1","2","3","100"]`) + var cfg Config484 err := toml.Unmarshal(raw, &cfg) require.NoError(t, err) @@ -864,6 +909,7 @@ func TestIssue494(t *testing.T) { foo = 2021-04-08 bar = 2021-04-08 ` + type s struct { Foo time.Time `toml:"foo"` Bar time.Time `toml:"bar"` @@ -880,7 +926,10 @@ func TestIssue507(t *testing.T) { require.Error(t, err) } +//nolint:funlen func TestUnmarshalDecodeErrors(t *testing.T) { + t.Parallel() + examples := []struct { desc string data string @@ -955,14 +1004,19 @@ world'`, } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + m := map[string]interface{}{} err := toml.Unmarshal([]byte(e.data), &m) require.Error(t, err) - de, ok := err.(*toml.DecodeError) - if !ok { + + var de *toml.DecodeError + if !errors.As(err, &de) { t.Fatalf("err should have been a *toml.DecodeError, but got %s (%T)", err, err) } + if e.msg != "" { t.Log("\n" + de.String()) require.Equal(t, e.msg, de.Error()) @@ -971,6 +1025,7 @@ world'`, } } +//nolint:funlen func TestLocalDateTime(t *testing.T) { t.Parallel() @@ -1058,6 +1113,7 @@ func TestIssue508(t *testing.T) { type head struct { Title string `toml:"title"` } + type text struct { head } @@ -1070,7 +1126,10 @@ func TestIssue508(t *testing.T) { require.Equal(t, "This is a title", t1.head.Title) } +//nolint:funlen func TestDecoderStrict(t *testing.T) { + t.Parallel() + examples := []struct { desc string input string @@ -1139,7 +1198,10 @@ bar = 42 } for _, e := range examples { + e := e t.Run(e.desc, func(t *testing.T) { + t.Parallel() + r := strings.NewReader(e.input) d := toml.NewDecoder(r) d.SetStrict(true) @@ -1148,8 +1210,13 @@ bar = 42 x = &struct{}{} } err := d.Decode(x) - details := err.(*toml.StrictMissingError) - equalStringsIgnoreNewlines(t, e.expected, details.String()) + + var tsm *toml.StrictMissingError + if errors.As(err, &tsm) { + equalStringsIgnoreNewlines(t, e.expected, tsm.String()) + } else { + t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err) + } }) } } @@ -1171,11 +1238,15 @@ key3 = "value3" err := d.Decode(&s) fmt.Println(err.Error()) - // Output: strict mode: fields in the document are missing in the target struct - details := err.(*toml.StrictMissingError) + var details *toml.StrictMissingError + if !errors.As(err, &details) { + panic(fmt.Sprintf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err)) + } + fmt.Println(details.String()) - // Ouput: + // Output: + // strict mode: fields in the document are missing in the target struct // 2| key1 = "value1" // 3| key2 = "value2" // | ~~~~ missing field