github.com/hamba/avro/v2@v2.22.1-0.20240518180522-aff3955acf7d/schema_test.go (about) 1 package avro_test 2 3 import ( 4 "encoding/json" 5 "testing" 6 7 "github.com/hamba/avro/v2" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 ) 11 12 func TestParse_InvalidType(t *testing.T) { 13 schemas := []string{ 14 `123`, 15 `{"type": 123}`, 16 } 17 18 for _, schm := range schemas { 19 _, err := avro.Parse(schm) 20 21 assert.Error(t, err) 22 } 23 } 24 25 func TestMustParse(t *testing.T) { 26 s := avro.MustParse("null") 27 28 assert.Equal(t, avro.Null, s.Type()) 29 } 30 31 func TestMustParse_PanicsOnError(t *testing.T) { 32 assert.Panics(t, func() { 33 avro.MustParse("123") 34 }) 35 } 36 37 func TestParseFiles(t *testing.T) { 38 s, err := avro.ParseFiles("testdata/schema.avsc") 39 40 require.NoError(t, err) 41 assert.Equal(t, avro.String, s.Type()) 42 } 43 44 func TestParseFiles_FileDoesntExist(t *testing.T) { 45 _, err := avro.ParseFiles("test.something") 46 47 assert.Error(t, err) 48 } 49 50 func TestParseFiles_InvalidSchema(t *testing.T) { 51 _, err := avro.ParseFiles("testdata/bad-schema.avsc") 52 53 assert.Error(t, err) 54 } 55 56 func TestNullSchema(t *testing.T) { 57 schemas := []string{ 58 `null`, 59 `{"type":"null"}`, 60 } 61 62 for _, schm := range schemas { 63 schema, err := avro.Parse(schm) 64 65 require.NoError(t, err) 66 assert.Equal(t, avro.Null, schema.Type()) 67 want := [32]byte{0xf0, 0x72, 0xcb, 0xec, 0x3b, 0xf8, 0x84, 0x18, 0x71, 0xd4, 0x28, 0x42, 0x30, 0xc5, 0xe9, 0x83, 0xdc, 0x21, 0x1a, 0x56, 0x83, 0x7a, 0xed, 0x86, 0x24, 0x87, 0x14, 0x8f, 0x94, 0x7d, 0x1a, 0x1f} 68 assert.Equal(t, want, schema.Fingerprint()) 69 } 70 } 71 72 func TestPrimitiveSchema(t *testing.T) { 73 tests := []struct { 74 schema string 75 want avro.Type 76 wantFingerprint [32]byte 77 }{ 78 { 79 schema: "string", 80 want: avro.String, 81 wantFingerprint: [32]byte{0xe9, 0xe5, 0xc1, 0xc9, 0xe4, 0xf6, 0x27, 0x73, 0x39, 0xd1, 0xbc, 0xde, 0x7, 0x33, 0xa5, 0x9b, 0xd4, 0x2f, 0x87, 0x31, 0xf4, 0x49, 0xda, 0x6d, 0xc1, 0x30, 0x10, 0xa9, 0x16, 0x93, 0xd, 0x48}, 82 }, 83 { 84 schema: `{"type":"string"}`, 85 want: avro.String, 86 wantFingerprint: [32]byte{0xe9, 0xe5, 0xc1, 0xc9, 0xe4, 0xf6, 0x27, 0x73, 0x39, 0xd1, 0xbc, 0xde, 0x7, 0x33, 0xa5, 0x9b, 0xd4, 0x2f, 0x87, 0x31, 0xf4, 0x49, 0xda, 0x6d, 0xc1, 0x30, 0x10, 0xa9, 0x16, 0x93, 0xd, 0x48}, 87 }, 88 { 89 schema: "bytes", 90 want: avro.Bytes, 91 wantFingerprint: [32]byte{0x9a, 0xe5, 0x7, 0xa9, 0xdd, 0x39, 0xee, 0x5b, 0x7c, 0x7e, 0x28, 0x5d, 0xa2, 0xc0, 0x84, 0x65, 0x21, 0xc8, 0xae, 0x8d, 0x80, 0xfe, 0xea, 0xe5, 0x50, 0x4e, 0xc, 0x98, 0x1d, 0x53, 0xf5, 0xfa}, 92 }, 93 { 94 schema: `{"type":"bytes"}`, 95 want: avro.Bytes, 96 wantFingerprint: [32]byte{0x9a, 0xe5, 0x7, 0xa9, 0xdd, 0x39, 0xee, 0x5b, 0x7c, 0x7e, 0x28, 0x5d, 0xa2, 0xc0, 0x84, 0x65, 0x21, 0xc8, 0xae, 0x8d, 0x80, 0xfe, 0xea, 0xe5, 0x50, 0x4e, 0xc, 0x98, 0x1d, 0x53, 0xf5, 0xfa}, 97 }, 98 { 99 schema: "int", 100 want: avro.Int, 101 wantFingerprint: [32]byte{0x3f, 0x2b, 0x87, 0xa9, 0xfe, 0x7c, 0xc9, 0xb1, 0x38, 0x35, 0x59, 0x8c, 0x39, 0x81, 0xcd, 0x45, 0xe3, 0xe3, 0x55, 0x30, 0x9e, 0x50, 0x90, 0xaa, 0x9, 0x33, 0xd7, 0xbe, 0xcb, 0x6f, 0xba, 0x45}, 102 }, 103 { 104 schema: `{"type":"int"}`, 105 want: avro.Int, 106 wantFingerprint: [32]byte{0x3f, 0x2b, 0x87, 0xa9, 0xfe, 0x7c, 0xc9, 0xb1, 0x38, 0x35, 0x59, 0x8c, 0x39, 0x81, 0xcd, 0x45, 0xe3, 0xe3, 0x55, 0x30, 0x9e, 0x50, 0x90, 0xaa, 0x9, 0x33, 0xd7, 0xbe, 0xcb, 0x6f, 0xba, 0x45}, 107 }, 108 { 109 schema: "long", 110 want: avro.Long, 111 wantFingerprint: [32]byte{0xc3, 0x2c, 0x49, 0x7d, 0xf6, 0x73, 0xc, 0x97, 0xfa, 0x7, 0x36, 0x2a, 0xa5, 0x2, 0x3f, 0x37, 0xd4, 0x9a, 0x2, 0x7e, 0xc4, 0x52, 0x36, 0x7, 0x78, 0x11, 0x4c, 0xf4, 0x27, 0x96, 0x5a, 0xdd}, 112 }, 113 { 114 schema: `{"type":"long"}`, 115 want: avro.Long, 116 wantFingerprint: [32]byte{0xc3, 0x2c, 0x49, 0x7d, 0xf6, 0x73, 0xc, 0x97, 0xfa, 0x7, 0x36, 0x2a, 0xa5, 0x2, 0x3f, 0x37, 0xd4, 0x9a, 0x2, 0x7e, 0xc4, 0x52, 0x36, 0x7, 0x78, 0x11, 0x4c, 0xf4, 0x27, 0x96, 0x5a, 0xdd}, 117 }, 118 { 119 schema: "float", 120 want: avro.Float, 121 wantFingerprint: [32]byte{0x1e, 0x71, 0xf9, 0xec, 0x5, 0x1d, 0x66, 0x3f, 0x56, 0xb0, 0xd8, 0xe1, 0xfc, 0x84, 0xd7, 0x1a, 0xa5, 0x6c, 0xcf, 0xe9, 0xfa, 0x93, 0xaa, 0x20, 0xd1, 0x5, 0x47, 0xa7, 0xab, 0xeb, 0x5c, 0xc0}, 122 }, 123 { 124 schema: `{"type":"float"}`, 125 want: avro.Float, 126 wantFingerprint: [32]byte{0x1e, 0x71, 0xf9, 0xec, 0x5, 0x1d, 0x66, 0x3f, 0x56, 0xb0, 0xd8, 0xe1, 0xfc, 0x84, 0xd7, 0x1a, 0xa5, 0x6c, 0xcf, 0xe9, 0xfa, 0x93, 0xaa, 0x20, 0xd1, 0x5, 0x47, 0xa7, 0xab, 0xeb, 0x5c, 0xc0}, 127 }, 128 { 129 schema: "double", 130 want: avro.Double, 131 wantFingerprint: [32]byte{0x73, 0xa, 0x9a, 0x8c, 0x61, 0x16, 0x81, 0xd7, 0xee, 0xf4, 0x42, 0xe0, 0x3c, 0x16, 0xc7, 0xd, 0x13, 0xbc, 0xa3, 0xeb, 0x8b, 0x97, 0x7b, 0xb4, 0x3, 0xea, 0xff, 0x52, 0x17, 0x6a, 0xf2, 0x54}, 132 }, 133 { 134 schema: `{"type":"double"}`, 135 want: avro.Double, 136 wantFingerprint: [32]byte{0x73, 0xa, 0x9a, 0x8c, 0x61, 0x16, 0x81, 0xd7, 0xee, 0xf4, 0x42, 0xe0, 0x3c, 0x16, 0xc7, 0xd, 0x13, 0xbc, 0xa3, 0xeb, 0x8b, 0x97, 0x7b, 0xb4, 0x3, 0xea, 0xff, 0x52, 0x17, 0x6a, 0xf2, 0x54}, 137 }, 138 { 139 schema: "boolean", 140 want: avro.Boolean, 141 wantFingerprint: [32]byte{0xa5, 0xb0, 0x31, 0xab, 0x62, 0xbc, 0x41, 0x6d, 0x72, 0xc, 0x4, 0x10, 0xd8, 0x2, 0xea, 0x46, 0xb9, 0x10, 0xc4, 0xfb, 0xe8, 0x5c, 0x50, 0xa9, 0x46, 0xcc, 0xc6, 0x58, 0xb7, 0x4e, 0x67, 0x7e}, 142 }, 143 { 144 schema: `{"type":"boolean"}`, 145 want: avro.Boolean, 146 wantFingerprint: [32]byte{0xa5, 0xb0, 0x31, 0xab, 0x62, 0xbc, 0x41, 0x6d, 0x72, 0xc, 0x4, 0x10, 0xd8, 0x2, 0xea, 0x46, 0xb9, 0x10, 0xc4, 0xfb, 0xe8, 0x5c, 0x50, 0xa9, 0x46, 0xcc, 0xc6, 0x58, 0xb7, 0x4e, 0x67, 0x7e}, 147 }, 148 } 149 150 for _, test := range tests { 151 test := test 152 t.Run(test.schema, func(t *testing.T) { 153 t.Parallel() 154 155 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 156 157 require.NoError(t, err) 158 assert.Equal(t, test.want, schema.Type()) 159 assert.Equal(t, test.wantFingerprint, schema.Fingerprint()) 160 }) 161 } 162 } 163 164 func TestPrimitiveSchema_HandlesProps(t *testing.T) { 165 schm := ` 166 { 167 "type": "string", 168 "foo": "bar", 169 "baz": 1 170 } 171 ` 172 173 s, err := avro.Parse(schm) 174 175 assert.NoError(t, err) 176 assert.Equal(t, avro.String, s.Type()) 177 assert.Equal(t, "bar", s.(*avro.PrimitiveSchema).Prop("foo")) 178 assert.Equal(t, float64(1), s.(*avro.PrimitiveSchema).Prop("baz")) 179 } 180 181 func TestRecordSchema(t *testing.T) { 182 tests := []struct { 183 name string 184 schema string 185 wantErr require.ErrorAssertionFunc 186 }{ 187 { 188 name: "Valid", 189 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "doc": "docs", "fields":[{"name": "field", "type": "int"}]}`, 190 wantErr: require.NoError, 191 }, 192 { 193 name: "Invalid Name First Char", 194 schema: `{"type":"record", "name":"0test", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 195 wantErr: require.Error, 196 }, 197 { 198 name: "Invalid Name Other Char", 199 schema: `{"type":"record", "name":"test+", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 200 wantErr: require.Error, 201 }, 202 { 203 name: "Empty Name", 204 schema: `{"type":"record", "name":"", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 205 wantErr: require.Error, 206 }, 207 { 208 name: "No Name", 209 schema: `{"type":"record", "namespace": "org.hamba.avro", "fields":[{"name": "intField", "type": "int"}]}`, 210 wantErr: require.Error, 211 }, 212 { 213 name: "Invalid Namespace", 214 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro+", "fields":[{"name": "field", "type": "int"}]}`, 215 wantErr: require.Error, 216 }, 217 { 218 name: "Empty Namespace", 219 schema: `{"type":"record", "name":"test", "namespace": "", "fields":[{"name": "intField", "type": "int"}]}`, 220 wantErr: require.Error, 221 }, 222 { 223 name: "No Fields", 224 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro"}`, 225 wantErr: require.Error, 226 }, 227 { 228 name: "Invalid Field Type", 229 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":["test"]}`, 230 wantErr: require.Error, 231 }, 232 { 233 name: "No Field Name", 234 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"type": "int"}]}`, 235 wantErr: require.Error, 236 }, 237 { 238 name: "Invalid Field Name", 239 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "field+", "type": "int"}]}`, 240 wantErr: require.Error, 241 }, 242 { 243 name: "Invalid Alias", 244 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "field", "aliases": ["test+"], "type": "int"}]}`, 245 wantErr: require.Error, 246 }, 247 { 248 name: "No Field Type", 249 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "field"}]}`, 250 wantErr: require.Error, 251 }, 252 { 253 name: "Invalid Field Type", 254 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "blah"}]}`, 255 wantErr: require.Error, 256 }, 257 } 258 259 for _, test := range tests { 260 test := test 261 t.Run(test.name, func(t *testing.T) { 262 t.Parallel() 263 264 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 265 266 test.wantErr(t, err) 267 if schema != nil { 268 assert.Equal(t, avro.Record, schema.Type()) 269 } 270 }) 271 } 272 } 273 274 func TestErrorRecordSchema(t *testing.T) { 275 tests := []struct { 276 name string 277 schema string 278 wantSchema bool 279 wantErr require.ErrorAssertionFunc 280 }{ 281 { 282 name: "Valid", 283 schema: `{"type":"error", "name":"test", "namespace": "org.hamba.avro", "doc": "docs", "fields":[{"name": "field", "type": "int"}]}`, 284 wantSchema: true, 285 wantErr: require.NoError, 286 }, 287 { 288 name: "Invalid Name First Char", 289 schema: `{"type":"error", "name":"0test", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 290 wantErr: require.Error, 291 }, 292 { 293 name: "Invalid Name Other Char", 294 schema: `{"type":"error", "name":"test+", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 295 wantErr: require.Error, 296 }, 297 { 298 name: "Empty Name", 299 schema: `{"type":"error", "name":"", "namespace": "org.hamba.avro", "fields":[{"name": "field", "type": "int"}]}`, 300 wantErr: require.Error, 301 }, 302 { 303 name: "No Name", 304 schema: `{"type":"error", "namespace": "org.hamba.avro", "fields":[{"name": "intField", "type": "int"}]}`, 305 wantErr: require.Error, 306 }, 307 { 308 name: "Invalid Namespace", 309 schema: `{"type":"error", "name":"test", "namespace": "org.hamba.avro+", "fields":[{"name": "field", "type": "int"}]}`, 310 wantErr: require.Error, 311 }, 312 { 313 name: "Empty Namespace", 314 schema: `{"type":"error", "name":"test", "namespace": "", "fields":[{"name": "intField", "type": "int"}]}`, 315 wantErr: require.Error, 316 }, 317 } 318 319 for _, test := range tests { 320 test := test 321 t.Run(test.name, func(t *testing.T) { 322 t.Parallel() 323 324 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 325 326 test.wantErr(t, err) 327 if test.wantSchema { 328 assert.Equal(t, avro.Record, schema.Type()) 329 recSchema := schema.(*avro.RecordSchema) 330 assert.True(t, recSchema.IsError()) 331 } 332 }) 333 } 334 } 335 336 func TestRecordSchema_ValidatesDefault(t *testing.T) { 337 tests := []struct { 338 name string 339 schema string 340 wantErr assert.ErrorAssertionFunc 341 }{ 342 { 343 name: "String", 344 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": "string", "default": "test"}]}`, 345 wantErr: assert.NoError, 346 }, 347 { 348 name: "Int", 349 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": "int", "default": 1}]}`, 350 wantErr: assert.NoError, 351 }, 352 { 353 name: "Long", 354 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": "long", "default": 1}]}`, 355 wantErr: assert.NoError, 356 }, 357 { 358 name: "Float", 359 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": "float", "default": 1}]}`, 360 wantErr: assert.NoError, 361 }, 362 { 363 name: "Double", 364 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": "double", "default": 1}]}`, 365 wantErr: assert.NoError, 366 }, 367 { 368 name: "Array", 369 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"array", "items": "int"}, "default": [1,2]}]}`, 370 wantErr: assert.NoError, 371 }, 372 { 373 name: "Array Not Array", 374 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"array", "items": "int"}, "default": "test"}]}`, 375 wantErr: assert.Error, 376 }, 377 { 378 name: "Array Invalid Type", 379 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"array", "items": "int"}, "default": ["test"]}]}`, 380 wantErr: assert.Error, 381 }, 382 { 383 name: "Map", 384 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"map", "values": "int"}, "default": {"b": 1}}]}`, 385 wantErr: assert.NoError, 386 }, 387 { 388 name: "Map Not Map", 389 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"map", "values": "int"}, "default": "test"}]}`, 390 wantErr: assert.Error, 391 }, 392 { 393 name: "Map Invalid Type", 394 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"map", "values": "int"}, "default": {"b": "test"}}]}`, 395 wantErr: assert.Error, 396 }, 397 { 398 name: "Union", 399 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": ["string", "null"]}]}`, 400 wantErr: assert.NoError, 401 }, 402 { 403 name: "Union Default", 404 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": ["null", "string"], "default": null}]}`, 405 wantErr: assert.NoError, 406 }, 407 { 408 name: "Union Invalid Type", 409 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": ["null", "string"], "default": "string"}]}`, 410 wantErr: assert.Error, 411 }, 412 { 413 name: "Record", 414 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"record", "name": "test2", "fields":[{"name": "b", "type": "int"},{"name": "c", "type": "int", "default": 1}]}, "default": {"b": 1}}]}`, 415 wantErr: assert.NoError, 416 }, 417 { 418 name: "Record Not Map", 419 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"record", "name": "test2", "fields":[{"name": "b", "type": "int"},{"name": "c", "type": "int", "default": 1}]}, "default": "test"}]}`, 420 wantErr: assert.Error, 421 }, 422 { 423 name: "Record Invalid Type", 424 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"record", "name": "test2", "fields":[{"name": "b", "type": "int"},{"name": "c", "type": "int", "default": 1}]}, "default": {"b": "test"}}]}`, 425 wantErr: assert.Error, 426 }, 427 { 428 name: "Record Invalid Field Type", 429 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "fields":[{"name": "a", "type": {"type":"record", "name": "test2", "fields":[{"name": "b", "type": "int"},{"name": "c", "type": "int", "default": "test"}]}, "default": {"b": 1}}]}`, 430 wantErr: assert.Error, 431 }, 432 } 433 434 for _, test := range tests { 435 test := test 436 t.Run(test.name, func(t *testing.T) { 437 t.Parallel() 438 439 _, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 440 441 test.wantErr(t, err) 442 }) 443 } 444 } 445 446 func TestRecordSchema_ValidatesOrder(t *testing.T) { 447 tests := []struct { 448 name string 449 schema string 450 want avro.Order 451 wantErr assert.ErrorAssertionFunc 452 }{ 453 { 454 name: "empty", 455 schema: `{"type":"record", "name":"test", "fields":[{"name": "a", "type": "string"}]}`, 456 want: avro.Asc, 457 wantErr: assert.NoError, 458 }, 459 { 460 name: "asc", 461 schema: `{"type":"record", "name":"test", "fields":[{"name": "a", "type": "string", "order": "ascending"}]}`, 462 want: avro.Asc, 463 wantErr: assert.NoError, 464 }, 465 { 466 name: "desc", 467 schema: `{"type":"record", "name":"test", "fields":[{"name": "a", "type": "string", "order": "descending"}]}`, 468 want: avro.Desc, 469 wantErr: assert.NoError, 470 }, 471 { 472 name: "ignore", 473 schema: `{"type":"record", "name":"test", "fields":[{"name": "a", "type": "string", "order": "ignore"}]}`, 474 want: avro.Ignore, 475 wantErr: assert.NoError, 476 }, 477 { 478 name: "invalid", 479 schema: `{"type":"record", "name":"test", "fields":[{"name": "a", "type": "string", "order": "blah"}]}`, 480 wantErr: assert.Error, 481 }, 482 } 483 484 for _, test := range tests { 485 test := test 486 t.Run(test.name, func(t *testing.T) { 487 t.Parallel() 488 489 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 490 491 test.wantErr(t, err) 492 if test.want != "" { 493 rs := schema.(*avro.RecordSchema) 494 require.Len(t, rs.Fields(), 1) 495 assert.Equal(t, test.want, rs.Fields()[0].Order()) 496 } 497 }) 498 } 499 } 500 501 func TestRecordSchema_HandlesProps(t *testing.T) { 502 schm := ` 503 { 504 "type": "record", 505 "name": "valid_name", 506 "namespace": "org.hamba.avro", 507 "doc": "foo", 508 "foo": "bar1", 509 "fields": [ 510 {"name": "intField", "doc": "bar", "type": "int", "foo": "bar2"} 511 ] 512 } 513 ` 514 515 s, err := avro.Parse(schm) 516 require.NoError(t, err) 517 518 rs := s.(*avro.RecordSchema) 519 assert.Equal(t, avro.Record, s.Type()) 520 assert.Equal(t, "foo", rs.Doc()) 521 assert.Equal(t, "bar1", rs.Prop("foo")) 522 require.Len(t, rs.Fields(), 1) 523 assert.Equal(t, "bar", rs.Fields()[0].Doc()) 524 assert.Equal(t, "bar2", rs.Fields()[0].Prop("foo")) 525 } 526 527 func TestRecordSchema_WithReference(t *testing.T) { 528 schm := ` 529 { 530 "type": "record", 531 "name": "valid_name", 532 "namespace": "org.hamba.avro", 533 "fields": [ 534 {"name": "intField", "type": "int"}, 535 {"name": "Ref", "type": "valid_name"} 536 ] 537 } 538 ` 539 540 s, err := avro.Parse(schm) 541 542 require.NoError(t, err) 543 assert.Equal(t, avro.Record, s.Type()) 544 assert.Equal(t, avro.Ref, s.(*avro.RecordSchema).Fields()[1].Type().Type()) 545 assert.Equal(t, s.Fingerprint(), s.(*avro.RecordSchema).Fields()[1].Type().Fingerprint()) 546 } 547 548 func TestRecordSchema_WithReferenceFullName(t *testing.T) { 549 schm := ` 550 { 551 "type": "record", 552 "name": "org.hamba.avro.ValidName", 553 "fields": [ 554 { 555 "name": "recordType1", 556 "type": { 557 "name": "refIntType", 558 "type": "record", 559 "fields": [ 560 {"name": "intField", "type": "int"} 561 ] 562 } 563 }, 564 { 565 "name": "recordType2", 566 "type": "org.hamba.avro.refIntType" 567 } 568 ] 569 } 570 ` 571 572 s, err := avro.Parse(schm) 573 574 require.NoError(t, err) 575 assert.Equal(t, avro.Record, s.Type()) 576 assert.Equal(t, avro.Ref, s.(*avro.RecordSchema).Fields()[1].Type().Type()) 577 assert.Equal(t, s.(*avro.RecordSchema).Fields()[0].Type().Fingerprint(), s.(*avro.RecordSchema).Fields()[1].Type().Fingerprint()) 578 } 579 580 func TestRecordSchema_WithAliasReference(t *testing.T) { 581 schm := ` 582 { 583 "type": "record", 584 "name": "valid_name", 585 "namespace": "org.hamba.avro", 586 "aliases": ["valid_alias"], 587 "fields": [ 588 {"name": "intField", "type": "int"}, 589 {"name": "ref", "type": "valid_alias"} 590 ] 591 } 592 ` 593 594 s, err := avro.Parse(schm) 595 596 require.NoError(t, err) 597 assert.Equal(t, avro.Record, s.Type()) 598 assert.Equal(t, avro.Ref, s.(*avro.RecordSchema).Fields()[1].Type().Type()) 599 assert.Equal(t, s.Fingerprint(), s.(*avro.RecordSchema).Fields()[1].Type().Fingerprint()) 600 } 601 602 func TestEnumSchema(t *testing.T) { 603 tests := []struct { 604 name string 605 schema string 606 wantName string 607 wantDefault string 608 wantErr require.ErrorAssertionFunc 609 }{ 610 { 611 name: "Valid", 612 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":["TEST"]}`, 613 wantName: "org.hamba.avro.test", 614 wantErr: require.NoError, 615 }, 616 { 617 name: "Valid With Default", 618 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":["TEST"], "default": "TEST"}`, 619 wantName: "org.hamba.avro.test", 620 wantDefault: "TEST", 621 wantErr: require.NoError, 622 }, 623 { 624 name: "Invalid Name", 625 schema: `{"type":"enum", "name":"test+", "namespace": "org.hamba.avro", "symbols":["TEST"]}`, 626 wantErr: require.Error, 627 }, 628 { 629 name: "Empty Name", 630 schema: `{"type":"enum", "name":"", "namespace": "org.hamba.avro", "symbols":["TEST"]}`, 631 wantErr: require.Error, 632 }, 633 { 634 name: "No Name", 635 schema: `{"type":"enum", "namespace": "org.hamba.avro", "symbols":["TEST"]}`, 636 wantErr: require.Error, 637 }, 638 { 639 name: "Invalid Namespace", 640 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro+", "symbols":["TEST"]}`, 641 wantErr: require.Error, 642 }, 643 { 644 name: "Empty Namespace", 645 schema: `{"type":"enum", "name":"test", "namespace": "", "symbols":["TEST"]}`, 646 wantErr: require.Error, 647 }, 648 { 649 name: "No Symbols", 650 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro"}`, 651 wantErr: require.Error, 652 }, 653 { 654 name: "Empty Symbols", 655 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":[]}`, 656 wantErr: require.Error, 657 }, 658 { 659 name: "Invalid Symbol", 660 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":["TEST+"]}`, 661 wantErr: require.Error, 662 }, 663 { 664 name: "Invalid Symbol Type", 665 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":[1]}`, 666 wantErr: require.Error, 667 }, 668 { 669 name: "Invalid Default", 670 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":["TEST"], "default": "foo"}`, 671 wantErr: require.Error, 672 }, 673 } 674 675 for _, test := range tests { 676 test := test 677 t.Run(test.name, func(t *testing.T) { 678 t.Parallel() 679 680 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 681 682 test.wantErr(t, err) 683 if test.wantName != "" { 684 assert.Equal(t, avro.Enum, schema.Type()) 685 named := schema.(*avro.EnumSchema) 686 assert.Equal(t, test.wantName, named.FullName()) 687 assert.Equal(t, test.wantDefault, named.Default()) 688 } 689 }) 690 } 691 } 692 693 func TestEnumSchema_HandlesProps(t *testing.T) { 694 schm := `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "doc": "hello", "symbols":["TEST"], "foo":"bar"}` 695 696 s, err := avro.Parse(schm) 697 require.NoError(t, err) 698 699 es := s.(*avro.EnumSchema) 700 assert.Equal(t, avro.Enum, s.Type()) 701 assert.Equal(t, "hello", es.Doc()) 702 assert.Equal(t, "bar", es.Prop("foo")) 703 } 704 705 func TestArraySchema(t *testing.T) { 706 tests := []struct { 707 name string 708 schema string 709 want avro.Schema 710 wantErr require.ErrorAssertionFunc 711 }{ 712 { 713 name: "Valid", 714 schema: `{"type":"array", "items": "int"}`, 715 want: avro.NewArraySchema(avro.NewPrimitiveSchema(avro.Int, nil)), 716 wantErr: require.NoError, 717 }, 718 { 719 name: "No Items", 720 schema: `{"type":"array"}`, 721 wantErr: require.Error, 722 }, 723 { 724 name: "Invalid Items Type", 725 schema: `{"type":"array", "items": "blah"}`, 726 wantErr: require.Error, 727 }, 728 } 729 730 for _, test := range tests { 731 test := test 732 t.Run(test.name, func(t *testing.T) { 733 t.Parallel() 734 735 got, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 736 737 test.wantErr(t, err) 738 assert.Equal(t, test.want, got) 739 }) 740 } 741 } 742 743 func TestArraySchema_HandlesProps(t *testing.T) { 744 schm := `{"type":"array", "items": "int", "foo":"bar"}` 745 746 s, err := avro.Parse(schm) 747 748 require.NoError(t, err) 749 assert.Equal(t, avro.Array, s.Type()) 750 assert.Equal(t, "bar", s.(*avro.ArraySchema).Prop("foo")) 751 } 752 753 func TestMapSchema(t *testing.T) { 754 tests := []struct { 755 name string 756 schema string 757 want avro.Schema 758 wantErr require.ErrorAssertionFunc 759 }{ 760 { 761 name: "Valid", 762 schema: `{"type":"map", "values": "int"}`, 763 want: avro.NewMapSchema(avro.NewPrimitiveSchema(avro.Int, nil)), 764 wantErr: require.NoError, 765 }, 766 { 767 name: "No Values", 768 schema: `{"type":"map"}`, 769 wantErr: require.Error, 770 }, 771 { 772 name: "Invalid Values Type", 773 schema: `{"type":"map", "values": "blah"}`, 774 wantErr: require.Error, 775 }, 776 } 777 778 for _, test := range tests { 779 test := test 780 t.Run(test.name, func(t *testing.T) { 781 t.Parallel() 782 783 got, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 784 785 test.wantErr(t, err) 786 assert.Equal(t, test.want, got) 787 }) 788 } 789 } 790 791 func TestMapSchema_HandlesProps(t *testing.T) { 792 schm := `{"type":"map", "values": "int", "foo":"bar"}` 793 794 s, err := avro.Parse(schm) 795 796 require.NoError(t, err) 797 assert.Equal(t, avro.Map, s.Type()) 798 assert.Equal(t, "bar", s.(*avro.MapSchema).Prop("foo")) 799 } 800 801 func TestUnionSchema(t *testing.T) { 802 tests := []struct { 803 name string 804 schema string 805 wantFingerprint [32]byte 806 wantErr require.ErrorAssertionFunc 807 }{ 808 { 809 name: "Valid Simple", 810 schema: `["null", "int"]`, 811 wantFingerprint: [32]byte{0xb4, 0x94, 0x95, 0xc5, 0xb1, 0xc2, 0x6f, 0x4, 0x89, 0x6a, 0x5f, 0x68, 0x65, 0xf, 0xe2, 0xb7, 0x64, 0x23, 0x62, 0xc3, 0x41, 0x98, 0xd6, 0xbc, 0x74, 0x65, 0xa1, 0xd9, 0xf7, 0xe1, 0xaf, 0xce}, 812 wantErr: require.NoError, 813 }, 814 { 815 name: "Valid Complex", 816 schema: `{"type":["null", "int"]}`, 817 wantFingerprint: [32]byte{0xb4, 0x94, 0x95, 0xc5, 0xb1, 0xc2, 0x6f, 0x4, 0x89, 0x6a, 0x5f, 0x68, 0x65, 0xf, 0xe2, 0xb7, 0x64, 0x23, 0x62, 0xc3, 0x41, 0x98, 0xd6, 0xbc, 0x74, 0x65, 0xa1, 0xd9, 0xf7, 0xe1, 0xaf, 0xce}, 818 wantErr: require.NoError, 819 }, 820 { 821 name: "No Nested Union Type", 822 schema: `["null", ["string"]]`, 823 wantErr: require.Error, 824 }, 825 { 826 name: "No Duplicate Types", 827 schema: `["string", "string"]`, 828 wantErr: require.Error, 829 }, 830 { 831 name: "No Duplicate Names", 832 schema: `[{"type":"enum", "name":"test", "symbols":["TEST"]}, {"type":"enum", "name":"test", "symbols":["TEST"]}]`, 833 wantErr: require.Error, 834 }, 835 { 836 name: "Invalid Type", 837 schema: `["null", "blah"]`, 838 wantErr: require.Error, 839 }, 840 } 841 842 for _, test := range tests { 843 test := test 844 t.Run(test.name, func(t *testing.T) { 845 t.Parallel() 846 847 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 848 849 test.wantErr(t, err) 850 if test.wantFingerprint != [32]byte{} { 851 assert.Equal(t, avro.Union, schema.Type()) 852 assert.Equal(t, test.wantFingerprint, schema.Fingerprint()) 853 } 854 }) 855 } 856 } 857 858 func TestUnionSchema_Indices(t *testing.T) { 859 tests := []struct { 860 name string 861 schema string 862 want [2]int 863 }{ 864 { 865 name: "Null First", 866 schema: `["null", "string"]`, 867 want: [2]int{0, 1}, 868 }, 869 { 870 name: "Null Second", 871 schema: `["string", "null"]`, 872 want: [2]int{1, 0}, 873 }, 874 { 875 name: "Not Nullable", 876 schema: `["null", "string", "int"]`, 877 want: [2]int{0, 0}, 878 }, 879 } 880 881 for _, test := range tests { 882 test := test 883 t.Run(test.name, func(t *testing.T) { 884 t.Parallel() 885 886 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 887 888 require.NoError(t, err) 889 null, typ := schema.(*avro.UnionSchema).Indices() 890 assert.Equal(t, test.want[0], null) 891 assert.Equal(t, test.want[1], typ) 892 }) 893 } 894 } 895 896 func TestFixedSchema(t *testing.T) { 897 tests := []struct { 898 name string 899 schema string 900 wantName string 901 wantFingerprint [32]byte 902 wantErr require.ErrorAssertionFunc 903 }{ 904 { 905 name: "Valid", 906 schema: `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro", "size": 12}`, 907 wantName: "org.hamba.avro.test", 908 wantFingerprint: [32]uint8{0x8c, 0x9e, 0xcb, 0x4, 0x83, 0x2f, 0x3b, 0xa7, 0x58, 0x85, 0x9, 0x99, 0x41, 0xe, 0xbf, 0xd4, 0x7, 0xc7, 0x87, 0x4f, 0x8a, 0x12, 0xf4, 0xd0, 0x7f, 0x45, 0xdd, 0xaa, 0x10, 0x6b, 0x2f, 0xb3}, 909 wantErr: require.NoError, 910 }, 911 { 912 name: "Invalid Name", 913 schema: `{"type":"fixed", "name":"test+", "namespace": "org.hamba.avro", "size": 12}`, 914 wantErr: require.Error, 915 }, 916 { 917 name: "Empty Name", 918 schema: `{"type":"fixed", "name":"", "namespace": "org.hamba.avro", "size": 12}`, 919 wantErr: require.Error, 920 }, 921 { 922 name: "No Name", 923 schema: `{"type":"fixed", "namespace": "org.hamba.avro", "size": 12}`, 924 wantErr: require.Error, 925 }, 926 { 927 name: "Invalid Namespace", 928 schema: `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro+", "size": 12}`, 929 wantErr: require.Error, 930 }, 931 { 932 name: "Empty Namespace", 933 schema: `{"type":"fixed", "name":"test", "namespace": "", "size": 12}`, 934 wantErr: require.Error, 935 }, 936 { 937 name: "No Size", 938 schema: `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro"}`, 939 wantErr: require.Error, 940 }, 941 { 942 name: "Invalid Size Type", 943 schema: `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro", "size": "test"}`, 944 wantErr: require.Error, 945 }, 946 } 947 948 for _, test := range tests { 949 test := test 950 t.Run(test.name, func(t *testing.T) { 951 t.Parallel() 952 953 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 954 955 test.wantErr(t, err) 956 if test.wantFingerprint != [32]byte{} { 957 assert.Equal(t, avro.Fixed, schema.Type()) 958 named := schema.(avro.NamedSchema) 959 assert.Equal(t, test.wantName, named.FullName()) 960 assert.Equal(t, test.wantFingerprint, named.Fingerprint()) 961 } 962 }) 963 } 964 } 965 966 func TestFixedSchema_HandlesProps(t *testing.T) { 967 schm := `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro", "size": 12, "foo":"bar"}` 968 969 s, err := avro.Parse(schm) 970 971 require.NoError(t, err) 972 assert.Equal(t, avro.Fixed, s.Type()) 973 assert.Equal(t, "bar", s.(*avro.FixedSchema).Prop("foo")) 974 } 975 976 func TestSchema_LogicalTypes(t *testing.T) { 977 tests := []struct { 978 name string 979 schema string 980 wantType avro.Type 981 wantLogical bool 982 wantLogicalType avro.LogicalType 983 assertFn func(t *testing.T, ls avro.LogicalSchema) 984 }{ 985 { 986 name: "Invalid", 987 schema: `{"type": "int", "logicalType": "test"}`, 988 wantType: avro.Int, 989 wantLogical: false, 990 }, 991 { 992 name: "Date", 993 schema: `{"type": "int", "logicalType": "date"}`, 994 wantType: avro.Int, 995 wantLogical: true, 996 wantLogicalType: avro.Date, 997 }, 998 { 999 name: "Time Millis", 1000 schema: `{"type": "int", "logicalType": "time-millis"}`, 1001 wantType: avro.Int, 1002 wantLogical: true, 1003 wantLogicalType: avro.TimeMillis, 1004 }, 1005 { 1006 name: "Time Micros", 1007 schema: `{"type": "long", "logicalType": "time-micros"}`, 1008 wantType: avro.Long, 1009 wantLogical: true, 1010 wantLogicalType: avro.TimeMicros, 1011 }, 1012 { 1013 name: "Timestamp Millis", 1014 schema: `{"type": "long", "logicalType": "timestamp-millis"}`, 1015 wantType: avro.Long, 1016 wantLogical: true, 1017 wantLogicalType: avro.TimestampMillis, 1018 }, 1019 { 1020 name: "Timestamp Micros", 1021 schema: `{"type": "long", "logicalType": "timestamp-micros"}`, 1022 wantType: avro.Long, 1023 wantLogical: true, 1024 wantLogicalType: avro.TimestampMicros, 1025 }, 1026 { 1027 name: "Local Timestamp Millis", 1028 schema: `{"type": "long", "logicalType": "local-timestamp-millis"}`, 1029 wantType: avro.Long, 1030 wantLogical: true, 1031 wantLogicalType: avro.LocalTimestampMillis, 1032 }, 1033 { 1034 name: "Local Timestamp Micros", 1035 schema: `{"type": "long", "logicalType": "local-timestamp-micros"}`, 1036 wantType: avro.Long, 1037 wantLogical: true, 1038 wantLogicalType: avro.LocalTimestampMicros, 1039 }, 1040 { 1041 name: "UUID", 1042 schema: `{"type": "string", "logicalType": "uuid"}`, 1043 wantType: avro.String, 1044 wantLogical: true, 1045 wantLogicalType: avro.UUID, 1046 }, 1047 { 1048 name: "Duration", 1049 schema: `{"type": "fixed", "name":"test", "size": 12, "logicalType": "duration"}`, 1050 wantType: avro.Fixed, 1051 wantLogical: true, 1052 wantLogicalType: avro.Duration, 1053 }, 1054 { 1055 name: "Invalid Duration", 1056 schema: `{"type": "fixed", "name":"test", "size": 11, "logicalType": "duration"}`, 1057 wantType: avro.Fixed, 1058 wantLogical: false, 1059 }, 1060 { 1061 name: "Bytes Decimal", 1062 schema: `{"type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2}`, 1063 wantType: avro.Bytes, 1064 wantLogical: true, 1065 wantLogicalType: avro.Decimal, 1066 assertFn: func(t *testing.T, ls avro.LogicalSchema) { 1067 dec, ok := ls.(*avro.DecimalLogicalSchema) 1068 require.True(t, ok) 1069 assert.Equal(t, 4, dec.Precision()) 1070 assert.Equal(t, 2, dec.Scale()) 1071 }, 1072 }, 1073 { 1074 name: "Bytes Decimal No Scale", 1075 schema: `{"type": "bytes", "logicalType": "decimal", "precision": 4}`, 1076 wantType: avro.Bytes, 1077 wantLogical: true, 1078 wantLogicalType: avro.Decimal, 1079 assertFn: func(t *testing.T, ls avro.LogicalSchema) { 1080 dec, ok := ls.(*avro.DecimalLogicalSchema) 1081 require.True(t, ok) 1082 assert.Equal(t, 4, dec.Precision()) 1083 assert.Equal(t, 0, dec.Scale()) 1084 }, 1085 }, 1086 { 1087 name: "Bytes Decimal Negative Precision", 1088 schema: `{"type": "bytes", "logicalType": "decimal", "precision": 0}`, 1089 wantType: avro.Bytes, 1090 wantLogical: false, 1091 }, 1092 { 1093 name: "Bytes Decimal Negative Scale", 1094 schema: `{"type": "bytes", "logicalType": "decimal", "precision": 1, "scale": -1}`, 1095 wantType: avro.Bytes, 1096 wantLogical: false, 1097 }, 1098 { 1099 name: "Bytes Decimal Scale Larger Than Precision", 1100 schema: `{"type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 6}`, 1101 wantType: avro.Bytes, 1102 wantLogical: false, 1103 }, 1104 { 1105 name: "Fixed Decimal", 1106 schema: `{"type": "fixed", "name":"test", "size": 12, "logicalType": "decimal", "precision": 4, "scale": 2}`, 1107 wantType: avro.Fixed, 1108 wantLogical: true, 1109 wantLogicalType: avro.Decimal, 1110 assertFn: func(t *testing.T, ls avro.LogicalSchema) { 1111 dec, ok := ls.(*avro.DecimalLogicalSchema) 1112 require.True(t, ok) 1113 assert.Equal(t, 4, dec.Precision()) 1114 assert.Equal(t, 2, dec.Scale()) 1115 }, 1116 }, 1117 { 1118 name: "Fixed Decimal No Scale", 1119 schema: `{"type": "fixed", "name":"test", "size": 12, "logicalType": "decimal", "precision": 4}`, 1120 wantType: avro.Fixed, 1121 wantLogical: true, 1122 wantLogicalType: avro.Decimal, 1123 assertFn: func(t *testing.T, ls avro.LogicalSchema) { 1124 dec, ok := ls.(*avro.DecimalLogicalSchema) 1125 require.True(t, ok) 1126 assert.Equal(t, 4, dec.Precision()) 1127 assert.Equal(t, 0, dec.Scale()) 1128 }, 1129 }, 1130 { 1131 name: "Fixed Decimal Negative Precision", 1132 schema: `{"type": "fixed", "name":"test", "size": 12, "logicalType": "decimal", "precision": 0}`, 1133 wantType: avro.Fixed, 1134 wantLogical: false, 1135 }, 1136 { 1137 name: "Fixed Decimal Precision Too Large", 1138 schema: `{"type": "fixed", "name":"test", "size": 4, "logicalType": "decimal", "precision": 10}`, 1139 wantType: avro.Fixed, 1140 wantLogical: false, 1141 }, 1142 { 1143 name: "Fixed Decimal Scale Larger Than Precision", 1144 schema: `{"type": "fixed", "name":"test", "size": 12, "logicalType": "decimal", "precision": 4, "scale": 6}`, 1145 wantType: avro.Fixed, 1146 wantLogical: false, 1147 }, 1148 } 1149 1150 for _, test := range tests { 1151 test := test 1152 t.Run(test.name, func(t *testing.T) { 1153 t.Parallel() 1154 1155 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 1156 require.NoError(t, err) 1157 1158 assert.Equal(t, test.wantType, schema.Type()) 1159 1160 lts, ok := schema.(avro.LogicalTypeSchema) 1161 if !ok { 1162 assert.Fail(t, "logical type schema expected") 1163 return 1164 } 1165 1166 ls := lts.Logical() 1167 require.Equal(t, test.wantLogical, ls != nil) 1168 if !test.wantLogical { 1169 return 1170 } 1171 1172 assert.Equal(t, test.wantLogicalType, ls.Type()) 1173 1174 if test.assertFn != nil { 1175 test.assertFn(t, ls) 1176 } 1177 }) 1178 } 1179 } 1180 1181 func TestSchema_FingerprintUsing(t *testing.T) { 1182 tests := []struct { 1183 name string 1184 schema string 1185 typ avro.FingerprintType 1186 want []byte 1187 }{ 1188 1189 { 1190 name: "Null CRC64", 1191 schema: "null", 1192 typ: avro.CRC64Avro, 1193 want: []byte{0x63, 0xdd, 0x24, 0xe7, 0xcc, 0x25, 0x8f, 0x8a}, 1194 }, 1195 { 1196 name: "Null MD5", 1197 schema: "null", 1198 typ: avro.MD5, 1199 want: []byte{0x9b, 0x41, 0xef, 0x67, 0x65, 0x1c, 0x18, 0x48, 0x8a, 0x8b, 0x8, 0xbb, 0x67, 0xc7, 0x56, 0x99}, 1200 }, 1201 { 1202 name: "Null SHA256", 1203 schema: "null", 1204 typ: avro.SHA256, 1205 want: []byte{0xf0, 0x72, 0xcb, 0xec, 0x3b, 0xf8, 0x84, 0x18, 0x71, 0xd4, 0x28, 0x42, 0x30, 0xc5, 0xe9, 0x83, 0xdc, 0x21, 0x1a, 0x56, 0x83, 0x7a, 0xed, 0x86, 0x24, 0x87, 0x14, 0x8f, 0x94, 0x7d, 0x1a, 0x1f}, 1206 }, 1207 { 1208 name: "Primitive CRC64", 1209 schema: "string", 1210 typ: avro.CRC64Avro, 1211 want: []byte{0x8f, 0x1, 0x48, 0x72, 0x63, 0x45, 0x3, 0xc7}, 1212 }, 1213 { 1214 name: "Record CRC64", 1215 schema: `{"type":"record", "name":"test", "namespace": "org.hamba.avro", "doc": "docs", "fields":[{"name": "field", "type": "int"}]}`, 1216 typ: avro.CRC64Avro, 1217 want: []byte{0xaf, 0x30, 0x30, 0xf0, 0x1c, 0x99, 0x76, 0xda}, 1218 }, 1219 { 1220 name: "Enum CRC64", 1221 schema: `{"type":"enum", "name":"test", "namespace": "org.hamba.avro", "symbols":["TEST"]}`, 1222 typ: avro.CRC64Avro, 1223 want: []byte{0xc, 0xb0, 0xa2, 0xa6, 0x5f, 0x96, 0x8, 0xd1}, 1224 }, 1225 { 1226 name: "Array CRC64", 1227 schema: `{"type":"array", "items": "int"}`, 1228 typ: avro.CRC64Avro, 1229 want: []byte{0x52, 0x2b, 0x81, 0x4f, 0xc9, 0x63, 0xb4, 0xbe}, 1230 }, 1231 { 1232 name: "Map CRC64", 1233 schema: `{"type":"map", "values": "int"}`, 1234 typ: avro.CRC64Avro, 1235 want: []byte{0xdb, 0x39, 0xe2, 0xc2, 0x53, 0x4c, 0x89, 0x73}, 1236 }, 1237 { 1238 name: "Union CRC64", 1239 schema: `["null", "int"]`, 1240 typ: avro.CRC64Avro, 1241 want: []byte{0xd5, 0x1c, 0xc0, 0x92, 0x2b, 0x46, 0xb1, 0xd7}, 1242 }, 1243 { 1244 name: "Fixed CRC64", 1245 schema: `{"type":"fixed", "name":"test", "namespace": "org.hamba.avro", "size": 12}`, 1246 typ: avro.CRC64Avro, 1247 want: []byte{0x1, 0x7c, 0x1f, 0x7f, 0xa7, 0x6d, 0xa0, 0xa1}, 1248 }, 1249 } 1250 1251 for _, test := range tests { 1252 test := test 1253 t.Run(test.name, func(t *testing.T) { 1254 t.Parallel() 1255 1256 schema, err := avro.ParseWithCache(test.schema, "", &avro.SchemaCache{}) 1257 require.NoError(t, err) 1258 1259 got, err := schema.FingerprintUsing(test.typ) 1260 1261 require.NoError(t, err) 1262 assert.Equal(t, test.want, got) 1263 }) 1264 } 1265 } 1266 1267 func TestSchema_FingerprintUsingReference(t *testing.T) { 1268 schema := avro.MustParse(` 1269 { 1270 "type": "record", 1271 "name": "valid_name", 1272 "namespace": "org.hamba.avro", 1273 "fields": [ 1274 {"name": "intField", "type": "int"}, 1275 {"name": "Ref", "type": "valid_name"} 1276 ] 1277 } 1278 `) 1279 1280 got, err := schema.(*avro.RecordSchema).Fields()[1].Type().FingerprintUsing(avro.CRC64Avro) 1281 1282 require.NoError(t, err) 1283 assert.Equal(t, []byte{0xe1, 0xd6, 0x1e, 0x7c, 0x2f, 0xe3, 0x3c, 0x2b}, got) 1284 } 1285 1286 func TestSchema_FingerprintUsingInvalidType(t *testing.T) { 1287 schema := avro.MustParse("string") 1288 1289 _, err := schema.FingerprintUsing("test") 1290 1291 assert.Error(t, err) 1292 } 1293 1294 func TestSchema_MultiFile(t *testing.T) { 1295 got, err := avro.ParseFiles("testdata/superhero-part1.avsc", "testdata/superhero-part2.avsc") 1296 1297 require.NoError(t, err) 1298 want, err := avro.ParseFiles("testdata/superhero.avsc") 1299 require.NoError(t, err) 1300 assert.Equal(t, want, got) 1301 } 1302 1303 func TestSchema_DoesNotAllowDuplicateFullNames(t *testing.T) { 1304 schm := ` 1305 { 1306 "type": "record", 1307 "name": "Interop", 1308 "namespace": "org.hamba.avro", 1309 "fields": [ 1310 { 1311 "name": "intField", 1312 "type": { 1313 "type": "record", 1314 "name": "Interop", 1315 "fields": [] 1316 } 1317 } 1318 ] 1319 } 1320 ` 1321 1322 _, err := avro.Parse(schm) 1323 1324 assert.Error(t, err) 1325 } 1326 1327 func TestSchema_Interop(t *testing.T) { 1328 schm := ` 1329 { 1330 "type": "record", 1331 "name": "Interop", 1332 "namespace": "org.hamba.avro", 1333 "fields": [ 1334 { 1335 "name": "intField", 1336 "type": "int" 1337 }, 1338 { 1339 "name": "longField", 1340 "type": "long" 1341 }, 1342 { 1343 "name": "stringField", 1344 "type": "string" 1345 }, 1346 { 1347 "name": "boolField", 1348 "type": "boolean" 1349 }, 1350 { 1351 "name": "floatField", 1352 "type": "float" 1353 }, 1354 { 1355 "name": "doubleField", 1356 "type": "double" 1357 }, 1358 { 1359 "name": "bytesField", 1360 "type": "bytes" 1361 }, 1362 { 1363 "name": "nullField", 1364 "type": "null" 1365 }, 1366 { 1367 "name": "arrayField", 1368 "type": { 1369 "type": "array", 1370 "items": "double" 1371 } 1372 }, 1373 { 1374 "name": "mapField", 1375 "type": { 1376 "type": "map", 1377 "values": { 1378 "type": "record", 1379 "name": "Foo", 1380 "fields": [ 1381 { 1382 "name": "label", 1383 "type": "string" 1384 } 1385 ] 1386 } 1387 } 1388 }, 1389 { 1390 "name": "unionField", 1391 "type": [ 1392 "boolean", 1393 "double", 1394 { 1395 "type": "array", 1396 "items": "bytes" 1397 } 1398 ] 1399 }, 1400 { 1401 "name": "enumField", 1402 "type": { 1403 "type": "enum", 1404 "name": "Kind", 1405 "symbols": [ 1406 "A", 1407 "B", 1408 "C" 1409 ] 1410 } 1411 }, 1412 { 1413 "name": "fixedField", 1414 "type": { 1415 "type": "fixed", 1416 "name": "MD5", 1417 "size": 16 1418 } 1419 }, 1420 { 1421 "name": "recordField", 1422 "type": { 1423 "type": "record", 1424 "name": "Node", 1425 "fields": [ 1426 { 1427 "name": "label", 1428 "type": "string" 1429 }, 1430 { 1431 "name": "child", 1432 "type": {"type": "org.hamba.avro.Node"} 1433 }, 1434 { 1435 "name": "children", 1436 "type": { 1437 "type": "array", 1438 "items": "Node" 1439 } 1440 } 1441 ] 1442 } 1443 } 1444 ] 1445 }` 1446 1447 _, err := avro.Parse(schm) 1448 1449 assert.NoError(t, err) 1450 } 1451 1452 func TestSchema_ParseBackAndForth(t *testing.T) { 1453 schemaStr := ` 1454 { 1455 "name": "a.b.rootType", 1456 "type": "record", 1457 "fields": [ 1458 { 1459 "name": "someEnum1", 1460 "type": { 1461 "name": "a.b.rootType.classEnum", 1462 "type": "enum", 1463 "symbols": ["A", "B", "C"] 1464 } 1465 }, 1466 { 1467 "name": "someEnum2", 1468 "type": "a.b.rootType.classEnum" 1469 } 1470 ] 1471 }` 1472 1473 schema, err := avro.Parse(schemaStr) 1474 require.NoError(t, err) 1475 1476 b, err := json.Marshal(schema) 1477 require.NoError(t, err) 1478 1479 schema2, err := avro.ParseBytes(b) 1480 1481 assert.NoError(t, err) 1482 assert.Equal(t, schema, schema2) 1483 }