goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/util/typeutil/typeutil_test.go (about) 1 package typeutil 2 3 import ( 4 "testing" 5 6 "github.com/samber/lo" 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 ) 10 11 func TestConvert(t *testing.T) { 12 type Nested struct { 13 C uint `json:"c"` 14 } 15 16 type Promoted struct { 17 P string `json:"p"` 18 } 19 20 type TestStruct struct { 21 Promoted 22 A string `json:"a"` 23 D []string `json:"d"` 24 B float64 `json:"b"` 25 Nested Nested `json:"nested"` 26 } 27 28 cases := []struct { 29 value any 30 want any 31 wantErr bool 32 }{ 33 { 34 value: map[string]any{"p": "p", "a": "hello", "b": 0.3, "d": []string{"world"}, "c": 456, "nested": map[string]any{"c": 123}}, 35 want: &TestStruct{Promoted: Promoted{P: "p"}, A: "hello", B: 0.3, D: []string{"world"}, Nested: Nested{C: 123}}, 36 wantErr: false, 37 }, 38 {value: &TestStruct{A: "hello"}, want: &TestStruct{A: "hello"}, wantErr: false}, 39 {value: struct{}{}, want: &TestStruct{}, wantErr: false}, 40 {value: "string", want: &TestStruct{}, wantErr: true}, 41 {value: 'a', want: &TestStruct{}, wantErr: true}, 42 {value: 2, want: &TestStruct{}, wantErr: true}, 43 {value: 2.5, want: &TestStruct{}, wantErr: true}, 44 {value: []string{"string"}, want: &TestStruct{}, wantErr: true}, 45 {value: map[string]any{"a": 1}, want: &TestStruct{}, wantErr: true}, 46 {value: true, want: &TestStruct{}, wantErr: true}, 47 {value: nil, want: (*TestStruct)(nil), wantErr: false}, 48 } 49 50 for _, c := range cases { 51 c := c 52 t.Run("TestStruct", func(t *testing.T) { 53 res, err := Convert[*TestStruct](c.value) 54 assert.Equal(t, c.want, res) 55 if c.wantErr { 56 require.Error(t, err) 57 } else { 58 require.NoError(t, err) 59 } 60 61 assert.Equal(t, c.want, res) 62 }) 63 } 64 65 t.Run("string", func(t *testing.T) { 66 res, err := Convert[string]("hello") 67 assert.Equal(t, "hello", res) 68 require.NoError(t, err) 69 }) 70 t.Run("int", func(t *testing.T) { 71 res, err := Convert[int](123) 72 assert.Equal(t, 123, res) 73 require.NoError(t, err) 74 }) 75 t.Run("float", func(t *testing.T) { 76 res, err := Convert[float64](0.3) 77 assert.InEpsilon(t, 0.3, res, 0) 78 require.NoError(t, err) 79 }) 80 t.Run("bool", func(t *testing.T) { 81 res, err := Convert[bool](true) 82 assert.True(t, res) 83 require.NoError(t, err) 84 }) 85 t.Run("mismatching types", func(t *testing.T) { 86 res, err := Convert[bool]("true") 87 assert.False(t, res) 88 require.Error(t, err) 89 }) 90 t.Run("[]string", func(t *testing.T) { 91 res, err := Convert[[]string]([]string{"a", "b", "c"}) 92 assert.Equal(t, []string{"a", "b", "c"}, res) 93 require.NoError(t, err) 94 }) 95 t.Run("[]any", func(t *testing.T) { 96 res, err := Convert[[]any]([]string{"a", "4", "c"}) 97 assert.Equal(t, []any{"a", "4", "c"}, res) 98 require.NoError(t, err) 99 100 res, err = Convert[[]any]([]any{"a", 4, 4.0, true, []any{"a", "b"}}) 101 assert.Equal(t, []any{"a", 4, 4.0, true, []any{"a", "b"}}, res) 102 require.NoError(t, err) 103 }) 104 } 105 106 func TestMustConvert(t *testing.T) { 107 assert.InEpsilon(t, 0.3, MustConvert[float64](0.3), 0) 108 109 assert.Panics(t, func() { 110 MustConvert[float64]("0.3") 111 }) 112 } 113 114 func TestCopy(t *testing.T) { 115 type Nested struct { 116 C uint `json:"c"` 117 } 118 119 type Promoted struct { 120 P string `json:"p"` 121 } 122 123 type TestStruct struct { 124 Promoted 125 Undefined Undefined[string] `json:"undefined"` 126 UndefinedPtr Undefined[*string] `json:"undefinedPtr"` 127 A string `json:"a"` 128 Ptr *string `json:"ptr"` 129 D []string `json:"d"` 130 B float64 `json:"b"` 131 Scanner Undefined[testInt64] `json:"scanner"` 132 Nested Nested `json:"nested"` 133 } 134 135 cases := []struct { 136 model *TestStruct 137 dto any 138 want *TestStruct 139 desc string 140 wantPanic bool 141 }{ 142 { 143 desc: "base", 144 model: &TestStruct{ 145 A: "test", 146 D: []string{"test1", "test2"}, 147 B: 1, 148 }, 149 dto: struct { 150 A string 151 D []string 152 }{A: "override", D: []string{"override1", "override2"}}, 153 want: &TestStruct{ 154 A: "override", 155 D: []string{"override1", "override2"}, 156 B: 1, 157 }, 158 }, 159 { 160 desc: "base_at_zero", 161 model: &TestStruct{}, 162 dto: struct { 163 B float64 164 }{B: 1.234}, 165 want: &TestStruct{ 166 B: 1.234, 167 }, 168 }, 169 { 170 desc: "promoted", 171 model: &TestStruct{ 172 A: "test", 173 Promoted: Promoted{ 174 P: "promoted", 175 }, 176 }, 177 dto: struct { 178 A string 179 P string 180 }{A: "override", P: "promoted override"}, 181 want: &TestStruct{ 182 A: "override", 183 Promoted: Promoted{ 184 P: "promoted override", 185 }, 186 }, 187 }, 188 { 189 desc: "promoted_dto", 190 model: &TestStruct{ 191 A: "test", 192 Promoted: Promoted{ 193 P: "promoted", 194 }, 195 }, 196 dto: struct { 197 A string 198 Promoted struct { 199 P string 200 } 201 }{A: "override", Promoted: struct { 202 P string 203 }{ 204 P: "promoted override", 205 }}, 206 want: &TestStruct{ 207 A: "override", 208 Promoted: Promoted{ 209 P: "promoted override", 210 }, 211 }, 212 }, 213 { 214 desc: "ignore_empty", 215 model: &TestStruct{ 216 A: "test", 217 D: []string{"test1", "test2"}, 218 B: 0, 219 }, 220 dto: struct { 221 Ptr Undefined[*string] 222 A string 223 D []string 224 B float64 225 }{A: "", B: 0, D: nil, Ptr: Undefined[*string]{}}, 226 want: &TestStruct{ 227 A: "test", 228 D: []string{"test1", "test2"}, 229 B: 0, 230 }, 231 }, 232 { 233 desc: "deep", 234 model: &TestStruct{ 235 Nested: Nested{ 236 C: 2, 237 }, 238 }, 239 dto: struct { 240 C uint 241 Nested struct { 242 C uint 243 } 244 }{C: 3, Nested: struct{ C uint }{C: 4}}, 245 want: &TestStruct{ 246 Nested: Nested{ 247 C: 4, 248 }, 249 }, 250 }, 251 { 252 desc: "undefined_field_zero_value", 253 model: &TestStruct{ 254 B: 1, 255 }, 256 dto: struct{ B Undefined[float64] }{B: NewUndefined(0.0)}, 257 want: &TestStruct{ 258 B: 0, 259 }, 260 }, 261 { 262 desc: "undefined_field", 263 model: &TestStruct{ 264 B: 1, 265 }, 266 dto: struct{ B Undefined[float64] }{B: NewUndefined(1.234)}, 267 want: &TestStruct{ 268 B: 1.234, 269 }, 270 }, 271 { 272 desc: "undefined_slice", 273 model: &TestStruct{}, 274 dto: struct{ D Undefined[[]string] }{D: NewUndefined([]string{"a", "b", "c"})}, 275 want: &TestStruct{ 276 D: []string{"a", "b", "c"}, 277 }, 278 }, 279 { 280 desc: "undefined_struct", 281 model: &TestStruct{}, 282 dto: struct{ Nested Undefined[struct{ C uint }] }{Nested: NewUndefined(struct{ C uint }{C: 4})}, 283 want: &TestStruct{ 284 Nested: Nested{ 285 C: 4, 286 }, 287 }, 288 }, 289 { 290 desc: "undefined_nil", 291 model: &TestStruct{ 292 A: "not nil", 293 Ptr: lo.ToPtr("not nil"), 294 }, 295 dto: struct { 296 A Undefined[*string] 297 Ptr Undefined[*string] 298 }{ 299 A: NewUndefined[*string](nil), 300 Ptr: NewUndefined[*string](nil), 301 }, 302 want: &TestStruct{ 303 A: "not nil", 304 Ptr: nil, 305 }, 306 }, 307 { 308 desc: "undefined_to_undefined", 309 model: &TestStruct{ 310 Undefined: NewUndefined("value"), 311 }, 312 dto: struct { 313 Undefined Undefined[string] 314 }{ 315 Undefined: NewUndefined("override"), 316 }, 317 want: &TestStruct{ 318 Undefined: NewUndefined("override"), 319 }, 320 }, 321 { 322 desc: "undefined_ptr_to_undefined", 323 model: &TestStruct{ 324 Undefined: NewUndefined("value"), 325 }, 326 dto: struct { 327 Undefined Undefined[*string] 328 }{ 329 Undefined: NewUndefined(lo.ToPtr("override")), 330 }, 331 want: &TestStruct{ 332 Undefined: NewUndefined("override"), 333 }, 334 }, 335 { 336 desc: "ptr_to_undefined", 337 model: &TestStruct{ 338 Undefined: NewUndefined("value"), 339 }, 340 dto: struct { 341 Undefined *string 342 }{ 343 Undefined: lo.ToPtr("override"), 344 }, 345 want: &TestStruct{ 346 Undefined: NewUndefined("override"), 347 }, 348 }, 349 { 350 desc: "undefined_ptr_to_undefined", 351 model: &TestStruct{ 352 Undefined: NewUndefined("value"), 353 }, 354 dto: struct { 355 Undefined Undefined[*string] 356 }{ 357 Undefined: NewUndefined(lo.ToPtr("override")), 358 }, 359 want: &TestStruct{ 360 Undefined: NewUndefined("override"), 361 }, 362 }, 363 { 364 desc: "undefined_to_undefined_incompatible_types", 365 model: &TestStruct{ 366 Undefined: NewUndefined("value"), 367 }, 368 dto: struct { 369 Undefined Undefined[int] 370 }{ 371 Undefined: NewUndefined(123), 372 }, 373 want: &TestStruct{ 374 Undefined: NewUndefined("value"), // The value has not been overridden because of incompatible types 375 }, 376 }, 377 { 378 desc: "scanner_undefined", 379 model: &TestStruct{ 380 Scanner: NewUndefined(testInt64{Val: 123}), 381 }, 382 dto: struct { 383 Scanner int64 384 }{ 385 Scanner: 456, 386 }, 387 want: &TestStruct{ 388 Scanner: NewUndefined(testInt64{Val: 456}), 389 }, 390 }, 391 { 392 desc: "undefined_scanner_incompatible", 393 model: &TestStruct{ 394 Scanner: NewUndefined(testInt64{Val: 123}), 395 }, 396 dto: struct { 397 Scanner string 398 }{ 399 Scanner: "456", 400 }, 401 want: &TestStruct{ 402 Scanner: NewUndefined(testInt64{Val: 123}), 403 }, 404 }, 405 } 406 407 for _, c := range cases { 408 c := c 409 t.Run(c.desc, func(t *testing.T) { 410 if c.wantPanic { 411 assert.Panics(t, func() { 412 Copy(c.model, c.dto) 413 }) 414 return 415 } 416 res := Copy(c.model, c.dto) 417 assert.Equal(t, c.want, res) 418 }) 419 } 420 }