Replace branch with AST version
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,302 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParser_AST_Numbers(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
kind ast.Kind
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "integer just digits",
|
||||
input: `1234`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer zero",
|
||||
input: `0`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer sign",
|
||||
input: `+99`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer hex uppercase",
|
||||
input: `0xDEADBEEF`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer hex lowercase",
|
||||
input: `0xdead_beef`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer octal",
|
||||
input: `0o01234567`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "integer binary",
|
||||
input: `0b11010110`,
|
||||
kind: ast.Integer,
|
||||
},
|
||||
{
|
||||
desc: "float pi",
|
||||
input: `3.1415`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float negative",
|
||||
input: `-0.01`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float signed exponent",
|
||||
input: `5e+22`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float exponent lowercase",
|
||||
input: `1e06`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float exponent uppercase",
|
||||
input: `-2E-2`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float fractional with exponent",
|
||||
input: `6.626e-34`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "float underscores",
|
||||
input: `224_617.445_991_228`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "inf",
|
||||
input: `inf`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "inf negative",
|
||||
input: `-inf`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "inf positive",
|
||||
input: `+inf`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "nan",
|
||||
input: `nan`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "nan negative",
|
||||
input: `-nan`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
{
|
||||
desc: "nan positive",
|
||||
input: `+nan`,
|
||||
kind: ast.Float,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
p := parser{}
|
||||
err := p.parse([]byte(`A = ` + e.input))
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`A`)},
|
||||
{Kind: e.kind, Data: []byte(e.input)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, expected, p.tree)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_AST(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
ast ast.Root
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "simple string assignment",
|
||||
input: `A = "hello"`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "simple bool assignment",
|
||||
input: `A = true`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Bool,
|
||||
Data: []byte(`true`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "array of strings",
|
||||
input: `A = ["hello", ["world", "again"]]`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`again`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "array of arrays of strings",
|
||||
input: `A = ["hello", "world"]`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "inline table",
|
||||
input: `name = { first = "Tom", last = "Preston-Werner" }`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`name`),
|
||||
},
|
||||
{
|
||||
Kind: ast.InlineTable,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`first`)},
|
||||
{Kind: ast.String, Data: []byte(`Tom`)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`last`)},
|
||||
{Kind: ast.String, Data: []byte(`Preston-Werner`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
p := parser{}
|
||||
err := p.parse([]byte(e.input))
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, e.ast, p.tree)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import "fmt"
|
||||
|
||||
func scanFollows(pattern []byte) func(b []byte) bool {
|
||||
return func(b []byte) bool {
|
||||
if len(b) < len(pattern) {
|
||||
return false
|
||||
}
|
||||
for i, c := range pattern {
|
||||
if b[i] != c {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var scanFollowsMultilineBasicStringDelimiter = scanFollows([]byte{'"', '"', '"'})
|
||||
var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'', '\''})
|
||||
var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'})
|
||||
var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'})
|
||||
var scanFollowsInf = scanFollows([]byte{'i', 'n', 'f'})
|
||||
var scanFollowsNan = scanFollows([]byte{'n', 'a', 'n'})
|
||||
|
||||
func scanUnquotedKey(b []byte) ([]byte, []byte, error) {
|
||||
//unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||
for i := 0; i < len(b); i++ {
|
||||
if !isUnquotedKeyChar(b[i]) {
|
||||
return b[:i], b[i:], nil
|
||||
}
|
||||
}
|
||||
return b, nil, nil
|
||||
}
|
||||
|
||||
func isUnquotedKeyChar(r byte) bool {
|
||||
return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_'
|
||||
}
|
||||
|
||||
func scanLiteralString(b []byte) ([]byte, []byte, error) {
|
||||
//literal-string = apostrophe *literal-char apostrophe
|
||||
//apostrophe = %x27 ; ' apostrophe
|
||||
//literal-char = %x09 / %x20-26 / %x28-7E / non-ascii
|
||||
for i := 1; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '\'':
|
||||
return b[:i+1], b[i+1:], nil
|
||||
case '\n':
|
||||
return nil, nil, fmt.Errorf("literal strings cannot have new lines")
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("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 ]
|
||||
//
|
||||
//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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(`multiline literal string not terminated by '''`)
|
||||
}
|
||||
|
||||
func scanWindowsNewline(b []byte) ([]byte, []byte, error) {
|
||||
if len(b) < 2 {
|
||||
return nil, nil, fmt.Errorf(`windows new line missing \n`)
|
||||
}
|
||||
if b[1] != '\n' {
|
||||
return nil, nil, fmt.Errorf(`windows new line should be \r\n`)
|
||||
}
|
||||
return b[:2], b[2:], nil
|
||||
}
|
||||
|
||||
func scanWhitespace(b []byte) ([]byte, []byte) {
|
||||
for i := 0; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case ' ', '\t':
|
||||
continue
|
||||
default:
|
||||
return b[:i], b[i:]
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func scanComment(b []byte) ([]byte, []byte, error) {
|
||||
//;; Comment
|
||||
//
|
||||
//comment-start-symbol = %x23 ; #
|
||||
//non-ascii = %x80-D7FF / %xE000-10FFFF
|
||||
//non-eol = %x09 / %x20-7F / non-ascii
|
||||
//
|
||||
//comment = comment-start-symbol *non-eol
|
||||
|
||||
for i := 1; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '\n':
|
||||
return b[:i], b[i:], nil
|
||||
}
|
||||
}
|
||||
return b, nil, nil
|
||||
}
|
||||
|
||||
// TODO perform validation on the string?
|
||||
func scanBasicString(b []byte) ([]byte, []byte, error) {
|
||||
//basic-string = quotation-mark *basic-char quotation-mark
|
||||
//quotation-mark = %x22 ; "
|
||||
//basic-char = basic-unescaped / escaped
|
||||
//basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
//escaped = escape escape-seq-char
|
||||
for i := 1; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '"':
|
||||
return b[:i+1], b[i+1:], nil
|
||||
case '\n':
|
||||
return nil, nil, fmt.Errorf("basic strings cannot have new lines")
|
||||
case '\\':
|
||||
if len(b) < i+2 {
|
||||
return nil, nil, fmt.Errorf("need a character after \\")
|
||||
}
|
||||
i++ // skip the next character
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(`basic string not terminated by "`)
|
||||
}
|
||||
|
||||
// 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 ]
|
||||
//
|
||||
//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 '"':
|
||||
if scanFollowsMultilineBasicStringDelimiter(b[i:]) {
|
||||
return b[:i+3], b[i+3:], nil
|
||||
}
|
||||
case '\\':
|
||||
if len(b) < i+2 {
|
||||
return nil, nil, fmt.Errorf("need a character after \\")
|
||||
}
|
||||
i++ // skip the next character
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(`multiline basic string not terminated by """`)
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type target interface {
|
||||
// Ensure the target's value is compatible with a slice and initialized.
|
||||
ensureSlice() error
|
||||
|
||||
// Store a string at the target.
|
||||
setString(v string) error
|
||||
|
||||
// Store a boolean at the target
|
||||
setBool(v bool) error
|
||||
|
||||
// Store an int64 at the target
|
||||
setInt64(v int64) error
|
||||
|
||||
// Store a float64 at the target
|
||||
setFloat64(v float64) error
|
||||
|
||||
// Creates a new value of the container's element type, and returns a
|
||||
// target to it.
|
||||
pushNew() (target, error)
|
||||
|
||||
// Dereferences the target.
|
||||
get() reflect.Value
|
||||
}
|
||||
|
||||
// valueTarget just contains a reflect.Value that can be set.
|
||||
// It is used for struct fields.
|
||||
type valueTarget reflect.Value
|
||||
|
||||
func (t valueTarget) get() reflect.Value {
|
||||
return reflect.Value(t)
|
||||
}
|
||||
|
||||
func (t valueTarget) ensureSlice() error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Type().Kind() {
|
||||
case reflect.Slice:
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
|
||||
}
|
||||
case reflect.Interface:
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0))
|
||||
} else {
|
||||
if f.Type().Elem().Kind() != reflect.Slice {
|
||||
return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind())
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot initialize a slice in %s", f.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) setString(v string) error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.String:
|
||||
f.SetString(v)
|
||||
case reflect.Interface:
|
||||
f.Set(reflect.ValueOf(v))
|
||||
default:
|
||||
return fmt.Errorf("cannot assign string to a %s", f.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) setBool(v bool) error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Bool:
|
||||
f.SetBool(v)
|
||||
case reflect.Interface:
|
||||
f.Set(reflect.ValueOf(v))
|
||||
default:
|
||||
return fmt.Errorf("cannot assign bool to a %s", f.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) setInt64(v int64) error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
// TODO: overflow checks
|
||||
f.SetInt(v)
|
||||
case reflect.Interface:
|
||||
f.Set(reflect.ValueOf(v))
|
||||
default:
|
||||
return fmt.Errorf("cannot assign int64 to a %s", f.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) setFloat64(v float64) error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
// TODO: overflow checks
|
||||
f.SetFloat(v)
|
||||
case reflect.Interface:
|
||||
f.Set(reflect.ValueOf(v))
|
||||
default:
|
||||
return fmt.Errorf("cannot assign float64 to a %s", f.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) pushNew() (target, error) {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Slice:
|
||||
idx := f.Len()
|
||||
f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem()))
|
||||
return valueTarget(f.Index(idx)), nil
|
||||
case reflect.Interface:
|
||||
if f.IsNil() {
|
||||
panic("interface should have been initialized")
|
||||
}
|
||||
ifaceElem := f.Elem()
|
||||
if ifaceElem.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
|
||||
}
|
||||
idx := ifaceElem.Len()
|
||||
newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
|
||||
newSlice := reflect.Append(ifaceElem, newElem)
|
||||
f.Set(newSlice)
|
||||
return valueTarget(f.Elem().Index(idx)), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func scopeTarget(t target, name string) (target, error) {
|
||||
x := t.get()
|
||||
return scope(x, name)
|
||||
}
|
||||
|
||||
func scope(v reflect.Value, name string) (target, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
return scopeStruct(v, name)
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
panic("not implemented") // TODO
|
||||
} else {
|
||||
return scope(v.Elem(), name)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("can't scope on a %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func scopeStruct(v reflect.Value, name string) (target, error) {
|
||||
// TODO: cache this
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.PkgPath != "" {
|
||||
// only consider exported fields
|
||||
continue
|
||||
}
|
||||
if f.Anonymous {
|
||||
// TODO: handle embedded structs
|
||||
} else {
|
||||
// TODO: handle names variations
|
||||
if f.Name == name {
|
||||
return valueTarget(v.Field(i)), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("field '%s' not found on %s", name, v.Type())
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStructTarget_Ensure(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input reflect.Value
|
||||
name string
|
||||
test func(v reflect.Value, err error)
|
||||
}{
|
||||
{
|
||||
desc: "handle a nil slice of string",
|
||||
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, v.IsNil())
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "handle an existing slice of string",
|
||||
input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.NoError(t, err)
|
||||
require.False(t, v.IsNil())
|
||||
s := v.Interface().([]string)
|
||||
assert.Equal(t, []string{"foo"}, s)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
target, err := scope(e.input, e.name)
|
||||
require.NoError(t, err)
|
||||
err = target.ensureSlice()
|
||||
v := target.get()
|
||||
e.test(v, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructTarget_SetString(t *testing.T) {
|
||||
str := "value"
|
||||
|
||||
examples := []struct {
|
||||
desc string
|
||||
input reflect.Value
|
||||
name string
|
||||
test func(v reflect.Value, err error)
|
||||
}{
|
||||
{
|
||||
desc: "sets a string",
|
||||
input: reflect.ValueOf(&struct{ A string }{}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, str, v.String())
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fails on a float",
|
||||
input: reflect.ValueOf(&struct{ A float64 }{}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fails on a slice",
|
||||
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
target, err := scope(e.input, e.name)
|
||||
require.NoError(t, err)
|
||||
err = target.setString(str)
|
||||
v := target.get()
|
||||
e.test(v, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushNew(t *testing.T) {
|
||||
t.Run("slice of strings", func(t *testing.T) {
|
||||
type Doc struct {
|
||||
A []string
|
||||
}
|
||||
d := Doc{}
|
||||
|
||||
x, err := scope(reflect.ValueOf(&d).Elem(), "A")
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err := x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("hello"))
|
||||
require.Equal(t, []string{"hello"}, d.A)
|
||||
|
||||
n, err = x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("world"))
|
||||
require.Equal(t, []string{"hello", "world"}, d.A)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces", func(t *testing.T) {
|
||||
type Doc struct {
|
||||
A []interface{}
|
||||
}
|
||||
d := Doc{}
|
||||
|
||||
x, err := scope(reflect.ValueOf(&d).Elem(), "A")
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err := x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("hello"))
|
||||
require.Equal(t, []interface{}{"hello"}, d.A)
|
||||
|
||||
n, err = x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("world"))
|
||||
require.Equal(t, []interface{}{"hello", "world"}, d.A)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScope_Struct(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input reflect.Value
|
||||
name string
|
||||
err bool
|
||||
idx []int
|
||||
}{
|
||||
{
|
||||
desc: "simple field",
|
||||
input: reflect.ValueOf(&struct{ A string }{}).Elem(),
|
||||
name: "A",
|
||||
idx: []int{0},
|
||||
},
|
||||
{
|
||||
desc: "fails not-exported field",
|
||||
input: reflect.ValueOf(&struct{ a string }{}).Elem(),
|
||||
name: "a",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
x, err := scope(e.input, e.name)
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
x2, ok := x.(valueTarget)
|
||||
require.True(t, ok)
|
||||
x2.get()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||
)
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
p := parser{}
|
||||
err := p.parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fromAst(p.tree, v)
|
||||
}
|
||||
|
||||
func fromAst(tree ast.Root, 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")
|
||||
}
|
||||
|
||||
var x target = valueTarget(r.Elem())
|
||||
var err error
|
||||
for _, node := range tree {
|
||||
x, err = unmarshalTopLevelNode(x, &node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The target return value is the target for the next top-level node. Mostly
|
||||
// unchanged, except by table and array table.
|
||||
func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) {
|
||||
switch node.Kind {
|
||||
case ast.Table:
|
||||
return scopeWithKey(x, node.Key())
|
||||
case ast.ArrayTable:
|
||||
panic("TODO")
|
||||
case ast.KeyValue:
|
||||
return x, unmarshalKeyValue(x, node)
|
||||
default:
|
||||
panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
func scopeWithKey(x target, key []ast.Node) (target, error) {
|
||||
var err error
|
||||
for _, n := range key {
|
||||
x, err = scopeTarget(x, string(n.Data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func unmarshalKeyValue(x target, node *ast.Node) error {
|
||||
assertNode(ast.KeyValue, node)
|
||||
|
||||
x, err := scopeWithKey(x, node.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshalValue(x, node.Value())
|
||||
}
|
||||
|
||||
func unmarshalValue(x target, node *ast.Node) error {
|
||||
switch node.Kind {
|
||||
case ast.String:
|
||||
return unmarshalString(x, node)
|
||||
case ast.Bool:
|
||||
return unmarshalBool(x, node)
|
||||
case ast.Integer:
|
||||
return unmarshalInteger(x, node)
|
||||
case ast.Float:
|
||||
return unmarshalFloat(x, node)
|
||||
case ast.Array:
|
||||
return unmarshalArray(x, node)
|
||||
case ast.InlineTable:
|
||||
return unmarshalInlineTable(x, node)
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalString(x target, node *ast.Node) error {
|
||||
assertNode(ast.String, node)
|
||||
return x.setString(string(node.Data))
|
||||
}
|
||||
|
||||
func unmarshalBool(x target, node *ast.Node) error {
|
||||
assertNode(ast.Bool, node)
|
||||
v := node.Data[0] == 't'
|
||||
return x.setBool(v)
|
||||
}
|
||||
|
||||
func unmarshalInteger(x target, node *ast.Node) error {
|
||||
assertNode(ast.Integer, node)
|
||||
v, err := node.DecodeInteger()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return x.setInt64(v)
|
||||
}
|
||||
|
||||
func unmarshalFloat(x target, node *ast.Node) error {
|
||||
assertNode(ast.Float, node)
|
||||
v, err := node.DecodeFloat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return x.setFloat64(v)
|
||||
}
|
||||
|
||||
func unmarshalInlineTable(x target, node *ast.Node) error {
|
||||
assertNode(ast.InlineTable, node)
|
||||
|
||||
for _, kv := range node.Children {
|
||||
err := unmarshalKeyValue(x, &kv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalArray(x target, node *ast.Node) error {
|
||||
assertNode(ast.Array, node)
|
||||
|
||||
err := x.ensureSlice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, n := range node.Children {
|
||||
v, err := x.pushNew()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = unmarshalValue(v, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,611 +0,0 @@
|
||||
package unmarshaler
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||
)
|
||||
|
||||
func TestUnmarshal_Integers(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
expected int64
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "integer just digits",
|
||||
input: `1234`,
|
||||
expected: 1234,
|
||||
},
|
||||
{
|
||||
desc: "integer zero",
|
||||
input: `0`,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
desc: "integer sign",
|
||||
input: `+99`,
|
||||
expected: 99,
|
||||
},
|
||||
{
|
||||
desc: "integer hex uppercase",
|
||||
input: `0xDEADBEEF`,
|
||||
expected: 0xDEADBEEF,
|
||||
},
|
||||
{
|
||||
desc: "integer hex lowercase",
|
||||
input: `0xdead_beef`,
|
||||
expected: 0xDEADBEEF,
|
||||
},
|
||||
{
|
||||
desc: "integer octal",
|
||||
input: `0o01234567`,
|
||||
expected: 0o01234567,
|
||||
},
|
||||
{
|
||||
desc: "integer binary",
|
||||
input: `0b11010110`,
|
||||
expected: 0b11010110,
|
||||
},
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
A int64
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
doc := doc{}
|
||||
err := Unmarshal([]byte(`A = `+e.input), &doc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, e.expected, doc.A)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal_Floats(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
expected float64
|
||||
testFn func(t *testing.T, v float64)
|
||||
err bool
|
||||
}{
|
||||
|
||||
{
|
||||
desc: "float pi",
|
||||
input: `3.1415`,
|
||||
expected: 3.1415,
|
||||
},
|
||||
{
|
||||
desc: "float negative",
|
||||
input: `-0.01`,
|
||||
expected: -0.01,
|
||||
},
|
||||
{
|
||||
desc: "float signed exponent",
|
||||
input: `5e+22`,
|
||||
expected: 5e+22,
|
||||
},
|
||||
{
|
||||
desc: "float exponent lowercase",
|
||||
input: `1e06`,
|
||||
expected: 1e06,
|
||||
},
|
||||
{
|
||||
desc: "float exponent uppercase",
|
||||
input: `-2E-2`,
|
||||
expected: -2e-2,
|
||||
},
|
||||
{
|
||||
desc: "float fractional with exponent",
|
||||
input: `6.626e-34`,
|
||||
expected: 6.626e-34,
|
||||
},
|
||||
{
|
||||
desc: "float underscores",
|
||||
input: `224_617.445_991_228`,
|
||||
expected: 224_617.445_991_228,
|
||||
},
|
||||
{
|
||||
desc: "inf",
|
||||
input: `inf`,
|
||||
expected: math.Inf(+1),
|
||||
},
|
||||
{
|
||||
desc: "inf negative",
|
||||
input: `-inf`,
|
||||
expected: math.Inf(-1),
|
||||
},
|
||||
{
|
||||
desc: "inf positive",
|
||||
input: `+inf`,
|
||||
expected: math.Inf(+1),
|
||||
},
|
||||
{
|
||||
desc: "nan",
|
||||
input: `nan`,
|
||||
testFn: func(t *testing.T, v float64) {
|
||||
assert.True(t, math.IsNaN(v))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nan negative",
|
||||
input: `-nan`,
|
||||
testFn: func(t *testing.T, v float64) {
|
||||
assert.True(t, math.IsNaN(v))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nan positive",
|
||||
input: `+nan`,
|
||||
testFn: func(t *testing.T, v float64) {
|
||||
assert.True(t, math.IsNaN(v))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
A float64
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
doc := doc{}
|
||||
err := Unmarshal([]byte(`A = `+e.input), &doc)
|
||||
require.NoError(t, err)
|
||||
if e.testFn != nil {
|
||||
e.testFn(t, doc.A)
|
||||
} else {
|
||||
assert.Equal(t, e.expected, doc.A)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
type test struct {
|
||||
target interface{}
|
||||
expected interface{}
|
||||
}
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
gen func() test
|
||||
}{
|
||||
{
|
||||
desc: "kv string",
|
||||
input: `A = "foo"`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
A string
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{A: "foo"},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "kv bool true",
|
||||
input: `A = true`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
A bool
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{A: true},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "kv bool false",
|
||||
input: `A = false`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
A bool
|
||||
}
|
||||
return test{
|
||||
&doc{A: true},
|
||||
&doc{A: false},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string array",
|
||||
input: `A = ["foo", "bar"]`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
A []string
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{A: []string{"foo", "bar"}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "standard table",
|
||||
input: `[A]
|
||||
B = "data"`,
|
||||
gen: func() test {
|
||||
type A struct {
|
||||
B string
|
||||
}
|
||||
type doc struct {
|
||||
A A
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{A: A{B: "data"}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "inline table",
|
||||
input: `Name = {First = "hello", Last = "world"}`,
|
||||
gen: func() test {
|
||||
type name struct {
|
||||
First string
|
||||
Last string
|
||||
}
|
||||
type doc struct {
|
||||
Name name
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{Name: name{
|
||||
First: "hello",
|
||||
Last: "world",
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "inline table inside array",
|
||||
input: `Names = [{First = "hello", Last = "world"}, {First = "ab", Last = "cd"}]`,
|
||||
gen: func() test {
|
||||
type name struct {
|
||||
First string
|
||||
Last string
|
||||
}
|
||||
type doc struct {
|
||||
Names []name
|
||||
}
|
||||
return test{
|
||||
&doc{},
|
||||
&doc{
|
||||
Names: []name{
|
||||
{
|
||||
First: "hello",
|
||||
Last: "world",
|
||||
},
|
||||
{
|
||||
First: "ab",
|
||||
Last: "cd",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
test := e.gen()
|
||||
err := Unmarshal([]byte(e.input), test.target)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, test.target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromAst_KV(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo string
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: "hello"}, x)
|
||||
}
|
||||
|
||||
func TestFromAst_Table(t *testing.T) {
|
||||
t.Run("one level table on struct", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.Table,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`Level1`)},
|
||||
},
|
||||
},
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
},
|
||||
},
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`B`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Level1 struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Level1 Level1
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{
|
||||
Level1: Level1{
|
||||
A: "hello",
|
||||
B: "world",
|
||||
},
|
||||
}, x)
|
||||
})
|
||||
t.Run("one level table on struct", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.Table,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`A`)},
|
||||
{Kind: ast.Key, Data: []byte(`B`)},
|
||||
},
|
||||
},
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`C`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`value`),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type B struct {
|
||||
C string
|
||||
}
|
||||
|
||||
type A struct {
|
||||
B B
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
A A
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{
|
||||
A: A{B: B{C: "value"}},
|
||||
}, x)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromAst_InlineTable(t *testing.T) {
|
||||
t.Run("one level of strings", func(t *testing.T) {
|
||||
// name = { first = "Tom", last = "Preston-Werner" }
|
||||
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Name`)},
|
||||
{
|
||||
Kind: ast.InlineTable,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`First`)},
|
||||
{Kind: ast.String, Data: []byte(`Tom`)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{Kind: ast.Key, Data: []byte(`Last`)},
|
||||
{Kind: ast.String, Data: []byte(`Preston-Werner`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Name struct {
|
||||
First string
|
||||
Last string
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Name Name
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{
|
||||
Name: Name{
|
||||
First: "Tom",
|
||||
Last: "Preston-Werner",
|
||||
},
|
||||
}, x)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromAst_Slice(t *testing.T) {
|
||||
t.Run("slice of string", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []string
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces for strings", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []interface{}
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces with slices", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`inner1`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`inner2`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []interface{}
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := fromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user