initial import
This commit is contained in:
105
core/mapping/fieldoptions.go
Normal file
105
core/mapping/fieldoptions.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package mapping
|
||||
|
||||
import "fmt"
|
||||
|
||||
const notSymbol = '!'
|
||||
|
||||
type (
|
||||
// use context and OptionalDep option to determine the value of Optional
|
||||
// nothing to do with context.Context
|
||||
fieldOptionsWithContext struct {
|
||||
FromString bool
|
||||
Optional bool
|
||||
Options []string
|
||||
Default string
|
||||
Range *numberRange
|
||||
}
|
||||
|
||||
fieldOptions struct {
|
||||
fieldOptionsWithContext
|
||||
OptionalDep string
|
||||
}
|
||||
|
||||
numberRange struct {
|
||||
left float64
|
||||
leftInclude bool
|
||||
right float64
|
||||
rightInclude bool
|
||||
}
|
||||
)
|
||||
|
||||
func (o *fieldOptionsWithContext) fromString() bool {
|
||||
return o != nil && o.FromString
|
||||
}
|
||||
|
||||
func (o *fieldOptionsWithContext) getDefault() (string, bool) {
|
||||
if o == nil {
|
||||
return "", false
|
||||
} else {
|
||||
return o.Default, len(o.Default) > 0
|
||||
}
|
||||
}
|
||||
|
||||
func (o *fieldOptionsWithContext) optional() bool {
|
||||
return o != nil && o.Optional
|
||||
}
|
||||
|
||||
func (o *fieldOptionsWithContext) options() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.Options
|
||||
}
|
||||
|
||||
func (o *fieldOptions) optionalDep() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
} else {
|
||||
return o.OptionalDep
|
||||
}
|
||||
}
|
||||
|
||||
func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName string) (
|
||||
*fieldOptionsWithContext, error) {
|
||||
var optional bool
|
||||
if o.optional() {
|
||||
dep := o.optionalDep()
|
||||
if len(dep) == 0 {
|
||||
optional = true
|
||||
} else if dep[0] == notSymbol {
|
||||
dep = dep[1:]
|
||||
if len(dep) == 0 {
|
||||
return nil, fmt.Errorf("wrong optional value for %q in %q", key, fullName)
|
||||
}
|
||||
|
||||
_, baseOn := m.Value(dep)
|
||||
_, selfOn := m.Value(key)
|
||||
if baseOn == selfOn {
|
||||
return nil, fmt.Errorf("set value for either %q or %q in %q", dep, key, fullName)
|
||||
} else {
|
||||
optional = baseOn
|
||||
}
|
||||
} else {
|
||||
_, baseOn := m.Value(dep)
|
||||
_, selfOn := m.Value(key)
|
||||
if baseOn != selfOn {
|
||||
return nil, fmt.Errorf("values for %q and %q should be both provided or both not in %q",
|
||||
dep, key, fullName)
|
||||
} else {
|
||||
optional = !baseOn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if o.fieldOptionsWithContext.Optional == optional {
|
||||
return &o.fieldOptionsWithContext, nil
|
||||
} else {
|
||||
return &fieldOptionsWithContext{
|
||||
FromString: o.FromString,
|
||||
Optional: optional,
|
||||
Options: o.Options,
|
||||
Default: o.Default,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
37
core/mapping/jsonunmarshaler.go
Normal file
37
core/mapping/jsonunmarshaler.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"zero/core/jsonx"
|
||||
)
|
||||
|
||||
const jsonTagKey = "json"
|
||||
|
||||
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
|
||||
|
||||
func UnmarshalJsonBytes(content []byte, v interface{}) error {
|
||||
return unmarshalJsonBytes(content, v, jsonUnmarshaler)
|
||||
}
|
||||
|
||||
func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
|
||||
return unmarshalJsonReader(reader, v, jsonUnmarshaler)
|
||||
}
|
||||
|
||||
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
func unmarshalJsonReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
if err := jsonx.UnmarshalFromReader(reader, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
873
core/mapping/jsonunmarshaler_test.go
Normal file
873
core/mapping/jsonunmarshaler_test.go
Normal file
@@ -0,0 +1,873 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnmarshalBytes(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
}
|
||||
content := []byte(`{"Name": "liao"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Name": "liao"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesOptionalDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",optional,default=1"`
|
||||
}
|
||||
content := []byte(`{"Name": "liao"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesDefaultOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",default=1,optional"`
|
||||
}
|
||||
content := []byte(`{"Name": "liao"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Name string `json:",default=liao"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesBool(t *testing.T) {
|
||||
var c struct {
|
||||
Great bool
|
||||
}
|
||||
content := []byte(`{"Great": true}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.True(t, c.Great)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesInt(t *testing.T) {
|
||||
var c struct {
|
||||
Age int
|
||||
}
|
||||
content := []byte(`{"Age": 1}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesUint(t *testing.T) {
|
||||
var c struct {
|
||||
Age uint
|
||||
}
|
||||
content := []byte(`{"Age": 1}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, uint(1), c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesFloat(t *testing.T) {
|
||||
var c struct {
|
||||
Age float32
|
||||
}
|
||||
content := []byte(`{"Age": 1.5}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, float32(1.5), c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMustInOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMustInOptionalMissedPart(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Inner": {"There": "sure"}}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMustInOptionalOnlyOptionalFilled(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Inner": {"Optional": "sure"}}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesNil(t *testing.T) {
|
||||
var c struct {
|
||||
Int int64 `json:"int,optional"`
|
||||
}
|
||||
content := []byte(`{"int":null}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, int64(0), c.Int)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesNilSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Ints []int64 `json:"ints"`
|
||||
}
|
||||
content := []byte(`{"ints":[null]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 0, len(c.Ints))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age float32
|
||||
}
|
||||
content := []byte(`{"Age": 1.5}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStruct(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Inner": {"Name": "liao"}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Inner": {"Name": "liao"}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Inner": {"Name": "liao"}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStructPtrOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Inner": {"Name": "liao"}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStructPtrDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
Age int `json:",default=4"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Inner": {"Name": "liao"}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
assert.Equal(t, 4, c.Inner.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceString(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
}
|
||||
content := []byte(`{"Names": ["liao", "chaoxin"]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []string{"liao", "chaoxin"}
|
||||
if !reflect.DeepEqual(c.Names, want) {
|
||||
t.Fatalf("want %q, got %q", c.Names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStringOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
Age []int `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Names": ["liao", "chaoxin"]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []string{"liao", "chaoxin"}
|
||||
if !reflect.DeepEqual(c.Names, want) {
|
||||
t.Fatalf("want %q, got %q", c.Names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStruct(t *testing.T) {
|
||||
var c struct {
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []struct {
|
||||
Name string
|
||||
Age int
|
||||
}{
|
||||
{"liao", 1},
|
||||
{"chaoxin", 2},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %q, got %q", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}{
|
||||
{"liao", 1, nil},
|
||||
{"chaoxin", 2, nil},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %q, got %q", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
}{
|
||||
{"liao", 1},
|
||||
{"chaoxin", 2},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %v, got %v", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructPtrOptional(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}{
|
||||
{"liao", 1, nil},
|
||||
{"chaoxin", 2, nil},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %v, got %v", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructPtrPartial(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructPtrDefault(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string `json:",default=chaoxin@liao.com"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"People": [{"Name": "liao", "Age": 1}, {"Name": "chaoxin", "Age": 2}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}{
|
||||
{"liao", 1, "chaoxin@liao.com"},
|
||||
{"chaoxin", 2, "chaoxin@liao.com"},
|
||||
}
|
||||
|
||||
for i := range c.People {
|
||||
actual := c.People[i]
|
||||
expect := want[i]
|
||||
assert.Equal(t, expect.Age, actual.Age)
|
||||
assert.Equal(t, expect.Email, actual.Email)
|
||||
assert.Equal(t, expect.Name, actual.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStringPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
Age int
|
||||
}
|
||||
content := []byte(`{"Age": 1}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesSliceStructPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Group string
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Group": "chaoxin"}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesInnerAnonymousPartial(t *testing.T) {
|
||||
type (
|
||||
Deep struct {
|
||||
A string
|
||||
B string `json:",optional"`
|
||||
}
|
||||
Inner struct {
|
||||
Deep
|
||||
InnerV string `json:",optional"`
|
||||
}
|
||||
)
|
||||
|
||||
var c struct {
|
||||
Value Inner `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Value": {"InnerV": "chaoxin"}}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesStructPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Group string
|
||||
Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Group": "chaoxin"}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesEmptyMap(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]int `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Persons": {}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Empty(t, c.Persons)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMap(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]int
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": 1, "second": 2}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 2, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"])
|
||||
assert.Equal(t, 2, c.Persons["second"])
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStruct(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": {"Id": 1, "name": "kevin"}}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": {"Id": 1, "name": "kevin"}}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructMissingPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": {"Id": 1}}}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": {"Id": 1}}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapEmptyStructSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": []}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Empty(t, c.Persons["first"])
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": [{"Id": 1, "name": "kevin"}]}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"][0].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapEmptyStructPtrSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": []}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Empty(t, c.Persons["first"])
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructPtrSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": [{"Id": 1, "name": "kevin"}]}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"][0].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructPtrSliceMissingPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": [{"Id": 1}]}}`)
|
||||
|
||||
assert.NotNil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesMapStructPtrSliceOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`{"Persons": {"first": [{"Id": 1}]}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
}
|
||||
|
||||
func TestUnmarshalStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Etcd struct {
|
||||
Hosts []string
|
||||
Key string
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`{"Name": "kevin"}`)
|
||||
|
||||
err := UnmarshalJsonBytes(content, &c)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "kevin", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalStructLowerCase(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Etcd struct {
|
||||
Key string
|
||||
} `json:"etcd"`
|
||||
}
|
||||
content := []byte(`{"Name": "kevin", "etcd": {"Key": "the key"}}`)
|
||||
|
||||
err := UnmarshalJsonBytes(content, &c)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "kevin", c.Name)
|
||||
assert.Equal(t, "the key", c.Etcd.Key)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalWithEmpty(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Inner": {}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Inner": {}}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
In Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "", c.In.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructPtrOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
In *Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Nil(t, c.In)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalProvoidedAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Optional": "optional"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "optional", c.Optional)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAllOptionalProvoidedAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Optional": "optional"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "optional", c.Optional)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Must": "must"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "must", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure", "Must": "must"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "must", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructAnonymousOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStructPtrAnonymousOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`{"Else": "sure"}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Nil(t, c.Inner)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithZeroValues(t *testing.T) {
|
||||
type inner struct {
|
||||
False bool `json:"no"`
|
||||
Int int `json:"int"`
|
||||
String string `json:"string"`
|
||||
}
|
||||
content := []byte(`{"no": false, "int": 0, "string": ""}`)
|
||||
reader := bytes.NewReader(content)
|
||||
|
||||
var in inner
|
||||
ast := assert.New(t)
|
||||
ast.Nil(UnmarshalJsonReader(reader, &in))
|
||||
ast.False(in.False)
|
||||
ast.Equal(0, in.Int)
|
||||
ast.Equal("", in.String)
|
||||
}
|
||||
|
||||
func TestUnmarshalBytesError(t *testing.T) {
|
||||
payload := `[{"abcd": "cdef"}]`
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonBytes([]byte(payload), &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
}
|
||||
|
||||
func TestUnmarshalReaderError(t *testing.T) {
|
||||
payload := `[{"abcd": "cdef"}]`
|
||||
reader := strings.NewReader(payload)
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
}
|
||||
737
core/mapping/unmarshaler.go
Normal file
737
core/mapping/unmarshaler.go
Normal file
@@ -0,0 +1,737 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"zero/core/jsonx"
|
||||
"zero/core/lang"
|
||||
"zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultKeyName = "key"
|
||||
delimiter = '.'
|
||||
)
|
||||
|
||||
var (
|
||||
errTypeMismatch = errors.New("type mismatch")
|
||||
errValueNotSettable = errors.New("value is not settable")
|
||||
keyUnmarshaler = NewUnmarshaler(defaultKeyName)
|
||||
cacheKeys atomic.Value
|
||||
cacheKeysLock sync.Mutex
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
emptyMap = map[string]interface{}{}
|
||||
emptyValue = reflect.ValueOf(lang.Placeholder)
|
||||
)
|
||||
|
||||
type (
|
||||
Unmarshaler struct {
|
||||
key string
|
||||
opts unmarshalOptions
|
||||
}
|
||||
|
||||
unmarshalOptions struct {
|
||||
fromString bool
|
||||
}
|
||||
|
||||
keyCache map[string][]string
|
||||
UnmarshalOption func(*unmarshalOptions)
|
||||
)
|
||||
|
||||
func init() {
|
||||
cacheKeys.Store(make(keyCache))
|
||||
}
|
||||
|
||||
func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
|
||||
unmarshaler := Unmarshaler{
|
||||
key: key,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&unmarshaler.opts)
|
||||
}
|
||||
|
||||
return &unmarshaler
|
||||
}
|
||||
|
||||
func UnmarshalKey(m map[string]interface{}, v interface{}) error {
|
||||
return keyUnmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) Unmarshal(m map[string]interface{}, v interface{}) error {
|
||||
return u.UnmarshalValuer(MapValuer(m), v)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v interface{}) error {
|
||||
return u.unmarshalWithFullName(m, v, "")
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName string) error {
|
||||
rv := reflect.ValueOf(v)
|
||||
if err := ValidatePtr(&rv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rte := reflect.TypeOf(v).Elem()
|
||||
rve := rv.Elem()
|
||||
numFields := rte.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := rte.Field(i)
|
||||
if usingDifferentKeys(u.key, field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := u.processField(field, rve.Field(i), m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value,
|
||||
m Valuer, fullName string) error {
|
||||
key, options, err := u.parseOptionsWithContext(field, m, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, hasValue := getValue(m, key); hasValue {
|
||||
return fmt.Errorf("fields of %s can't be wrapped inside, because it's anonymous", key)
|
||||
}
|
||||
|
||||
if options.optional() {
|
||||
return u.processAnonymousFieldOptional(field, value, key, m, fullName)
|
||||
} else {
|
||||
return u.processAnonymousFieldRequired(field, value, m, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value,
|
||||
key string, m Valuer, fullName string) error {
|
||||
var filled bool
|
||||
var required int
|
||||
var requiredFilled int
|
||||
var indirectValue reflect.Value
|
||||
fieldType := Deref(field.Type)
|
||||
|
||||
for i := 0; i < fieldType.NumField(); i++ {
|
||||
subField := fieldType.Field(i)
|
||||
fieldKey, fieldOpts, err := u.parseOptionsWithContext(subField, m, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, hasValue := getValue(m, fieldKey)
|
||||
if hasValue {
|
||||
if !filled {
|
||||
filled = true
|
||||
maybeNewValue(field, value)
|
||||
indirectValue = reflect.Indirect(value)
|
||||
|
||||
}
|
||||
if err = u.processField(subField, indirectValue.Field(i), m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !fieldOpts.optional() {
|
||||
required++
|
||||
if hasValue {
|
||||
requiredFilled++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filled && required != requiredFilled {
|
||||
return fmt.Errorf("%s is not fully set", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value,
|
||||
m Valuer, fullName string) error {
|
||||
maybeNewValue(field, value)
|
||||
fieldType := Deref(field.Type)
|
||||
indirectValue := reflect.Indirect(value)
|
||||
|
||||
for i := 0; i < fieldType.NumField(); i++ {
|
||||
if err := u.processField(fieldType.Field(i), indirectValue.Field(i), m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value, m Valuer,
|
||||
fullName string) error {
|
||||
if usingDifferentKeys(u.key, field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if field.Anonymous {
|
||||
return u.processAnonymousField(field, value, m, fullName)
|
||||
} else {
|
||||
return u.processNamedField(field, value, m, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldNotFromString(field reflect.StructField, value reflect.Value,
|
||||
mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error {
|
||||
fieldType := field.Type
|
||||
derefedFieldType := Deref(fieldType)
|
||||
typeKind := derefedFieldType.Kind()
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||
return u.processFieldStruct(field, value, mapValue, fullName)
|
||||
case valueKind == reflect.String && typeKind == reflect.Slice:
|
||||
return u.fillSliceFromString(fieldType, value, mapValue, fullName)
|
||||
case valueKind == reflect.String && derefedFieldType == durationType:
|
||||
return fillDurationValue(fieldType.Kind(), value, mapValue.(string))
|
||||
default:
|
||||
return u.processFieldPrimitive(field, value, mapValue, opts, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldPrimitive(field reflect.StructField, value reflect.Value,
|
||||
mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error {
|
||||
fieldType := field.Type
|
||||
typeKind := Deref(fieldType).Kind()
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||
return u.fillSlice(fieldType, value, mapValue)
|
||||
case typeKind == reflect.Map && valueKind == reflect.Map:
|
||||
return u.fillMap(field, value, mapValue)
|
||||
default:
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJsonNumber(field, value, v, opts, fullName)
|
||||
default:
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(opts.options(), mapValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fillWithSameType(field, value, mapValue, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newTypeMismatchError(fullName)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldPrimitiveWithJsonNumber(field reflect.StructField, value reflect.Value,
|
||||
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
||||
fieldType := field.Type
|
||||
fieldKind := fieldType.Kind()
|
||||
typeKind := Deref(fieldType).Kind()
|
||||
|
||||
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fieldKind == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch typeKind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
iValue, err := v.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.SetInt(iValue)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
iValue, err := v.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.SetUint(uint64(iValue))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fValue, err := v.Float64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.SetFloat(fValue)
|
||||
default:
|
||||
return newTypeMismatchError(fullName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldStruct(field reflect.StructField, value reflect.Value,
|
||||
mapValue interface{}, fullName string) error {
|
||||
convertedValue, ok := mapValue.(map[string]interface{})
|
||||
if !ok {
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
return fmt.Errorf("error: field: %s, expect map[string]interface{}, actual %v", fullName, valueKind)
|
||||
}
|
||||
|
||||
return u.processFieldStructWithMap(field, value, MapValuer(convertedValue), fullName)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldStructWithMap(field reflect.StructField, value reflect.Value,
|
||||
m Valuer, fullName string) error {
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
baseType := Deref(field.Type)
|
||||
target := reflect.New(baseType).Elem()
|
||||
if err := u.unmarshalWithFullName(m, target.Addr().Interface(), fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.Set(target.Addr())
|
||||
} else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
||||
m Valuer, fullName string) error {
|
||||
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullName = join(fullName, key)
|
||||
mapValue, hasValue := getValue(m, key)
|
||||
if hasValue {
|
||||
return u.processNamedFieldWithValue(field, value, mapValue, key, opts, fullName)
|
||||
} else {
|
||||
return u.processNamedFieldWithoutValue(field, value, opts, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, value reflect.Value,
|
||||
mapValue interface{}, key string, opts *fieldOptionsWithContext, fullName string) error {
|
||||
if mapValue == nil {
|
||||
if opts.optional() {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("field %s mustn't be nil", key)
|
||||
}
|
||||
}
|
||||
|
||||
maybeNewValue(field, value)
|
||||
|
||||
fieldKind := Deref(field.Type).Kind()
|
||||
switch fieldKind {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
|
||||
return u.processFieldNotFromString(field, value, mapValue, opts, fullName)
|
||||
default:
|
||||
if u.opts.fromString || opts.fromString() {
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
if valueKind != reflect.String {
|
||||
return fmt.Errorf("error: the value in map is not string, but %s", valueKind)
|
||||
}
|
||||
|
||||
options := opts.options()
|
||||
if len(options) > 0 {
|
||||
if !stringx.Contains(options, mapValue.(string)) {
|
||||
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in opts "%v"`,
|
||||
mapValue, key, options)
|
||||
}
|
||||
}
|
||||
|
||||
return fillPrimitive(field.Type, value, mapValue, opts, fullName)
|
||||
}
|
||||
|
||||
return u.processFieldNotFromString(field, value, mapValue, opts, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, value reflect.Value,
|
||||
opts *fieldOptionsWithContext, fullName string) error {
|
||||
derefedType := Deref(field.Type)
|
||||
fieldKind := derefedType.Kind()
|
||||
if defaultValue, ok := opts.getDefault(); ok {
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
maybeNewValue(field, value)
|
||||
value = value.Elem()
|
||||
}
|
||||
if derefedType == durationType {
|
||||
return fillDurationValue(fieldKind, value, defaultValue)
|
||||
}
|
||||
return setValue(fieldKind, value, defaultValue)
|
||||
}
|
||||
|
||||
switch fieldKind {
|
||||
case reflect.Array, reflect.Map, reflect.Slice:
|
||||
if !opts.optional() {
|
||||
return u.processFieldNotFromString(field, value, emptyMap, opts, fullName)
|
||||
}
|
||||
case reflect.Struct:
|
||||
if !opts.optional() {
|
||||
required, err := structValueRequired(u.key, derefedType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if required {
|
||||
return fmt.Errorf("%q is not set", fullName)
|
||||
}
|
||||
return u.processFieldNotFromString(field, value, emptyMap, opts, fullName)
|
||||
}
|
||||
default:
|
||||
if !opts.optional() {
|
||||
return newInitError(fullName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillMap(field reflect.StructField, value reflect.Value, mapValue interface{}) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
fieldKeyType := field.Type.Key()
|
||||
fieldElemType := field.Type.Elem()
|
||||
targetValue, err := u.generateMap(fieldKeyType, fieldElemType, mapValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.Set(targetValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue interface{}) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
baseKind := baseType.Kind()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
refValue := reflect.ValueOf(mapValue)
|
||||
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
|
||||
|
||||
var valid bool
|
||||
for i := 0; i < refValue.Len(); i++ {
|
||||
ithValue := refValue.Index(i).Interface()
|
||||
if ithValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
valid = true
|
||||
switch dereffedBaseKind {
|
||||
case reflect.Struct:
|
||||
target := reflect.New(dereffedBaseType)
|
||||
if err := u.Unmarshal(ithValue.(map[string]interface{}), target.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if baseKind == reflect.Ptr {
|
||||
conv.Index(i).Set(target)
|
||||
} else {
|
||||
conv.Index(i).Set(target.Elem())
|
||||
}
|
||||
default:
|
||||
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
value.Set(conv)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
|
||||
mapValue interface{}, fullName string) error {
|
||||
var slice []interface{}
|
||||
if err := jsonx.UnmarshalFromString(mapValue.(string), &slice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseFieldType := Deref(fieldType.Elem())
|
||||
baseFieldKind := baseFieldType.Kind()
|
||||
conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice))
|
||||
|
||||
for i := 0; i < len(slice); i++ {
|
||||
if err := u.fillSliceValue(conv, i, baseFieldKind, slice[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
value.Set(conv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind reflect.Kind, value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case json.Number:
|
||||
return setValue(baseKind, slice.Index(index), v.String())
|
||||
default:
|
||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
||||
// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
|
||||
if slice.Index(index).Kind() != reflect.TypeOf(value).Kind() {
|
||||
return errTypeMismatch
|
||||
}
|
||||
|
||||
slice.Index(index).Set(reflect.ValueOf(value))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue interface{}) (reflect.Value, error) {
|
||||
mapType := reflect.MapOf(keyType, elemType)
|
||||
valueType := reflect.TypeOf(mapValue)
|
||||
if mapType == valueType {
|
||||
return reflect.ValueOf(mapValue), nil
|
||||
}
|
||||
|
||||
refValue := reflect.ValueOf(mapValue)
|
||||
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
||||
fieldElemKind := elemType.Kind()
|
||||
dereffedElemType := Deref(elemType)
|
||||
dereffedElemKind := dereffedElemType.Kind()
|
||||
|
||||
for _, key := range refValue.MapKeys() {
|
||||
keythValue := refValue.MapIndex(key)
|
||||
keythData := keythValue.Interface()
|
||||
|
||||
switch dereffedElemKind {
|
||||
case reflect.Slice:
|
||||
target := reflect.New(dereffedElemType)
|
||||
if err := u.fillSlice(elemType, target.Elem(), keythData); err != nil {
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, target.Elem())
|
||||
case reflect.Struct:
|
||||
keythMap, ok := keythData.(map[string]interface{})
|
||||
if !ok {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
target := reflect.New(dereffedElemType)
|
||||
if err := u.Unmarshal(keythMap, target.Interface()); err != nil {
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
if fieldElemKind == reflect.Ptr {
|
||||
targetValue.SetMapIndex(key, target)
|
||||
} else {
|
||||
targetValue.SetMapIndex(key, target.Elem())
|
||||
}
|
||||
case reflect.Map:
|
||||
keythMap, ok := keythData.(map[string]interface{})
|
||||
if !ok {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
innerValue, err := u.generateMap(elemType.Key(), elemType.Elem(), keythMap)
|
||||
if err != nil {
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, innerValue)
|
||||
default:
|
||||
switch v := keythData.(type) {
|
||||
case string:
|
||||
targetValue.SetMapIndex(key, reflect.ValueOf(v))
|
||||
case json.Number:
|
||||
target := reflect.New(dereffedElemType)
|
||||
if err := setValue(dereffedElemKind, target.Elem(), v.String()); err != nil {
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, target.Elem())
|
||||
default:
|
||||
targetValue.SetMapIndex(key, keythValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetValue, nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Valuer, fullName string) (
|
||||
string, *fieldOptionsWithContext, error) {
|
||||
key, options, err := parseKeyAndOptions(u.key, field)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
} else if options == nil {
|
||||
return key, nil, nil
|
||||
}
|
||||
|
||||
optsWithContext, err := options.toOptionsWithContext(key, m, fullName)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return key, optsWithContext, nil
|
||||
}
|
||||
|
||||
func WithStringValues() UnmarshalOption {
|
||||
return func(opt *unmarshalOptions) {
|
||||
opt.fromString = true
|
||||
}
|
||||
}
|
||||
|
||||
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
||||
d, err := time.ParseDuration(dur)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fieldKind == reflect.Ptr {
|
||||
value.Elem().Set(reflect.ValueOf(d))
|
||||
} else {
|
||||
value.Set(reflect.ValueOf(d))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interface{},
|
||||
opts *fieldOptionsWithContext, fullName string) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
baseType := Deref(fieldType)
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
target := reflect.New(baseType).Elem()
|
||||
switch mapValue.(type) {
|
||||
case string, json.Number:
|
||||
value.Set(target.Addr())
|
||||
value = target
|
||||
}
|
||||
}
|
||||
|
||||
switch v := mapValue.(type) {
|
||||
case string:
|
||||
return validateAndSetValue(baseType.Kind(), value, v, opts)
|
||||
case json.Number:
|
||||
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return setValue(baseType.Kind(), value, v.String())
|
||||
default:
|
||||
return newTypeMismatchError(fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func fillWithSameType(field reflect.StructField, value reflect.Value, mapValue interface{},
|
||||
opts *fieldOptionsWithContext) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
if err := validateValueRange(mapValue, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
baseType := Deref(field.Type)
|
||||
target := reflect.New(baseType).Elem()
|
||||
target.Set(reflect.ValueOf(mapValue))
|
||||
value.Set(target.Addr())
|
||||
} else {
|
||||
value.Set(reflect.ValueOf(mapValue))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
||||
func getValue(m Valuer, key string) (interface{}, bool) {
|
||||
keys := readKeys(key)
|
||||
return getValueWithChainedKeys(m, keys)
|
||||
}
|
||||
|
||||
func getValueWithChainedKeys(m Valuer, keys []string) (interface{}, bool) {
|
||||
if len(keys) == 1 {
|
||||
v, ok := m.Value(keys[0])
|
||||
return v, ok
|
||||
} else if len(keys) > 1 {
|
||||
if v, ok := m.Value(keys[0]); ok {
|
||||
if nextm, ok := v.(map[string]interface{}); ok {
|
||||
return getValueWithChainedKeys(MapValuer(nextm), keys[1:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func insertKeys(key string, cache []string) {
|
||||
cacheKeysLock.Lock()
|
||||
defer cacheKeysLock.Unlock()
|
||||
|
||||
keys := cacheKeys.Load().(keyCache)
|
||||
// copy the contents into the new map, to guarantee the old map is immutable
|
||||
newKeys := make(keyCache)
|
||||
for k, v := range keys {
|
||||
newKeys[k] = v
|
||||
}
|
||||
newKeys[key] = cache
|
||||
cacheKeys.Store(newKeys)
|
||||
}
|
||||
|
||||
func join(elem ...string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
var fillSep bool
|
||||
for _, e := range elem {
|
||||
if len(e) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if fillSep {
|
||||
builder.WriteByte(delimiter)
|
||||
} else {
|
||||
fillSep = true
|
||||
}
|
||||
|
||||
builder.WriteString(e)
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func newInitError(name string) error {
|
||||
return fmt.Errorf("field %s is not set", name)
|
||||
}
|
||||
|
||||
func newTypeMismatchError(name string) error {
|
||||
return fmt.Errorf("error: type mismatch for field %s", name)
|
||||
}
|
||||
|
||||
func readKeys(key string) []string {
|
||||
cache := cacheKeys.Load().(keyCache)
|
||||
if keys, ok := cache[key]; ok {
|
||||
return keys
|
||||
}
|
||||
|
||||
keys := strings.FieldsFunc(key, func(c rune) bool {
|
||||
return c == delimiter
|
||||
})
|
||||
insertKeys(key, keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
2469
core/mapping/unmarshaler_test.go
Normal file
2469
core/mapping/unmarshaler_test.go
Normal file
File diff suppressed because it is too large
Load Diff
513
core/mapping/utils.go
Normal file
513
core/mapping/utils.go
Normal file
@@ -0,0 +1,513 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultOption = "default"
|
||||
stringOption = "string"
|
||||
optionalOption = "optional"
|
||||
optionsOption = "options"
|
||||
rangeOption = "range"
|
||||
optionSeparator = "|"
|
||||
equalToken = "="
|
||||
)
|
||||
|
||||
var (
|
||||
errUnsupportedType = errors.New("unsupported type on setting field value")
|
||||
errNumberRange = errors.New("wrong number range setting")
|
||||
optionsCache = make(map[string]optionsCacheValue)
|
||||
cacheLock sync.RWMutex
|
||||
structRequiredCache = make(map[reflect.Type]requiredCacheValue)
|
||||
structCacheLock sync.RWMutex
|
||||
)
|
||||
|
||||
type (
|
||||
optionsCacheValue struct {
|
||||
key string
|
||||
options *fieldOptions
|
||||
err error
|
||||
}
|
||||
|
||||
requiredCacheValue struct {
|
||||
required bool
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func Deref(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func Repr(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// if func (v *Type) String() string, we can't use Elem()
|
||||
switch vt := v.(type) {
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch vt := val.Interface().(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(vt)
|
||||
case error:
|
||||
return vt.Error()
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(vt, 'f', -1, 64)
|
||||
case fmt.Stringer:
|
||||
return vt.String()
|
||||
case int:
|
||||
return strconv.Itoa(vt)
|
||||
case int8:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int16:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int32:
|
||||
return strconv.Itoa(int(vt))
|
||||
case int64:
|
||||
return strconv.FormatInt(vt, 10)
|
||||
case string:
|
||||
return vt
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(vt), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(vt, 10)
|
||||
case []byte:
|
||||
return string(vt)
|
||||
default:
|
||||
return fmt.Sprint(val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
func ValidatePtr(v *reflect.Value) error {
|
||||
// sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr,
|
||||
// panic otherwise
|
||||
if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return fmt.Errorf("not a valid pointer: %v", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertType(kind reflect.Kind, str string) (interface{}, error) {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return str == "1" || strings.ToLower(str) == "true", nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if intValue, err := strconv.ParseInt(str, 10, 64); err != nil {
|
||||
return 0, fmt.Errorf("the value %q cannot parsed as int", str)
|
||||
} else {
|
||||
return intValue, nil
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if uintValue, err := strconv.ParseUint(str, 10, 64); err != nil {
|
||||
return 0, fmt.Errorf("the value %q cannot parsed as uint", str)
|
||||
} else {
|
||||
return uintValue, nil
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if floatValue, err := strconv.ParseFloat(str, 64); err != nil {
|
||||
return 0, fmt.Errorf("the value %q cannot parsed as float", str)
|
||||
} else {
|
||||
return floatValue, nil
|
||||
}
|
||||
case reflect.String:
|
||||
return str, nil
|
||||
default:
|
||||
return nil, errUnsupportedType
|
||||
}
|
||||
}
|
||||
|
||||
func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) {
|
||||
segments := strings.Split(value, ",")
|
||||
key := strings.TrimSpace(segments[0])
|
||||
options := segments[1:]
|
||||
|
||||
if len(options) > 0 {
|
||||
var fieldOpts fieldOptions
|
||||
|
||||
for _, segment := range options {
|
||||
option := strings.TrimSpace(segment)
|
||||
switch {
|
||||
case option == stringOption:
|
||||
fieldOpts.FromString = true
|
||||
case strings.HasPrefix(option, optionalOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
switch len(segs) {
|
||||
case 1:
|
||||
fieldOpts.Optional = true
|
||||
case 2:
|
||||
fieldOpts.Optional = true
|
||||
fieldOpts.OptionalDep = segs[1]
|
||||
default:
|
||||
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
|
||||
}
|
||||
case option == optionalOption:
|
||||
fieldOpts.Optional = true
|
||||
case strings.HasPrefix(option, optionsOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
|
||||
} else {
|
||||
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
|
||||
}
|
||||
case strings.HasPrefix(option, defaultOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
|
||||
} else {
|
||||
fieldOpts.Default = strings.TrimSpace(segs[1])
|
||||
}
|
||||
case strings.HasPrefix(option, rangeOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
|
||||
}
|
||||
if nr, err := parseNumberRange(segs[1]); err != nil {
|
||||
return "", nil, err
|
||||
} else {
|
||||
fieldOpts.Range = nr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key, &fieldOpts, nil
|
||||
}
|
||||
|
||||
return key, nil, nil
|
||||
}
|
||||
|
||||
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
||||
numFields := tp.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
childField := tp.Field(i)
|
||||
if usingDifferentKeys(tag, childField) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
_, opts, err := parseKeyAndOptions(tag, childField)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
if childField.Type.Kind() != reflect.Struct {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if required, err := implicitValueRequiredStruct(tag, childField.Type); err != nil {
|
||||
return false, err
|
||||
} else if required {
|
||||
return true, nil
|
||||
}
|
||||
} else if !opts.Optional && len(opts.Default) == 0 {
|
||||
return true, nil
|
||||
} else if len(opts.OptionalDep) > 0 && opts.OptionalDep[0] == notSymbol {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func maybeNewValue(field reflect.StructField, value reflect.Value) {
|
||||
if field.Type.Kind() == reflect.Ptr && value.IsNil() {
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
}
|
||||
}
|
||||
|
||||
// don't modify returned fieldOptions, it's cached and shared among different calls.
|
||||
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
|
||||
value := field.Tag.Get(tagName)
|
||||
if len(value) == 0 {
|
||||
return field.Name, nil, nil
|
||||
}
|
||||
|
||||
cacheLock.RLock()
|
||||
cache, ok := optionsCache[value]
|
||||
cacheLock.RUnlock()
|
||||
if ok {
|
||||
return stringx.TakeOne(cache.key, field.Name), cache.options, cache.err
|
||||
}
|
||||
|
||||
key, options, err := doParseKeyAndOptions(field, value)
|
||||
cacheLock.Lock()
|
||||
optionsCache[value] = optionsCacheValue{
|
||||
key: key,
|
||||
options: options,
|
||||
err: err,
|
||||
}
|
||||
cacheLock.Unlock()
|
||||
|
||||
return stringx.TakeOne(key, field.Name), options, err
|
||||
}
|
||||
|
||||
// support below notations:
|
||||
// [:5] (:5] [:5) (:5)
|
||||
// [1:] [1:) (1:] (1:)
|
||||
// [1:5] [1:5) (1:5] (1:5)
|
||||
func parseNumberRange(str string) (*numberRange, error) {
|
||||
if len(str) == 0 {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
var leftInclude bool
|
||||
switch str[0] {
|
||||
case '[':
|
||||
leftInclude = true
|
||||
case '(':
|
||||
leftInclude = false
|
||||
default:
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
str = str[1:]
|
||||
if len(str) == 0 {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
var rightInclude bool
|
||||
switch str[len(str)-1] {
|
||||
case ']':
|
||||
rightInclude = true
|
||||
case ')':
|
||||
rightInclude = false
|
||||
default:
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
str = str[:len(str)-1]
|
||||
fields := strings.Split(str, ":")
|
||||
if len(fields) != 2 {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
if len(fields[0]) == 0 && len(fields[1]) == 0 {
|
||||
return nil, errNumberRange
|
||||
}
|
||||
|
||||
var left float64
|
||||
if len(fields[0]) > 0 {
|
||||
var err error
|
||||
if left, err = strconv.ParseFloat(fields[0], 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
left = -math.MaxFloat64
|
||||
}
|
||||
|
||||
var right float64
|
||||
if len(fields[1]) > 0 {
|
||||
var err error
|
||||
if right, err = strconv.ParseFloat(fields[1], 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
right = math.MaxFloat64
|
||||
}
|
||||
|
||||
return &numberRange{
|
||||
left: left,
|
||||
leftInclude: leftInclude,
|
||||
right: right,
|
||||
rightInclude: rightInclude,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v interface{}) error {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
value.SetBool(v.(bool))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
value.SetInt(v.(int64))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
value.SetUint(v.(uint64))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
value.SetFloat(v.(float64))
|
||||
case reflect.String:
|
||||
value.SetString(v.(string))
|
||||
default:
|
||||
return errUnsupportedType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setValue(kind reflect.Kind, value reflect.Value, str string) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
v, err := convertType(kind, str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setMatchedPrimitiveValue(kind, value, v)
|
||||
}
|
||||
|
||||
func structValueRequired(tag string, tp reflect.Type) (bool, error) {
|
||||
structCacheLock.RLock()
|
||||
val, ok := structRequiredCache[tp]
|
||||
structCacheLock.RUnlock()
|
||||
if ok {
|
||||
return val.required, val.err
|
||||
}
|
||||
|
||||
required, err := implicitValueRequiredStruct(tag, tp)
|
||||
structCacheLock.Lock()
|
||||
structRequiredCache[tp] = requiredCacheValue{
|
||||
required: required,
|
||||
err: err,
|
||||
}
|
||||
structCacheLock.Unlock()
|
||||
|
||||
return required, err
|
||||
}
|
||||
|
||||
func toFloat64(v interface{}) (float64, bool) {
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
return float64(val), true
|
||||
case int8:
|
||||
return float64(val), true
|
||||
case int16:
|
||||
return float64(val), true
|
||||
case int32:
|
||||
return float64(val), true
|
||||
case int64:
|
||||
return float64(val), true
|
||||
case uint:
|
||||
return float64(val), true
|
||||
case uint8:
|
||||
return float64(val), true
|
||||
case uint16:
|
||||
return float64(val), true
|
||||
case uint32:
|
||||
return float64(val), true
|
||||
case uint64:
|
||||
return float64(val), true
|
||||
case float32:
|
||||
return float64(val), true
|
||||
case float64:
|
||||
return val, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func usingDifferentKeys(key string, field reflect.StructField) bool {
|
||||
if len(field.Tag) > 0 {
|
||||
if _, ok := field.Tag.Lookup(key); !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
v, err := convertType(kind, str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateValueRange(v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setMatchedPrimitiveValue(kind, value, v)
|
||||
}
|
||||
|
||||
func validateJsonNumberRange(v json.Number, opts *fieldOptionsWithContext) error {
|
||||
if opts == nil || opts.Range == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fv, err := v.Float64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateNumberRange(fv, opts.Range)
|
||||
}
|
||||
|
||||
func validateNumberRange(fv float64, nr *numberRange) error {
|
||||
if nr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if (nr.leftInclude && fv < nr.left) || (!nr.leftInclude && fv <= nr.left) {
|
||||
return errNumberRange
|
||||
}
|
||||
|
||||
if (nr.rightInclude && fv > nr.right) || (!nr.rightInclude && fv >= nr.right) {
|
||||
return errNumberRange
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateValueInOptions(options []string, value interface{}) error {
|
||||
if len(options) > 0 {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
if !stringx.Contains(options, v) {
|
||||
return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options)
|
||||
}
|
||||
default:
|
||||
if !stringx.Contains(options, Repr(v)) {
|
||||
return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, value, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateValueRange(mapValue interface{}, opts *fieldOptionsWithContext) error {
|
||||
if opts == nil || opts.Range == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fv, ok := toFloat64(mapValue)
|
||||
if !ok {
|
||||
return errNumberRange
|
||||
}
|
||||
|
||||
return validateNumberRange(fv, opts.Range)
|
||||
}
|
||||
295
core/mapping/utils_test.go
Normal file
295
core/mapping/utils_test.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testTagName = "key"
|
||||
|
||||
type Foo struct {
|
||||
Str string
|
||||
StrWithTag string `key:"stringwithtag"`
|
||||
StrWithTagAndOption string `key:"stringwithtag,string"`
|
||||
}
|
||||
|
||||
func TestDeferInt(t *testing.T) {
|
||||
var i = 1
|
||||
var s = "hello"
|
||||
number := struct {
|
||||
f float64
|
||||
}{
|
||||
f: 6.4,
|
||||
}
|
||||
cases := []struct {
|
||||
t reflect.Type
|
||||
expect reflect.Kind
|
||||
}{
|
||||
{
|
||||
t: reflect.TypeOf(i),
|
||||
expect: reflect.Int,
|
||||
},
|
||||
{
|
||||
t: reflect.TypeOf(&i),
|
||||
expect: reflect.Int,
|
||||
},
|
||||
{
|
||||
t: reflect.TypeOf(s),
|
||||
expect: reflect.String,
|
||||
},
|
||||
{
|
||||
t: reflect.TypeOf(&s),
|
||||
expect: reflect.String,
|
||||
},
|
||||
{
|
||||
t: reflect.TypeOf(number.f),
|
||||
expect: reflect.Float64,
|
||||
},
|
||||
{
|
||||
t: reflect.TypeOf(&number.f),
|
||||
expect: reflect.Float64,
|
||||
},
|
||||
}
|
||||
|
||||
for _, each := range cases {
|
||||
t.Run(each.t.String(), func(t *testing.T) {
|
||||
assert.Equal(t, each.expect, Deref(each.t).Kind())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionWithoutTag(t *testing.T) {
|
||||
var foo Foo
|
||||
rte := reflect.TypeOf(&foo).Elem()
|
||||
field, _ := rte.FieldByName("Str")
|
||||
key, options, err := parseKeyAndOptions(testTagName, field)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Str", key)
|
||||
assert.Nil(t, options)
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionWithTagWithoutOption(t *testing.T) {
|
||||
var foo Foo
|
||||
rte := reflect.TypeOf(&foo).Elem()
|
||||
field, _ := rte.FieldByName("StrWithTag")
|
||||
key, options, err := parseKeyAndOptions(testTagName, field)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "stringwithtag", key)
|
||||
assert.Nil(t, options)
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionWithTagAndOption(t *testing.T) {
|
||||
var foo Foo
|
||||
rte := reflect.TypeOf(&foo).Elem()
|
||||
field, _ := rte.FieldByName("StrWithTagAndOption")
|
||||
key, options, err := parseKeyAndOptions(testTagName, field)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "stringwithtag", key)
|
||||
assert.True(t, options.FromString)
|
||||
}
|
||||
|
||||
func TestValidatePtrWithNonPtr(t *testing.T) {
|
||||
var foo string
|
||||
rve := reflect.ValueOf(foo)
|
||||
assert.NotNil(t, ValidatePtr(&rve))
|
||||
}
|
||||
|
||||
func TestValidatePtrWithPtr(t *testing.T) {
|
||||
var foo string
|
||||
rve := reflect.ValueOf(&foo)
|
||||
assert.Nil(t, ValidatePtr(&rve))
|
||||
}
|
||||
|
||||
func TestValidatePtrWithNilPtr(t *testing.T) {
|
||||
var foo *string
|
||||
rve := reflect.ValueOf(foo)
|
||||
assert.NotNil(t, ValidatePtr(&rve))
|
||||
}
|
||||
|
||||
func TestValidatePtrWithZeroValue(t *testing.T) {
|
||||
var s string
|
||||
e := reflect.Zero(reflect.TypeOf(s))
|
||||
assert.NotNil(t, ValidatePtr(&e))
|
||||
}
|
||||
|
||||
func TestSetValueNotSettable(t *testing.T) {
|
||||
var i int
|
||||
assert.NotNil(t, setValue(reflect.Int, reflect.ValueOf(i), "1"))
|
||||
}
|
||||
|
||||
func TestParseKeyAndOptionsErrors(t *testing.T) {
|
||||
type Bar struct {
|
||||
OptionsValue string `key:",options=a=b"`
|
||||
DefaultValue string `key:",default=a=b"`
|
||||
}
|
||||
|
||||
var bar Bar
|
||||
_, _, err := parseKeyAndOptions("key", reflect.TypeOf(&bar).Elem().Field(0))
|
||||
assert.NotNil(t, err)
|
||||
_, _, err = parseKeyAndOptions("key", reflect.TypeOf(&bar).Elem().Field(1))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestSetValueFormatErrors(t *testing.T) {
|
||||
type Bar struct {
|
||||
IntValue int
|
||||
UintValue uint
|
||||
FloatValue float32
|
||||
MapValue map[string]interface{}
|
||||
}
|
||||
|
||||
var bar Bar
|
||||
tests := []struct {
|
||||
kind reflect.Kind
|
||||
target reflect.Value
|
||||
value string
|
||||
}{
|
||||
{
|
||||
kind: reflect.Int,
|
||||
target: reflect.ValueOf(&bar.IntValue).Elem(),
|
||||
value: "a",
|
||||
},
|
||||
{
|
||||
kind: reflect.Uint,
|
||||
target: reflect.ValueOf(&bar.UintValue).Elem(),
|
||||
value: "a",
|
||||
},
|
||||
{
|
||||
kind: reflect.Float32,
|
||||
target: reflect.ValueOf(&bar.FloatValue).Elem(),
|
||||
value: "a",
|
||||
},
|
||||
{
|
||||
kind: reflect.Map,
|
||||
target: reflect.ValueOf(&bar.MapValue).Elem(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.kind.String(), func(t *testing.T) {
|
||||
err := setValue(test.kind, test.target, test.value)
|
||||
assert.NotEqual(t, errValueNotSettable, err)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepr(t *testing.T) {
|
||||
var (
|
||||
f32 float32 = 1.1
|
||||
f64 = 2.2
|
||||
i8 int8 = 1
|
||||
i16 int16 = 2
|
||||
i32 int32 = 3
|
||||
i64 int64 = 4
|
||||
u8 uint8 = 5
|
||||
u16 uint16 = 6
|
||||
u32 uint32 = 7
|
||||
u64 uint64 = 8
|
||||
)
|
||||
tests := []struct {
|
||||
v interface{}
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
mockStringable{},
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
new(mockStringable),
|
||||
"mocked",
|
||||
},
|
||||
{
|
||||
newMockPtr(),
|
||||
"mockptr",
|
||||
},
|
||||
{
|
||||
true,
|
||||
"true",
|
||||
},
|
||||
{
|
||||
false,
|
||||
"false",
|
||||
},
|
||||
{
|
||||
f32,
|
||||
"1.1",
|
||||
},
|
||||
{
|
||||
f64,
|
||||
"2.2",
|
||||
},
|
||||
{
|
||||
i8,
|
||||
"1",
|
||||
},
|
||||
{
|
||||
i16,
|
||||
"2",
|
||||
},
|
||||
{
|
||||
i32,
|
||||
"3",
|
||||
},
|
||||
{
|
||||
i64,
|
||||
"4",
|
||||
},
|
||||
{
|
||||
u8,
|
||||
"5",
|
||||
},
|
||||
{
|
||||
u16,
|
||||
"6",
|
||||
},
|
||||
{
|
||||
u32,
|
||||
"7",
|
||||
},
|
||||
{
|
||||
u64,
|
||||
"8",
|
||||
},
|
||||
{
|
||||
[]byte(`abcd`),
|
||||
"abcd",
|
||||
},
|
||||
{
|
||||
mockOpacity{val: 1},
|
||||
"{1}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expect, func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, Repr(test.v))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockStringable struct{}
|
||||
|
||||
func (m mockStringable) String() string {
|
||||
return "mocked"
|
||||
}
|
||||
|
||||
type mockPtr struct{}
|
||||
|
||||
func newMockPtr() *mockPtr {
|
||||
return new(mockPtr)
|
||||
}
|
||||
|
||||
func (m *mockPtr) String() string {
|
||||
return "mockptr"
|
||||
}
|
||||
|
||||
type mockOpacity struct {
|
||||
val int
|
||||
}
|
||||
14
core/mapping/valuer.go
Normal file
14
core/mapping/valuer.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mapping
|
||||
|
||||
type (
|
||||
Valuer interface {
|
||||
Value(key string) (interface{}, bool)
|
||||
}
|
||||
|
||||
MapValuer map[string]interface{}
|
||||
)
|
||||
|
||||
func (mv MapValuer) Value(key string) (interface{}, bool) {
|
||||
v, ok := mv[key]
|
||||
return v, ok
|
||||
}
|
||||
95
core/mapping/yamlunmarshaler.go
Normal file
95
core/mapping/yamlunmarshaler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// To make .json & .yaml consistent, we just use json as the tag key.
|
||||
const yamlTagKey = "json"
|
||||
|
||||
var (
|
||||
ErrUnsupportedType = errors.New("only map-like configs are suported")
|
||||
|
||||
yamlUnmarshaler = NewUnmarshaler(yamlTagKey)
|
||||
)
|
||||
|
||||
func UnmarshalYamlBytes(content []byte, v interface{}) error {
|
||||
return unmarshalYamlBytes(content, v, yamlUnmarshaler)
|
||||
}
|
||||
|
||||
func UnmarshalYamlReader(reader io.Reader, v interface{}) error {
|
||||
return unmarshalYamlReader(reader, v, yamlUnmarshaler)
|
||||
}
|
||||
|
||||
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var o interface{}
|
||||
if err := yamlUnmarshal(content, &o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m, ok := o.(map[string]interface{}); ok {
|
||||
return unmarshaler.Unmarshal(m, v)
|
||||
} else {
|
||||
return ErrUnsupportedType
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
content, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshalYamlBytes(content, v, unmarshaler)
|
||||
}
|
||||
|
||||
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
|
||||
func yamlUnmarshal(in []byte, out interface{}) error {
|
||||
var res interface{}
|
||||
if err := yaml.Unmarshal(in, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*out.(*interface{}) = cleanupMapValue(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
for k, v := range in {
|
||||
res[Repr(k)] = cleanupMapValue(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func cleanupInterfaceNumber(in interface{}) json.Number {
|
||||
return json.Number(Repr(in))
|
||||
}
|
||||
|
||||
func cleanupInterfaceSlice(in []interface{}) []interface{} {
|
||||
res := make([]interface{}, len(in))
|
||||
for i, v := range in {
|
||||
res[i] = cleanupMapValue(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func cleanupMapValue(v interface{}) interface{} {
|
||||
switch v := v.(type) {
|
||||
case []interface{}:
|
||||
return cleanupInterfaceSlice(v)
|
||||
case map[interface{}]interface{}:
|
||||
return cleanupInterfaceMap(v)
|
||||
case bool, string:
|
||||
return v
|
||||
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64:
|
||||
return cleanupInterfaceNumber(v)
|
||||
default:
|
||||
return Repr(v)
|
||||
}
|
||||
}
|
||||
920
core/mapping/yamlunmarshaler_test.go
Normal file
920
core/mapping/yamlunmarshaler_test.go
Normal file
@@ -0,0 +1,920 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnmarshalYamlBytes(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
}
|
||||
content := []byte(`Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
content := []byte(`Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesOptionalDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",optional,default=1"`
|
||||
}
|
||||
content := []byte(`Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesDefaultOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age int `json:",default=1,optional"`
|
||||
}
|
||||
content := []byte(`Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Name string `json:",default=liao"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesBool(t *testing.T) {
|
||||
var c struct {
|
||||
Great bool
|
||||
}
|
||||
content := []byte(`Great: true`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.True(t, c.Great)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesInt(t *testing.T) {
|
||||
var c struct {
|
||||
Age int
|
||||
}
|
||||
content := []byte(`Age: 1`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesUint(t *testing.T) {
|
||||
var c struct {
|
||||
Age uint
|
||||
}
|
||||
content := []byte(`Age: 1`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, uint(1), c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesFloat(t *testing.T) {
|
||||
var c struct {
|
||||
Age float32
|
||||
}
|
||||
content := []byte(`Age: 1.5`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, float32(1.5), c.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMustInOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMustInOptionalMissedPart(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
There: sure`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMustInOptionalOnlyOptionalFilled(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
There string
|
||||
Must string
|
||||
Optional string `json:",optional"`
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Optional: sure`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Age float32
|
||||
}
|
||||
content := []byte(`Age: 1.5`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStruct(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStructPtrOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
Age int `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStructPtrDefault(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Name string
|
||||
Age int `json:",default=4"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Inner:
|
||||
Name: liao`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "liao", c.Inner.Name)
|
||||
assert.Equal(t, 4, c.Inner.Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceString(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
}
|
||||
content := []byte(`Names:
|
||||
- liao
|
||||
- chaoxin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []string{"liao", "chaoxin"}
|
||||
if !reflect.DeepEqual(c.Names, want) {
|
||||
t.Fatalf("want %q, got %q", c.Names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStringOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
Age []int `json:",optional"`
|
||||
}
|
||||
content := []byte(`Names:
|
||||
- liao
|
||||
- chaoxin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []string{"liao", "chaoxin"}
|
||||
if !reflect.DeepEqual(c.Names, want) {
|
||||
t.Fatalf("want %q, got %q", c.Names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStruct(t *testing.T) {
|
||||
var c struct {
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []struct {
|
||||
Name string
|
||||
Age int
|
||||
}{
|
||||
{"liao", 1},
|
||||
{"chaoxin", 2},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %q, got %q", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}{
|
||||
{"liao", 1, nil},
|
||||
{"chaoxin", 2, nil},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %q, got %q", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
}{
|
||||
{"liao", 1},
|
||||
{"chaoxin", 2},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %v, got %v", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructPtrOptional(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string `json:",optional"`
|
||||
}{
|
||||
{"liao", 1, nil},
|
||||
{"chaoxin", 2, nil},
|
||||
}
|
||||
if !reflect.DeepEqual(c.People, want) {
|
||||
t.Fatalf("want %v, got %v", c.People, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructPtrPartial(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructPtrDefault(t *testing.T) {
|
||||
var c struct {
|
||||
People []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string `json:",default=chaoxin@liao.com"`
|
||||
}
|
||||
}
|
||||
content := []byte(`People:
|
||||
- Name: liao
|
||||
Age: 1
|
||||
- Name: chaoxin
|
||||
Age: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
|
||||
want := []*struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}{
|
||||
{"liao", 1, "chaoxin@liao.com"},
|
||||
{"chaoxin", 2, "chaoxin@liao.com"},
|
||||
}
|
||||
|
||||
for i := range c.People {
|
||||
actual := c.People[i]
|
||||
expect := want[i]
|
||||
assert.Equal(t, expect.Age, actual.Age)
|
||||
assert.Equal(t, expect.Email, actual.Email)
|
||||
assert.Equal(t, expect.Name, actual.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStringPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Names []string
|
||||
Age int
|
||||
}
|
||||
content := []byte(`Age: 1`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesSliceStructPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Group string
|
||||
People []struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`Group: chaoxin`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesInnerAnonymousPartial(t *testing.T) {
|
||||
type (
|
||||
Deep struct {
|
||||
A string
|
||||
B string `json:",optional"`
|
||||
}
|
||||
Inner struct {
|
||||
Deep
|
||||
InnerV string `json:",optional"`
|
||||
}
|
||||
)
|
||||
|
||||
var c struct {
|
||||
Value Inner `json:",optional"`
|
||||
}
|
||||
content := []byte(`Value:
|
||||
InnerV: chaoxin`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesStructPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Group string
|
||||
Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
}
|
||||
content := []byte(`Group: chaoxin`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesEmptyMap(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]int `json:",optional"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Empty(t, c.Persons)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMap(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]int
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first: 1
|
||||
second: 2`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 2, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"])
|
||||
assert.Equal(t, 2, c.Persons["second"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStruct(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
Id: 1
|
||||
name: kevin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
Id: 1
|
||||
name: kevin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructMissingPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
Id: 1`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
Id: 1`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"].Id)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
- Id: 1
|
||||
name: kevin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"][0].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapEmptyStructSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first: []`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Empty(t, c.Persons["first"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructPtrSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
- Id: 1
|
||||
name: kevin`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
assert.Equal(t, "kevin", c.Persons["first"][0].Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapEmptyStructPtrSlice(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first: []`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Empty(t, c.Persons["first"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructPtrSliceMissingPartial(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
- Id: 1`)
|
||||
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesMapStructPtrSliceOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Persons map[string][]*struct {
|
||||
Id int
|
||||
Name string `json:"name,optional"`
|
||||
}
|
||||
}
|
||||
content := []byte(`Persons:
|
||||
first:
|
||||
- Id: 1`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, 1, len(c.Persons))
|
||||
assert.Equal(t, 1, c.Persons["first"][0].Id)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlStructOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Etcd struct {
|
||||
Hosts []string
|
||||
Key string
|
||||
} `json:",optional"`
|
||||
}
|
||||
content := []byte(`Name: kevin`)
|
||||
|
||||
err := UnmarshalYamlBytes(content, &c)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "kevin", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlStructLowerCase(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
Etcd struct {
|
||||
Key string
|
||||
} `json:"etcd"`
|
||||
}
|
||||
content := []byte(`Name: kevin
|
||||
etcd:
|
||||
Key: the key`)
|
||||
|
||||
err := UnmarshalYamlBytes(content, &c)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "kevin", c.Name)
|
||||
assert.Equal(t, "the key", c.Etcd.Key)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalWithEmpty(t *testing.T) {
|
||||
var c struct {
|
||||
Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalPtr(t *testing.T) {
|
||||
var c struct {
|
||||
Inner *struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
In Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "", c.In.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructPtrOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
In *Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Nil(t, c.In)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalProvoidedAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure
|
||||
Optional: optional`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "optional", c.Optional)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAllOptionalProvoidedAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Optional string `json:",optional"`
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure
|
||||
Optional: optional`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "optional", c.Optional)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAnonymous(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure
|
||||
Must: must`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "must", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAnonymousPtr(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure
|
||||
Must: must`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "must", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructAnonymousOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Equal(t, "", c.Must)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithStructPtrAnonymousOptional(t *testing.T) {
|
||||
type Inner struct {
|
||||
Must string
|
||||
}
|
||||
|
||||
var c struct {
|
||||
*Inner `json:",optional"`
|
||||
Else string
|
||||
}
|
||||
content := []byte(`Else: sure`)
|
||||
|
||||
assert.Nil(t, UnmarshalYamlBytes(content, &c))
|
||||
assert.Equal(t, "sure", c.Else)
|
||||
assert.Nil(t, c.Inner)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlWithZeroValues(t *testing.T) {
|
||||
type inner struct {
|
||||
False bool `json:"negative"`
|
||||
Int int `json:"int"`
|
||||
String string `json:"string"`
|
||||
}
|
||||
content := []byte(`negative: false
|
||||
int: 0
|
||||
string: ""`)
|
||||
|
||||
var in inner
|
||||
ast := assert.New(t)
|
||||
ast.Nil(UnmarshalYamlBytes(content, &in))
|
||||
ast.False(in.False)
|
||||
ast.Equal(0, in.Int)
|
||||
ast.Equal("", in.String)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesError(t *testing.T) {
|
||||
payload := `abcd:
|
||||
- cdef`
|
||||
var v struct {
|
||||
Any []string `json:"abcd"`
|
||||
}
|
||||
|
||||
err := UnmarshalYamlBytes([]byte(payload), &v)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(v.Any))
|
||||
assert.Equal(t, "cdef", v.Any[0])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlReaderError(t *testing.T) {
|
||||
payload := `abcd: cdef`
|
||||
reader := strings.NewReader(payload)
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalYamlReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user