github.com/urso/go-structform@v0.0.2/gotype/fold_test.go (about)

     1  package gotype
     2  
     3  import (
     4  	"bytes"
     5  	gojson "encoding/json"
     6  	"errors"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	structform "github.com/urso/go-structform"
    12  	"github.com/urso/go-structform/json"
    13  	"github.com/urso/go-structform/sftest"
    14  )
    15  
    16  var foldSamples = []struct {
    17  	json  string
    18  	value interface{}
    19  }{
    20  	// primitives
    21  	{`null`, nil},
    22  	{`true`, true},
    23  	{`false`, false},
    24  	{`10`, int8(10)},
    25  	{`10`, int32(10)},
    26  	{`10`, int(10)},
    27  	{`10`, uint(10)},
    28  	{`10`, uint8(10)},
    29  	{`10`, uint16(10)},
    30  	{`10`, uint32(10)},
    31  	{`12340`, uint16(12340)},
    32  	{`1234567`, uint32(1234567)},
    33  	{`12345678190`, uint64(12345678190)},
    34  	{`-10`, int8(-10)},
    35  	{`-10`, int32(-10)},
    36  	{`-10`, int(-10)},
    37  	{`3.14`, float32(3.14)},
    38  	{`3.14`, float64(3.14)},
    39  	{`"test"`, "test"},
    40  	{`"test with \" being escaped"`, "test with \" being escaped"},
    41  
    42  	// arrays
    43  	{`[]`, []uint8{}},
    44  	{`[]`, []string{}},
    45  	{`[]`, []interface{}{}},
    46  	{`[]`, []struct{ A string }{}},
    47  	{`[[]]`, [][]uint8{{}}},
    48  	{`[[]]`, [][]string{{}}},
    49  	{`[[]]`, [][]interface{}{{}}},
    50  	{`[[]]`, [][]struct{ A string }{{}}},
    51  	{
    52  		`[null,true,false,12345678910,3.14,"test"]`,
    53  		[]interface{}{nil, true, false, uint64(12345678910), 3.14, "test"},
    54  	},
    55  	{`[1,2,3,4,5,6,7,8,9,10]`, []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    56  	{`[1,2,3,4,5,6,7,8,9,10]`, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    57  	{`[1,2,3,4,5,6,7,8,9,10]`, []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    58  	{`[1,2,3,4,5,6,7,8,9,10]`, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    59  	{`[1,2,3,4,5,6,7,8,9,10]`, []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    60  	{`["testa","testb","testc"]`, []string{"testa", "testb", "testc"}},
    61  	{`["testa","testb","testc"]`, []interface{}{"testa", "testb", "testc"}},
    62  
    63  	// objects
    64  	{`{}`, map[string]interface{}{}},
    65  	{`{}`, map[string]int8{}},
    66  	{`{}`, map[string]int64{}},
    67  	{`{}`, map[string]struct{ A *int }{}},
    68  	{`{}`, mapstr{}},
    69  	{`{"a":null}`, map[string]interface{}{"a": nil}},
    70  	{`{"a":null}`, mapstr{"a": nil}},
    71  	{`{"a":null}`, struct{ A *int }{}},
    72  	{`{"a":null}`, struct{ A *struct{ B int } }{}},
    73  	{`{"a":true,"b":1,"c":"test"}`, map[string]interface{}{"a": true, "b": 1, "c": "test"}},
    74  	{`{"a":true,"b":1,"c":"test"}`, mapstr{"a": true, "b": 1, "c": "test"}},
    75  	{`{"a":true,"b":1,"c":"test"}`, struct {
    76  		A bool
    77  		B int
    78  		C string
    79  	}{true, 1, "test"}},
    80  
    81  	{`{"field":[1,2,3,4,5,6,7,8,9,10]}`,
    82  		map[string]interface{}{
    83  			"field": []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    84  		},
    85  	},
    86  	{`{"field":[1,2,3,4,5,6,7,8,9,10]}`,
    87  		map[string]interface{}{
    88  			"field": []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    89  		},
    90  	},
    91  	{`{"field":[1,2,3,4,5,6,7,8,9,10]}`,
    92  		mapstr{
    93  			"field": []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    94  		},
    95  	},
    96  	{`{"field":[1,2,3,4,5,6,7,8,9,10]}`,
    97  		map[string][]int{
    98  			"field": []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    99  		},
   100  	},
   101  	{`{"field":[1,2,3,4,5,6,7,8,9,10]}`,
   102  		struct {
   103  			Field []int
   104  		}{
   105  			Field: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   106  		},
   107  	},
   108  
   109  	// structs with inlines
   110  	{
   111  		`{"a": 1}`,
   112  		struct {
   113  			X interface{} `struct:",inline"`
   114  		}{X: map[string]int{"a": 1}},
   115  	},
   116  	{
   117  		`{"a": 1}`,
   118  		struct {
   119  			X interface{} `struct:",inline"`
   120  		}{X: struct{ A int }{1}},
   121  	},
   122  	{
   123  		`{"a": 1}`,
   124  		struct {
   125  			X interface{} `struct:",inline"`
   126  		}{X: &struct{ A int }{1}},
   127  	},
   128  	{
   129  		`{"a": 1}`,
   130  		struct {
   131  			X map[string]interface{} `struct:",inline"`
   132  		}{X: map[string]interface{}{"a": 1}},
   133  	},
   134  	{
   135  		`{"a": 1}`,
   136  		struct {
   137  			X map[string]int `struct:",inline"`
   138  		}{X: map[string]int{"a": 1}},
   139  	},
   140  	{
   141  		`{"a": 1}`,
   142  		struct {
   143  			X struct{ A int } `struct:",inline"`
   144  		}{X: struct{ A int }{1}},
   145  	},
   146  
   147  	// omit empty without values
   148  	{
   149  		`{"a": 1}`,
   150  		struct {
   151  			A int
   152  			B interface{} `struct:",omitempty"`
   153  		}{A: 1},
   154  	},
   155  	{
   156  		`{"a": 1}`,
   157  		struct {
   158  			A int
   159  			B map[string]interface{} `struct:",omitempty"`
   160  		}{A: 1},
   161  	},
   162  	{
   163  		`{"a": 1}`,
   164  		struct {
   165  			A int
   166  			B []int `struct:",omitempty"`
   167  		}{A: 1},
   168  	},
   169  	{
   170  		`{"a": 1}`,
   171  		struct {
   172  			A int
   173  			B string `struct:",omitempty"`
   174  		}{A: 1},
   175  	},
   176  	{
   177  		`{"a": 1}`,
   178  		struct {
   179  			A int
   180  			B *int `struct:",omitempty"`
   181  		}{A: 1},
   182  	},
   183  	{
   184  		`{"a": 1}`,
   185  		struct {
   186  			A int
   187  			B *struct{ C int } `struct:",omitempty"`
   188  		}{A: 1},
   189  	},
   190  
   191  	// omit empty with values
   192  	{
   193  		`{"a": 1, "b": 2}`,
   194  		struct {
   195  			A int
   196  			B interface{} `struct:",omitempty"`
   197  		}{A: 1, B: 2},
   198  	},
   199  	{
   200  		`{"a": 1, "b": {"c": 2}}`,
   201  		struct {
   202  			A int
   203  			B map[string]interface{} `struct:",omitempty"`
   204  		}{A: 1, B: map[string]interface{}{"c": 2}},
   205  	},
   206  	{
   207  		`{"a": 1, "b":[2]}`,
   208  		struct {
   209  			A int
   210  			B []int `struct:",omitempty"`
   211  		}{A: 1, B: []int{2}},
   212  	},
   213  	{
   214  		`{"a": 1, "b": "test"}`,
   215  		struct {
   216  			A int
   217  			B string `struct:",omitempty"`
   218  		}{A: 1, B: "test"},
   219  	},
   220  	{
   221  		`{"a": 1, "b": 0}`,
   222  		struct {
   223  			A int
   224  			B *int `struct:",omitempty"`
   225  		}{A: 1, B: new(int)},
   226  	},
   227  	{
   228  		`{"a": 1, "b": {"c": 2}}`,
   229  		struct {
   230  			A int
   231  			B *struct{ C int } `struct:",omitempty"`
   232  		}{A: 1, B: &struct{ C int }{2}},
   233  	},
   234  }
   235  
   236  func TestIter2JsonConsistent(t *testing.T) {
   237  	tests := foldSamples
   238  	for i, test := range tests {
   239  		t.Logf("run test (%v): %v (%T)", i, test.json, test.value)
   240  
   241  		var buf bytes.Buffer
   242  		iter, err := NewIterator(json.NewVisitor(&buf))
   243  		if err != nil {
   244  			panic(err)
   245  		}
   246  
   247  		err = iter.Fold(test.value)
   248  		if err != nil {
   249  			t.Error(err)
   250  			continue
   251  		}
   252  
   253  		// compare conversions did preserve type
   254  		assertJSON(t, test.json, buf.String())
   255  	}
   256  }
   257  
   258  func TestUserFold(t *testing.T) {
   259  	ts := time.Now()
   260  	tsStr := ts.String()
   261  	tsInt := ts.Unix()
   262  
   263  	foldTsString := func(t *time.Time, vs structform.ExtVisitor) error {
   264  		return vs.OnString(t.String())
   265  	}
   266  
   267  	foldTsInt := func(t *time.Time, vs structform.ExtVisitor) error {
   268  		return vs.OnInt64(t.Unix())
   269  	}
   270  
   271  	tests := []struct {
   272  		v        interface{}
   273  		folder   interface{}
   274  		expected sftest.Recording
   275  	}{
   276  		{ts, foldTsString, sftest.Recording{sftest.StringRec{tsStr}}},
   277  		{ts, foldTsInt, sftest.Recording{sftest.Int64Rec{tsInt}}},
   278  		{&ts, foldTsString, sftest.Recording{sftest.StringRec{tsStr}}},
   279  		{&ts, foldTsInt, sftest.Recording{sftest.Int64Rec{tsInt}}},
   280  		{map[string]interface{}{"ts": ts}, foldTsInt, sftest.Obj(1, structform.AnyType,
   281  			"ts", sftest.Int64Rec{tsInt},
   282  		)},
   283  		{map[string]interface{}{"ts": &ts}, foldTsInt, sftest.Obj(1, structform.AnyType,
   284  			"ts", sftest.Int64Rec{tsInt},
   285  		)},
   286  		{map[string]interface{}{"ts": ts}, foldTsString, sftest.Obj(1, structform.AnyType,
   287  			"ts", sftest.StringRec{tsStr},
   288  		)},
   289  		{map[string]interface{}{"ts": &ts}, foldTsString, sftest.Obj(1, structform.AnyType,
   290  			"ts", sftest.StringRec{tsStr},
   291  		)},
   292  	}
   293  
   294  	for i, test := range tests {
   295  		t.Logf("run test(%v): %#v -> %#v", i, test.v, test.expected)
   296  
   297  		var rec sftest.Recording
   298  		err := Fold(test.v, &rec, Folders(test.folder))
   299  		if err != nil {
   300  			t.Error(err)
   301  			continue
   302  		}
   303  
   304  		rec.Assert(t, test.expected)
   305  	}
   306  }
   307  
   308  func assertJSON(t *testing.T, expected, actual string) (err error) {
   309  	expected, err = normalizeJSON(expected)
   310  	if err != nil {
   311  		t.Error(err)
   312  		return
   313  	}
   314  
   315  	actual, err = normalizeJSON(actual)
   316  	if err != nil {
   317  		t.Error(err)
   318  		return
   319  	}
   320  
   321  	// compare conversions did preserve type
   322  	if !assert.Equal(t, expected, actual) {
   323  		return errors.New("match failure")
   324  	}
   325  	return nil
   326  }
   327  
   328  func normalizeJSON(in string) (string, error) {
   329  	var tmp interface{}
   330  	if err := gojson.Unmarshal([]byte(in), &tmp); err != nil {
   331  		return "", err
   332  	}
   333  
   334  	b, err := gojson.MarshalIndent(tmp, "", "  ")
   335  	if err != nil {
   336  		return "", err
   337  	}
   338  
   339  	return string(b), nil
   340  }