Decoder: prevent duplicates of inline tables (#667)
* seen: prevent duplicates of inline tables * Provide clearer error message for redefined keys For example: `` toml: key b is already defined ```
This commit is contained in:
+14
-15
@@ -216,35 +216,34 @@ func (s *SeenTracker) checkKeyValue(parentIdx int, node *ast.Node) error {
|
|||||||
|
|
||||||
idx := s.find(parentIdx, k)
|
idx := s.find(parentIdx, k)
|
||||||
|
|
||||||
if idx >= 0 {
|
if idx < 0 {
|
||||||
if s.entries[idx].kind != tableKind {
|
idx = s.create(parentIdx, k, tableKind, false)
|
||||||
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), s.entries[idx].kind)
|
} else {
|
||||||
}
|
entry := s.entries[idx]
|
||||||
if s.entries[idx].explicit {
|
if it.IsLast() {
|
||||||
|
return fmt.Errorf("toml: key %s is already defined", string(k))
|
||||||
|
} else if entry.kind != tableKind {
|
||||||
|
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
|
||||||
|
} else if entry.explicit {
|
||||||
return fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
|
return fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
idx = s.create(parentIdx, k, tableKind, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parentIdx = idx
|
parentIdx = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
kind := valueKind
|
s.entries[parentIdx].kind = valueKind
|
||||||
var err error
|
|
||||||
|
|
||||||
value := node.Value()
|
value := node.Value()
|
||||||
|
|
||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
case ast.InlineTable:
|
case ast.InlineTable:
|
||||||
kind = tableKind
|
return s.checkInlineTable(parentIdx, value)
|
||||||
err = s.checkInlineTable(parentIdx, value)
|
|
||||||
case ast.Array:
|
case ast.Array:
|
||||||
err = s.checkArray(parentIdx, value)
|
return s.checkArray(parentIdx, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.entries[parentIdx].kind = kind
|
return nil
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeenTracker) checkArray(parentIdx int, node *ast.Node) error {
|
func (s *SeenTracker) checkArray(parentIdx int, node *ast.Node) error {
|
||||||
|
|||||||
+339
-335
@@ -16,6 +16,66 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ExampleDecoder_SetStrict() {
|
||||||
|
type S struct {
|
||||||
|
Key1 string
|
||||||
|
Key3 string
|
||||||
|
}
|
||||||
|
doc := `
|
||||||
|
key1 = "value1"
|
||||||
|
key2 = "value2"
|
||||||
|
key3 = "value3"
|
||||||
|
`
|
||||||
|
r := strings.NewReader(doc)
|
||||||
|
d := toml.NewDecoder(r)
|
||||||
|
d.SetStrict(true)
|
||||||
|
s := S{}
|
||||||
|
err := d.Decode(&s)
|
||||||
|
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
|
||||||
|
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())
|
||||||
|
// Output:
|
||||||
|
// strict mode: fields in the document are missing in the target struct
|
||||||
|
// 2| key1 = "value1"
|
||||||
|
// 3| key2 = "value2"
|
||||||
|
// | ~~~~ missing field
|
||||||
|
// 4| key3 = "value3"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleUnmarshal() {
|
||||||
|
type MyConfig struct {
|
||||||
|
Version int
|
||||||
|
Name string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := `
|
||||||
|
version = 2
|
||||||
|
name = "go-toml"
|
||||||
|
tags = ["go", "toml"]
|
||||||
|
`
|
||||||
|
|
||||||
|
var cfg MyConfig
|
||||||
|
err := toml.Unmarshal([]byte(doc), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("version:", cfg.Version)
|
||||||
|
fmt.Println("name:", cfg.Name)
|
||||||
|
fmt.Println("tags:", cfg.Tags)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// version: 2
|
||||||
|
// name: go-toml
|
||||||
|
// tags: [go toml]
|
||||||
|
}
|
||||||
|
|
||||||
type badReader struct{}
|
type badReader struct{}
|
||||||
|
|
||||||
func (r *badReader) Read([]byte) (int, error) {
|
func (r *badReader) Read([]byte) (int, error) {
|
||||||
@@ -1723,6 +1783,174 @@ func TestUnmarshalFloat32(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecoderStrict(t *testing.T) {
|
||||||
|
examples := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
target interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "multiple missing root keys",
|
||||||
|
input: `
|
||||||
|
key1 = "value1"
|
||||||
|
key2 = "missing2"
|
||||||
|
key3 = "missing3"
|
||||||
|
key4 = "value4"
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
2| key1 = "value1"
|
||||||
|
3| key2 = "missing2"
|
||||||
|
| ~~~~ missing field
|
||||||
|
4| key3 = "missing3"
|
||||||
|
5| key4 = "value4"
|
||||||
|
---
|
||||||
|
2| key1 = "value1"
|
||||||
|
3| key2 = "missing2"
|
||||||
|
4| key3 = "missing3"
|
||||||
|
| ~~~~ missing field
|
||||||
|
5| key4 = "value4"
|
||||||
|
`,
|
||||||
|
target: &struct {
|
||||||
|
Key1 string
|
||||||
|
Key4 string
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multi-part key",
|
||||||
|
input: `a.short.key="foo"`,
|
||||||
|
expected: `
|
||||||
|
1| a.short.key="foo"
|
||||||
|
| ~~~~~~~~~~~ missing field
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing table",
|
||||||
|
input: `
|
||||||
|
[foo]
|
||||||
|
bar = 42
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
2| [foo]
|
||||||
|
| ~~~ missing table
|
||||||
|
3| bar = 42
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: "missing array table",
|
||||||
|
input: `
|
||||||
|
[[foo]]
|
||||||
|
bar = 42
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
2| [[foo]]
|
||||||
|
| ~~~ missing table
|
||||||
|
3| bar = 42
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range examples {
|
||||||
|
e := e
|
||||||
|
t.Run(e.desc, func(t *testing.T) {
|
||||||
|
t.Run("strict", func(t *testing.T) {
|
||||||
|
r := strings.NewReader(e.input)
|
||||||
|
d := toml.NewDecoder(r)
|
||||||
|
d.SetStrict(true)
|
||||||
|
x := e.target
|
||||||
|
if x == nil {
|
||||||
|
x = &struct{}{}
|
||||||
|
}
|
||||||
|
err := d.Decode(x)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
r := strings.NewReader(e.input)
|
||||||
|
d := toml.NewDecoder(r)
|
||||||
|
d.SetStrict(false)
|
||||||
|
x := e.target
|
||||||
|
if x == nil {
|
||||||
|
x = &struct{}{}
|
||||||
|
}
|
||||||
|
err := d.Decode(x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue252(t *testing.T) {
|
||||||
|
type config struct {
|
||||||
|
Val1 string `toml:"val1"`
|
||||||
|
Val2 string `toml:"val2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile := []byte(
|
||||||
|
`
|
||||||
|
val1 = "test1"
|
||||||
|
`)
|
||||||
|
|
||||||
|
cfg := &config{
|
||||||
|
Val2: "test2",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := toml.Unmarshal(configFile, cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "test2", cfg.Val2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue287(t *testing.T) {
|
||||||
|
b := `y=[[{}]]`
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
err := toml.Unmarshal([]byte(b), &v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"y": []interface{}{
|
||||||
|
[]interface{}{
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Map458 map[string]interface{}
|
||||||
|
Slice458 []interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m Map458) A(s string) Slice458 {
|
||||||
|
return m[s].([]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue458(t *testing.T) {
|
||||||
|
s := []byte(`[[package]]
|
||||||
|
dependencies = ["regex"]
|
||||||
|
name = "decode"
|
||||||
|
version = "0.1.0"`)
|
||||||
|
m := Map458{}
|
||||||
|
err := toml.Unmarshal(s, &m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
a := m.A("package")
|
||||||
|
expected := Slice458{
|
||||||
|
map[string]interface{}{
|
||||||
|
"dependencies": []interface{}{"regex"},
|
||||||
|
"name": "decode",
|
||||||
|
"version": "0.1.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, a)
|
||||||
|
}
|
||||||
|
|
||||||
type Integer484 struct {
|
type Integer484 struct {
|
||||||
Value int
|
Value int
|
||||||
}
|
}
|
||||||
@@ -1756,54 +1984,6 @@ func TestIssue484(t *testing.T) {
|
|||||||
}, cfg)
|
}, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
|
||||||
Map458 map[string]interface{}
|
|
||||||
Slice458 []interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m Map458) A(s string) Slice458 {
|
|
||||||
return m[s].([]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue458(t *testing.T) {
|
|
||||||
s := []byte(`[[package]]
|
|
||||||
dependencies = ["regex"]
|
|
||||||
name = "decode"
|
|
||||||
version = "0.1.0"`)
|
|
||||||
m := Map458{}
|
|
||||||
err := toml.Unmarshal(s, &m)
|
|
||||||
require.NoError(t, err)
|
|
||||||
a := m.A("package")
|
|
||||||
expected := Slice458{
|
|
||||||
map[string]interface{}{
|
|
||||||
"dependencies": []interface{}{"regex"},
|
|
||||||
"name": "decode",
|
|
||||||
"version": "0.1.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue252(t *testing.T) {
|
|
||||||
type config struct {
|
|
||||||
Val1 string `toml:"val1"`
|
|
||||||
Val2 string `toml:"val2"`
|
|
||||||
}
|
|
||||||
|
|
||||||
configFile := []byte(
|
|
||||||
`
|
|
||||||
val1 = "test1"
|
|
||||||
`)
|
|
||||||
|
|
||||||
cfg := &config{
|
|
||||||
Val2: "test2",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := toml.Unmarshal(configFile, cfg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "test2", cfg.Val2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue494(t *testing.T) {
|
func TestIssue494(t *testing.T) {
|
||||||
data := `
|
data := `
|
||||||
foo = 2021-04-08
|
foo = 2021-04-08
|
||||||
@@ -1819,6 +1999,23 @@ bar = 2021-04-08
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue508(t *testing.T) {
|
||||||
|
type head struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type text struct {
|
||||||
|
head
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(`title = "This is a title"`)
|
||||||
|
|
||||||
|
t1 := text{}
|
||||||
|
err := toml.Unmarshal(b, &t1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "This is a title", t1.head.Title)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue507(t *testing.T) {
|
func TestIssue507(t *testing.T) {
|
||||||
data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'}
|
data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'}
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
@@ -1826,6 +2023,84 @@ func TestIssue507(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type uuid [16]byte
|
||||||
|
|
||||||
|
func (u *uuid) UnmarshalText(text []byte) (err error) {
|
||||||
|
// Note: the original reported issue had a more complex implementation
|
||||||
|
// of this function. But the important part is to verify that a
|
||||||
|
// non-struct type implementing UnmarshalText works with the unmarshal
|
||||||
|
// process.
|
||||||
|
placeholder := bytes.Repeat([]byte{0xAA}, 16)
|
||||||
|
copy(u[:], placeholder)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue564(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
ID uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
err := toml.Unmarshal([]byte(`id = "0818a52b97b94768941ba1172c76cf6c"`), &config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uuid{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, config.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue575(t *testing.T) {
|
||||||
|
b := []byte(`
|
||||||
|
[pkg.cargo]
|
||||||
|
version = "0.55.0 (5ae8d74b3 2021-06-22)"
|
||||||
|
git_commit_hash = "a178d0322ce20e33eac124758e837cbd80a6f633"
|
||||||
|
[pkg.cargo.target.aarch64-apple-darwin]
|
||||||
|
available = true
|
||||||
|
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.gz"
|
||||||
|
hash = "7bac3901d8eb6a4191ffeebe75b29c78bcb270158ec901addb31f588d965d35d"
|
||||||
|
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz"
|
||||||
|
xz_hash = "5207644fd6379f3e5b8ae60016b854efa55a381b0c363bff7f9b2f25bfccc430"
|
||||||
|
|
||||||
|
[pkg.cargo.target.aarch64-pc-windows-msvc]
|
||||||
|
available = true
|
||||||
|
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.gz"
|
||||||
|
hash = "eb8ccd9b1f6312b06dc749c17896fa4e9c163661c273dcb61cd7a48376227f6d"
|
||||||
|
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz"
|
||||||
|
xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388"
|
||||||
|
`)
|
||||||
|
|
||||||
|
type target struct {
|
||||||
|
XZ_URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type pkg struct {
|
||||||
|
Target map[string]target
|
||||||
|
}
|
||||||
|
|
||||||
|
type doc struct {
|
||||||
|
Pkg map[string]pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
var dist doc
|
||||||
|
err := toml.Unmarshal(b, &dist)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := doc{
|
||||||
|
Pkg: map[string]pkg{
|
||||||
|
"cargo": pkg{
|
||||||
|
Target: map[string]target{
|
||||||
|
"aarch64-apple-darwin": {
|
||||||
|
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz",
|
||||||
|
},
|
||||||
|
"aarch64-pc-windows-msvc": {
|
||||||
|
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expected, dist)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue579(t *testing.T) {
|
func TestIssue579(t *testing.T) {
|
||||||
var v interface{}
|
var v interface{}
|
||||||
err := toml.Unmarshal([]byte(`[foo`), &v)
|
err := toml.Unmarshal([]byte(`[foo`), &v)
|
||||||
@@ -1838,6 +2113,12 @@ func TestIssue581(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue585(t *testing.T) {
|
||||||
|
var v interface{}
|
||||||
|
err := toml.Unmarshal([]byte(`a=1979-05127T 0`), &v)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue586(t *testing.T) {
|
func TestIssue586(t *testing.T) {
|
||||||
var v interface{}
|
var v interface{}
|
||||||
err := toml.Unmarshal([]byte(`a={ `), &v)
|
err := toml.Unmarshal([]byte(`a={ `), &v)
|
||||||
@@ -1850,12 +2131,6 @@ func TestIssue588(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue585(t *testing.T) {
|
|
||||||
var v interface{}
|
|
||||||
err := toml.Unmarshal([]byte(`a=1979-05127T 0`), &v)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support lowercase 'T' and 'Z'
|
// Support lowercase 'T' and 'Z'
|
||||||
func TestIssue600(t *testing.T) {
|
func TestIssue600(t *testing.T) {
|
||||||
var v interface{}
|
var v interface{}
|
||||||
@@ -1891,28 +2166,10 @@ foo = "bar"`
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type uuid [16]byte
|
func TestIssue631(t *testing.T) {
|
||||||
|
v := map[string]interface{}{}
|
||||||
func (u *uuid) UnmarshalText(text []byte) (err error) {
|
err := toml.Unmarshal([]byte("\"\\b\u007f\"= 2"), &v)
|
||||||
// Note: the original reported issue had a more complex implementation
|
require.Error(t, err)
|
||||||
// of this function. But the important part is to verify that a
|
|
||||||
// non-struct type implementing UnmarshalText works with the unmarshal
|
|
||||||
// process.
|
|
||||||
placeholder := bytes.Repeat([]byte{0xAA}, 16)
|
|
||||||
copy(u[:], placeholder)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue564(t *testing.T) {
|
|
||||||
type Config struct {
|
|
||||||
ID uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
var config Config
|
|
||||||
|
|
||||||
err := toml.Unmarshal([]byte(`id = "0818a52b97b94768941ba1172c76cf6c"`), &config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, uuid{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, config.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue658(t *testing.T) {
|
func TestIssue658(t *testing.T) {
|
||||||
@@ -1927,6 +2184,12 @@ func TestIssue662(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue666(t *testing.T) {
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := toml.Unmarshal([]byte("a={}\na={}"), &v)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func TestUnmarshalDecodeErrors(t *testing.T) {
|
func TestUnmarshalDecodeErrors(t *testing.T) {
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
@@ -2458,205 +2721,6 @@ func TestLocalDateTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue287(t *testing.T) {
|
|
||||||
b := `y=[[{}]]`
|
|
||||||
v := map[string]interface{}{}
|
|
||||||
err := toml.Unmarshal([]byte(b), &v)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
|
||||||
"y": []interface{}{
|
|
||||||
[]interface{}{
|
|
||||||
map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue508(t *testing.T) {
|
|
||||||
type head struct {
|
|
||||||
Title string `toml:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type text struct {
|
|
||||||
head
|
|
||||||
}
|
|
||||||
|
|
||||||
b := []byte(`title = "This is a title"`)
|
|
||||||
|
|
||||||
t1 := text{}
|
|
||||||
err := toml.Unmarshal(b, &t1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "This is a title", t1.head.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue575(t *testing.T) {
|
|
||||||
b := []byte(`
|
|
||||||
[pkg.cargo]
|
|
||||||
version = "0.55.0 (5ae8d74b3 2021-06-22)"
|
|
||||||
git_commit_hash = "a178d0322ce20e33eac124758e837cbd80a6f633"
|
|
||||||
[pkg.cargo.target.aarch64-apple-darwin]
|
|
||||||
available = true
|
|
||||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.gz"
|
|
||||||
hash = "7bac3901d8eb6a4191ffeebe75b29c78bcb270158ec901addb31f588d965d35d"
|
|
||||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz"
|
|
||||||
xz_hash = "5207644fd6379f3e5b8ae60016b854efa55a381b0c363bff7f9b2f25bfccc430"
|
|
||||||
|
|
||||||
[pkg.cargo.target.aarch64-pc-windows-msvc]
|
|
||||||
available = true
|
|
||||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.gz"
|
|
||||||
hash = "eb8ccd9b1f6312b06dc749c17896fa4e9c163661c273dcb61cd7a48376227f6d"
|
|
||||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz"
|
|
||||||
xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388"
|
|
||||||
`)
|
|
||||||
|
|
||||||
type target struct {
|
|
||||||
XZ_URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type pkg struct {
|
|
||||||
Target map[string]target
|
|
||||||
}
|
|
||||||
|
|
||||||
type doc struct {
|
|
||||||
Pkg map[string]pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
var dist doc
|
|
||||||
err := toml.Unmarshal(b, &dist)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := doc{
|
|
||||||
Pkg: map[string]pkg{
|
|
||||||
"cargo": pkg{
|
|
||||||
Target: map[string]target{
|
|
||||||
"aarch64-apple-darwin": {
|
|
||||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz",
|
|
||||||
},
|
|
||||||
"aarch64-pc-windows-msvc": {
|
|
||||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Equal(t, expected, dist)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssue631(t *testing.T) {
|
|
||||||
v := map[string]interface{}{}
|
|
||||||
|
|
||||||
err := toml.Unmarshal([]byte("\"\\b\u007f\"= 2"), &v)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestDecoderStrict(t *testing.T) {
|
|
||||||
examples := []struct {
|
|
||||||
desc string
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
target interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "multiple missing root keys",
|
|
||||||
input: `
|
|
||||||
key1 = "value1"
|
|
||||||
key2 = "missing2"
|
|
||||||
key3 = "missing3"
|
|
||||||
key4 = "value4"
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
2| key1 = "value1"
|
|
||||||
3| key2 = "missing2"
|
|
||||||
| ~~~~ missing field
|
|
||||||
4| key3 = "missing3"
|
|
||||||
5| key4 = "value4"
|
|
||||||
---
|
|
||||||
2| key1 = "value1"
|
|
||||||
3| key2 = "missing2"
|
|
||||||
4| key3 = "missing3"
|
|
||||||
| ~~~~ missing field
|
|
||||||
5| key4 = "value4"
|
|
||||||
`,
|
|
||||||
target: &struct {
|
|
||||||
Key1 string
|
|
||||||
Key4 string
|
|
||||||
}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "multi-part key",
|
|
||||||
input: `a.short.key="foo"`,
|
|
||||||
expected: `
|
|
||||||
1| a.short.key="foo"
|
|
||||||
| ~~~~~~~~~~~ missing field
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing table",
|
|
||||||
input: `
|
|
||||||
[foo]
|
|
||||||
bar = 42
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
2| [foo]
|
|
||||||
| ~~~ missing table
|
|
||||||
3| bar = 42
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
desc: "missing array table",
|
|
||||||
input: `
|
|
||||||
[[foo]]
|
|
||||||
bar = 42
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
2| [[foo]]
|
|
||||||
| ~~~ missing table
|
|
||||||
3| bar = 42
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range examples {
|
|
||||||
e := e
|
|
||||||
t.Run(e.desc, func(t *testing.T) {
|
|
||||||
t.Run("strict", func(t *testing.T) {
|
|
||||||
r := strings.NewReader(e.input)
|
|
||||||
d := toml.NewDecoder(r)
|
|
||||||
d.SetStrict(true)
|
|
||||||
x := e.target
|
|
||||||
if x == nil {
|
|
||||||
x = &struct{}{}
|
|
||||||
}
|
|
||||||
err := d.Decode(x)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
|
||||||
r := strings.NewReader(e.input)
|
|
||||||
d := toml.NewDecoder(r)
|
|
||||||
d.SetStrict(false)
|
|
||||||
x := e.target
|
|
||||||
if x == nil {
|
|
||||||
x = &struct{}{}
|
|
||||||
}
|
|
||||||
err := d.Decode(x)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshal_RecursiveTable(t *testing.T) {
|
func TestUnmarshal_RecursiveTable(t *testing.T) {
|
||||||
type Foo struct {
|
type Foo struct {
|
||||||
I int
|
I int
|
||||||
@@ -2827,63 +2891,3 @@ func TestUnmarshal_RecursiveTableArray(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleDecoder_SetStrict() {
|
|
||||||
type S struct {
|
|
||||||
Key1 string
|
|
||||||
Key3 string
|
|
||||||
}
|
|
||||||
doc := `
|
|
||||||
key1 = "value1"
|
|
||||||
key2 = "value2"
|
|
||||||
key3 = "value3"
|
|
||||||
`
|
|
||||||
r := strings.NewReader(doc)
|
|
||||||
d := toml.NewDecoder(r)
|
|
||||||
d.SetStrict(true)
|
|
||||||
s := S{}
|
|
||||||
err := d.Decode(&s)
|
|
||||||
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
|
|
||||||
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())
|
|
||||||
// Output:
|
|
||||||
// strict mode: fields in the document are missing in the target struct
|
|
||||||
// 2| key1 = "value1"
|
|
||||||
// 3| key2 = "value2"
|
|
||||||
// | ~~~~ missing field
|
|
||||||
// 4| key3 = "value3"
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleUnmarshal() {
|
|
||||||
type MyConfig struct {
|
|
||||||
Version int
|
|
||||||
Name string
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
doc := `
|
|
||||||
version = 2
|
|
||||||
name = "go-toml"
|
|
||||||
tags = ["go", "toml"]
|
|
||||||
`
|
|
||||||
|
|
||||||
var cfg MyConfig
|
|
||||||
err := toml.Unmarshal([]byte(doc), &cfg)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println("version:", cfg.Version)
|
|
||||||
fmt.Println("name:", cfg.Name)
|
|
||||||
fmt.Println("tags:", cfg.Tags)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// version: 2
|
|
||||||
// name: go-toml
|
|
||||||
// tags: [go toml]
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user