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 }