1package funk 2 3import ( 4 "database/sql" 5 "fmt" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9) 10 11func TestSet_EmptyPath(t *testing.T) { 12 // it is supposed to change the var passed in 13 var testCases = []struct { 14 // will use path = "" 15 Original interface{} 16 SetVal interface{} 17 }{ 18 // int 19 { 20 Original: 100, 21 SetVal: 1, 22 }, 23 // string 24 { 25 Original: "", 26 SetVal: "val", 27 }, 28 // struct 29 { 30 Original: Bar{Name: "bar"}, 31 SetVal: Bar{Name: "val"}, 32 }, 33 // slice 34 { 35 Original: []Bar{{Name: "bar"}}, 36 SetVal: []Bar{{Name: "val1"}, {Name: "val2"}}, 37 }, 38 } 39 40 for idx, tc := range testCases { 41 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 42 is := assert.New(t) 43 // use empty path 44 // must take the addr of the variable to be set 45 err := Set(&tc.Original, tc.SetVal, "") 46 is.NoError(err) 47 is.Equal(tc.Original, tc.SetVal) // original should be set to SetVal 48 }) 49 } 50} 51 52func TestSet_StructBasicOneLevel(t *testing.T) { 53 is := assert.New(t) 54 // we set field one by one of baz with expected value 55 baz := Foo{ 56 ID: 100, 57 FirstName: "firstname", 58 LastName: "lastname", 59 Age: 23, 60 Bar: &Bar{Name: "bar"}, 61 Bars: []*Bar{{Name: "1"}}, 62 EmptyValue: sql.NullInt64{ 63 Int64: 64, 64 Valid: false, 65 }, 66 } 67 expected := Foo{ 68 ID: 1, 69 FirstName: "firstname1", 70 LastName: "lastname1", 71 Age: 24, 72 Bar: &Bar{Name: "b1", Bar: &Bar{Name: "b2"}}, 73 Bars: []*Bar{{Name: "1"}, {Name: "2"}}, 74 EmptyValue: sql.NullInt64{ 75 Int64: 11, 76 Valid: true, 77 }, 78 } 79 err := Set(&baz, 1, "ID") 80 is.NoError(err) 81 err = Set(&baz, expected.FirstName, "FirstName") 82 is.NoError(err) 83 err = Set(&baz, expected.LastName, "LastName") 84 is.NoError(err) 85 err = Set(&baz, expected.Age, "Age") 86 is.NoError(err) 87 err = Set(&baz, expected.Bar, "Bar") 88 is.NoError(err) 89 err = Set(&baz, expected.Bars, "Bars") 90 is.NoError(err) 91 err = Set(&baz, expected.EmptyValue, "EmptyValue") 92 is.NoError(err) 93 is.Equal(baz, expected) 94} 95 96func TestSetStruct_MultiLevels(t *testing.T) { 97 98 var testCases = []struct { 99 Original Bar 100 Path string 101 SetVal interface{} 102 Expected Bar 103 }{ 104 // Set slice in 4th level 105 { 106 Original: Bar{ 107 Name: "1", // name indicates level 108 Bar: &Bar{ 109 Name: "2", 110 Bars: []*Bar{ 111 {Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}}, 112 {Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}}, 113 }, 114 }, 115 }, 116 Path: "Bar.Bars.Bars.Name", 117 SetVal: "val", 118 Expected: Bar{ 119 Name: "1", 120 Bar: &Bar{ 121 Name: "2", 122 Bars: []*Bar{ 123 {Name: "3-1", Bars: []*Bar{{Name: "val"}, {Name: "val"}, {Name: "val"}}}, 124 {Name: "3-2", Bars: []*Bar{{Name: "val"}, {Name: "val"}}}, 125 }, 126 }, 127 }, 128 }, 129 // Set multilevel uninitialized ptr 130 { 131 Original: Bar{ 132 Name: "1", // name indicates level 133 Bar: nil, 134 }, 135 Path: "Bar.Bar.Bar.Name", 136 SetVal: "val", 137 Expected: Bar{ 138 Name: "1", 139 Bar: &Bar{ 140 Name: "", // level 2 141 Bar: &Bar{ 142 Bar: &Bar{ 143 Name: "val", //level 3 144 }, 145 }, 146 }, 147 }, 148 }, 149 // mix of uninitialized ptr and slices 150 { 151 Original: Bar{ 152 Name: "1", // name indicates level 153 Bar: &Bar{ 154 Name: "2", 155 Bars: []*Bar{ 156 {Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}}, 157 {Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}}, 158 }, 159 }, 160 }, 161 Path: "Bar.Bars.Bars.Bar.Name", 162 SetVal: "val", 163 Expected: Bar{ 164 Name: "1", // name indicates level 165 Bar: &Bar{ 166 Name: "2", 167 Bars: []*Bar{ 168 {Name: "3-1", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}}, 169 {Name: "4-2", Bar: &Bar{Name: "val"}}, {Name: "4-3", Bar: &Bar{Name: "val"}}}}, 170 {Name: "3-2", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}}, {Name: "4-2", Bar: &Bar{Name: "val"}}}}, 171 }, 172 }, 173 }, 174 }, 175 } 176 177 for idx, tc := range testCases { 178 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 179 is := assert.New(t) 180 // take the addr and then pass it in 181 err := Set(&tc.Original, tc.SetVal, tc.Path) 182 is.NoError(err) 183 is.Equal(tc.Expected, tc.Original) 184 }) 185 } 186} 187 188func TestSet_StructWithCyclicStruct(t *testing.T) { 189 is := assert.New(t) 190 191 testBar := Bar{ 192 Name: "testBar", 193 Bar: nil, 194 } 195 testBar.Bar = &testBar 196 197 err := Set(&testBar, "val", "Bar.Bar.Name") 198 is.NoError(err) 199 is.Equal("val", testBar.Name) 200} 201 202func TestSet_StructWithFieldNotInitialized(t *testing.T) { 203 is := assert.New(t) 204 myFoo := &Foo{ 205 Bar: nil, // we will try to set bar's field 206 } 207 err := Set(myFoo, "name", "Bar.Name") 208 is.NoError(err) 209 is.Equal("name", myFoo.Bar.Name) 210} 211 212func TestSet_SlicePassByPtr(t *testing.T) { 213 214 var testCases = []struct { 215 Original interface{} // slice or array 216 Path string 217 SetVal interface{} 218 Expected interface{} 219 }{ 220 // Set Slice itself 221 { 222 Original: []*Bar{}, 223 Path: "", // empty path means set the passed in ptr itself 224 SetVal: []*Bar{{Name: "bar"}}, 225 Expected: []*Bar{{Name: "bar"}}, 226 }, 227 // empty slice 228 { 229 Original: []*Bar{}, 230 Path: "Name", 231 SetVal: "val", 232 Expected: []*Bar{}, 233 }, 234 // slice of ptr 235 { 236 Original: []*Bar{{Name: "a"}, {Name: "b"}}, 237 Path: "Name", 238 SetVal: "val", 239 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 240 }, 241 // slice of struct 242 { 243 Original: []Bar{{Name: "a"}, {Name: "b"}}, 244 Path: "Name", 245 SetVal: "val", 246 Expected: []Bar{{Name: "val"}, {Name: "val"}}, 247 }, 248 // slice of empty ptr 249 { 250 Original: []*Bar{nil, nil}, 251 Path: "Name", 252 SetVal: "val", 253 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 254 }, 255 // mix of init ptr and nil ptr 256 { 257 Original: []*Bar{{Name: "bar"}, nil}, 258 Path: "Name", 259 SetVal: "val", 260 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 261 }, 262 } 263 264 for idx, tc := range testCases { 265 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 266 is := assert.New(t) 267 // take the addr and then pass it in 268 err := Set(&tc.Original, tc.SetVal, tc.Path) 269 is.NoError(err) 270 is.Equal(tc.Expected, tc.Original) 271 }) 272 } 273} 274 275func TestSet_SlicePassDirectly(t *testing.T) { 276 var testCases = []struct { 277 Original interface{} // slice or array 278 Path string 279 SetVal interface{} 280 Expected interface{} 281 }{ 282 // Set Slice itself does not work here since not passing by ptr 283 284 // empty slice 285 { 286 Original: []*Bar{}, 287 Path: "Name", 288 SetVal: "val", 289 Expected: []*Bar{}, 290 }, 291 // slice of ptr 292 { 293 Original: []*Bar{{Name: "a"}, {Name: "b"}}, 294 Path: "Name", 295 SetVal: "val", 296 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 297 }, 298 // slice of struct 299 { 300 Original: []Bar{{Name: "a"}, {Name: "b"}}, 301 Path: "Name", 302 SetVal: "val", 303 Expected: []Bar{{Name: "val"}, {Name: "val"}}, 304 }, 305 // slice of empty ptr 306 { 307 Original: []*Bar{nil, nil}, 308 Path: "Name", 309 SetVal: "val", 310 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 311 }, 312 // mix of init ptr and nil ptr 313 { 314 Original: []*Bar{{Name: "bar"}, nil}, 315 Path: "Name", 316 SetVal: "val", 317 Expected: []*Bar{{Name: "val"}, {Name: "val"}}, 318 }, 319 } 320 321 for idx, tc := range testCases { 322 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 323 is := assert.New(t) 324 // Not take ptr, pass directly 325 err := Set(tc.Original, tc.SetVal, tc.Path) 326 is.NoError(err) 327 is.Equal(tc.Expected, tc.Original) 328 }) 329 } 330} 331 332func TestInterface(t *testing.T) { 333 334 var testCases = []struct { 335 OriginalFoo Foo 336 Path string 337 SetVal interface{} 338 ExpectedFoo Foo 339 }{ 340 // set string field 341 { 342 Foo{FirstName: ""}, 343 "FirstName", 344 "hi", 345 Foo{FirstName: "hi"}, 346 }, 347 // set interface{} field 348 { 349 Foo{FirstName: "", GeneralInterface: nil}, 350 "GeneralInterface", 351 "str", 352 Foo{FirstName: "", GeneralInterface: "str"}, 353 }, 354 // set field of the interface{} field 355 // Note: set uninitialized interface{} should fail 356 // Note: interface of struct (not ptr to struct) should fail 357 { 358 Foo{FirstName: "", GeneralInterface: &Foo{FirstName: ""}}, // if Foo is not ptr this will fail 359 "GeneralInterface.FirstName", 360 "foo", 361 Foo{FirstName: "", GeneralInterface: &Foo{FirstName: "foo"}}, 362 }, 363 // interface two level 364 { 365 Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: nil}}, 366 "GeneralInterface.GeneralInterface", 367 "val", 368 Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: "val"}}, 369 }, 370 } 371 372 for idx, tc := range testCases { 373 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 374 is := assert.New(t) 375 376 err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path) 377 is.NoError(err) 378 is.Equal(tc.ExpectedFoo, tc.OriginalFoo) 379 }) 380 } 381 382} 383 384func TestSet_ErrorCaces(t *testing.T) { 385 386 var testCases = []struct { 387 OriginalFoo Foo 388 Path string 389 SetVal interface{} 390 }{ 391 // uninit interface 392 // Itf is not initialized so Set cannot properly allocate type 393 { 394 Foo{BarInterface: nil}, 395 "BarInterface.Name", 396 "val", 397 }, 398 { 399 Foo{GeneralInterface: &Foo{BarInterface: nil}}, 400 "GeneralInterface.BarInterface.Name", 401 "val", 402 }, 403 // type mismatch 404 { 405 Foo{FirstName: ""}, 406 "FirstName", 407 20, 408 }, 409 } 410 411 for idx, tc := range testCases { 412 t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) { 413 is := assert.New(t) 414 415 err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path) 416 is.Error(err) 417 }) 418 } 419 420 t.Run("not pointer", func(t *testing.T) { 421 is := assert.New(t) 422 baz := Bar{Name: "dummy"} 423 err := Set(baz, Bar{Name: "dummy2"}, "Name") 424 is.Error(err) 425 }) 426 427 t.Run("Unexported field", func(t *testing.T) { 428 is := assert.New(t) 429 s := struct { 430 name string 431 }{name: "dummy"} 432 err := Set(&s, s, "name") 433 is.Error(err) 434 }) 435} 436 437func TestMustSet_Basic(t *testing.T) { 438 t.Run("Variable", func(t *testing.T) { 439 is := assert.New(t) 440 s := 1 441 MustSet(&s, 2, "") 442 is.Equal(2, s) 443 }) 444 445 t.Run("Struct", func(t *testing.T) { 446 is := assert.New(t) 447 s := Bar{Name: "a"} 448 MustSet(&s, "b", "Name") 449 is.Equal("b", s.Name) 450 }) 451} 452 453// Examples 454 455func ExampleSet() { 456 457 var bar Bar = Bar{ 458 Name: "level-0", 459 Bar: &Bar{ 460 Name: "level-1", 461 Bars: []*Bar{ 462 {Name: "level2-1"}, 463 {Name: "level2-2"}, 464 }, 465 }, 466 } 467 468 _ = Set(&bar, "level-0-new", "Name") 469 fmt.Println(bar.Name) 470 471 // discard error use MustSet 472 MustSet(&bar, "level-1-new", "Bar.Name") 473 fmt.Println(bar.Bar.Name) 474 475 _ = Set(&bar, "level-2-new", "Bar.Bars.Name") 476 fmt.Println(bar.Bar.Bars[0].Name) 477 fmt.Println(bar.Bar.Bars[1].Name) 478 479 // Output: 480 // level-0-new 481 // level-1-new 482 // level-2-new 483 // level-2-new 484} 485