1use std::{fs::File, io::Write, path::PathBuf};
2use vector_common::Result;
3
4use apache_avro::{Decimal, Schema, types::Value};
5use serde::{Deserialize, Serialize};
6
7const FIXTURES_PATH: &str = "lib/codecs/tests/data/avro/generated";
8
9fn generate_avro_test_case_boolean() -> Result<()> {
10 let schema = r#"
11 {
12 "type": "record",
13 "name": "test",
14 "fields": [
15 {"name": "bool_field", "type": "boolean", "default": false}
16 ]
17 }
18 "#;
19 #[derive(Debug, Serialize, Deserialize, Clone)]
20 struct Test {
21 bool_field: bool,
22 }
23 let value = Test { bool_field: true };
24 generate_test_case(schema, value, "boolean")
25}
26
27fn generate_avro_test_case_int() -> Result<()> {
28 let schema = r#"
29 {
30 "type": "record",
31 "name": "test",
32 "fields": [
33 {"name": "int_field", "type": "int", "default": 0}
34 ]
35 }
36 "#;
37 #[derive(Debug, Serialize, Deserialize, Clone)]
38 struct Test {
39 int_field: i32,
40 }
41 let value = Test { int_field: 1234 };
42 generate_test_case(schema, value, "int")
43}
44
45fn generate_avro_test_case_long() -> Result<()> {
46 let schema = r#"
47 {
48 "type": "record",
49 "name": "test",
50 "fields": [
51 {"name": "long_field", "type": "long", "default": 0}
52 ]
53 }
54 "#;
55 #[derive(Debug, Serialize, Deserialize, Clone)]
56 struct Test {
57 long_field: i64,
58 }
59 let value = Test {
60 long_field: 42949672960i64,
61 };
62 generate_test_case(schema, value, "long")
63}
64
65fn generate_avro_test_case_float() -> Result<()> {
66 let schema = r#"
67 {
68 "type": "record",
69 "name": "test",
70 "fields": [
71 {"name": "float_field", "type": "float", "default": 0}
72 ]
73 }
74 "#;
75 #[derive(Debug, Serialize, Deserialize, Clone)]
76 struct Test {
77 float_field: f32,
78 }
79 let value = Test {
80 float_field: 123.456,
81 };
82 generate_test_case(schema, value, "float")
83}
84
85fn generate_avro_test_case_double() -> Result<()> {
86 let schema = r#"
87 {
88 "type": "record",
89 "name": "test",
90 "fields": [
91 {"name": "double_field", "type": "double", "default": 0}
92 ]
93 }
94 "#;
95 #[derive(Debug, Serialize, Deserialize, Clone)]
96 struct Test {
97 double_field: f64,
98 }
99 let value = Test {
100 double_field: 123.456f64,
101 };
102 generate_test_case(schema, value, "double")
103}
104
105fn generate_avro_test_case_bytes() -> Result<()> {
106 let schema = r#"
107 {
108 "type": "record",
109 "name": "test",
110 "fields": [
111 {"name": "bytes_field", "type": "bytes"}
112 ]
113 }
114 "#;
115 #[derive(Debug, Serialize, Deserialize, Clone)]
116 struct Test {
117 bytes_field: Vec<u8>,
118 }
119 let value = Test {
120 bytes_field: vec![1, 2, 3, 4, 5, 6, 6, 7],
121 };
122 generate_test_case(schema, value, "bytes")
123}
124
125fn generate_avro_test_case_string() -> Result<()> {
126 let schema = r#"
127 {
128 "type": "record",
129 "name": "test",
130 "fields": [
131 {"name": "string_field", "type": "string"}
132 ]
133 }
134 "#;
135 #[derive(Debug, Serialize, Deserialize, Clone)]
136 struct Test {
137 string_field: String,
138 }
139 let value = Test {
140 string_field: "hello world!".to_string(),
141 };
142 generate_test_case(schema, value, "string")
143}
144
145#[allow(unused)]
146fn generate_avro_test_case_fixed() -> Result<()> {
147 let schema = r#"
148 {
149 "type": "record",
150 "name": "test",
151 "fields": [
152 {"name": "fixed_field", "type":"fixed", "size": 16}
153 ]
154 }
155 "#;
156 let record = Value::Record(vec![(
157 "fixed_field".into(),
158 Value::Fixed(16, b"1019181716151413".to_vec()),
159 )]);
160 generate_test_case_from_value(schema, record, "fixed")
161}
162
163fn generate_avro_test_case_enum() -> Result<()> {
164 let schema = r#"
165 {
166 "type": "record",
167 "name": "test",
168 "fields": [
169 {"name": "enum_field", "type": "enum", "symbols" : ["Spades", "Hearts", "Diamonds", "Clubs"]}
170 ]
171 }
172 "#;
173 #[derive(Debug, Serialize, Deserialize, Clone)]
174 enum Value {
175 Spades,
176 Hearts,
177 Diamonds,
178 Clubs,
179 }
180 #[derive(Debug, Serialize, Deserialize, Clone)]
181 struct Test {
182 enum_field: Value,
183 }
184 let value = Test {
185 enum_field: Value::Hearts,
186 };
187 generate_test_case(schema, value, "enum")
188}
189
190fn generate_avro_test_case_union() -> Result<()> {
191 let schema = r#"
192 {
193 "type": "record",
194 "name": "test",
195 "fields": [
196 {"name": "union_field", "type": [
197 "string",
198 "int"
199 ]
200 }
201 ]
202 }
203 "#;
204 #[derive(Debug, Serialize, Deserialize, Clone)]
205 struct Test {
206 union_field: i32,
207 }
208 let value = Test {
209 union_field: 123456,
210 };
211 generate_test_case(schema, value, "union")
212}
213
214fn generate_avro_test_case_array() -> Result<()> {
215 let schema = r#"
216 {
217 "type": "record",
218 "name": "test",
219 "fields": [
220 {"name": "array_field", "type": "array", "items" : "string"}
221 ]
222 }
223 "#;
224 #[derive(Debug, Serialize, Deserialize, Clone)]
225 struct Test {
226 array_field: Vec<String>,
227 }
228 let value = Test {
229 array_field: vec![
230 "hello".to_string(),
231 "vector".to_string(),
232 "avro".to_string(),
233 "codec".to_string(),
234 ],
235 };
236 generate_test_case(schema, value, "array")
237}
238
239fn generate_avro_test_case_map() -> Result<()> {
240 let schema = r#"
241 {
242 "type": "record",
243 "name": "test",
244 "fields": [
245 {"name": "map_field", "type": "map", "values" : "long","default": {}}
246 ]
247 }
248 "#;
249 use std::collections::HashMap;
250 #[derive(Debug, Serialize, Deserialize, Clone)]
251 struct Test {
252 map_field: HashMap<String, i64>,
253 }
254 let mut scores = HashMap::new();
255 scores.insert(String::from("Blue"), 10i64);
256 let value = Test { map_field: scores };
257 generate_test_case(schema, value, "map")
258}
259
260fn generate_avro_test_case_record() -> Result<()> {
261 let schema = r#"
262 {
263 "type": "record",
264 "name": "test",
265 "fields": [
266 {"name": "name", "type": "string"},
267 {"name": "age", "type": "int"}
268 ]
269 }
270 "#;
271 #[derive(Debug, Serialize, Deserialize, Clone)]
272 struct Test {
273 name: String,
274 age: i32,
275 }
276 let value = Test {
277 name: "John".to_string(),
278 age: 23,
279 };
280 generate_test_case(schema, value, "record")
281}
282
283#[allow(unused)]
284fn generate_avro_test_case_date() -> Result<()> {
285 let schema = r#"
286 {
287 "type": "record",
288 "name": "test",
289 "fields": [
290 {"name": "date_field", "type": "int", "logicalType": "date"}
291 ]
292 }
293 "#;
294 #[derive(Debug, Serialize, Deserialize, Clone)]
295 struct Test {
296 date_field: i32,
297 }
298 let value = Test { date_field: 19646 };
299 generate_test_case(schema, value, "date")
300}
301
302#[allow(unused)]
303fn generate_avro_test_case_decimal_var() -> Result<()> {
304 let schema = r#"
305 {
306 "type": "record",
307 "name": "test",
308 "fields": [
309 {"name": "decimal_var_field", "type": "bytes", "logicalType": "decimal","precision": 10,"scale": 3}
310 ]
311 }
312 "#;
313
314 let record = Value::Record(vec![(
315 "decimal_var_field".into(),
316 Value::Decimal(Decimal::from([
317 249, 33, 74, 206, 142, 64, 190, 170, 17, 153,
318 ])),
319 )]);
320 generate_test_case_from_value(schema, record, "decimal_var")
321}
322
323#[allow(unused)]
324fn generate_avro_test_case_time_millis() -> Result<()> {
325 let schema = r#"
326 {
327 "type": "record",
328 "name": "test",
329 "fields": [
330 {"name": "time_millis_field", "type": "int", "logicalType": "time-millis"}
331 ]
332 }
333 "#;
334 #[derive(Debug, Serialize, Deserialize, Clone)]
335 struct Test {
336 time_millis_field: i32,
337 }
338 let value = Test {
339 time_millis_field: 59820123,
340 };
341 generate_test_case(schema, value, "time_millis")
342}
343
344fn generate_avro_test_case_time_micros() -> Result<()> {
345 let schema = r#"
346 {
347 "type": "record",
348 "name": "test",
349 "fields": [
350 {"name": "time_micros_field", "type": "long", "logicalType": "time-micros"}
351 ]
352 }
353 "#;
354 #[derive(Debug, Serialize, Deserialize, Clone)]
355 struct Test {
356 time_micros_field: i64,
357 }
358 let value: Test = Test {
359 time_micros_field: 59820123456i64,
360 };
361 generate_test_case(schema, value, "time_micros")
362}
363
364fn generate_avro_test_case_timestamp_millis() -> Result<()> {
365 let schema = r#"
366 {
367 "type": "record",
368 "name": "test",
369 "fields": [
370 {"name": "timestamp_millis_field", "type": "long", "logicalType": "timestamp-millis"}
371 ]
372 }
373 "#;
374 #[derive(Debug, Serialize, Deserialize, Clone)]
375 struct Test {
376 timestamp_millis_field: i64,
377 }
378 let value = Test {
379 timestamp_millis_field: 1697445291056i64,
380 };
381 generate_test_case(schema, value, "timestamp_millis")
382}
383
384fn generate_avro_test_case_timestamp_micros() -> Result<()> {
385 let schema = r#"
386 {
387 "type": "record",
388 "name": "test",
389 "fields": [
390 {"name": "timestamp_micros_field", "type": "long", "logicalType": "timestamp-micros"}
391 ]
392 }
393 "#;
394 #[derive(Debug, Serialize, Deserialize, Clone)]
395 struct Test {
396 timestamp_micros_field: i64,
397 }
398 let value = Test {
399 timestamp_micros_field: 1697445291056567i64,
400 };
401 generate_test_case(schema, value, "timestamp_micros")
402}
403
404fn generate_avro_test_case_local_timestamp_millis() -> Result<()> {
405 let schema = r#"
406 {
407 "type": "record",
408 "name": "test",
409 "fields": [
410 {"name": "local_timestamp_millis_field", "type": "long", "logicalType": "local-timestamp-millis"}
411 ]
412 }
413 "#;
414 #[derive(Debug, Serialize, Deserialize, Clone)]
415 struct Test {
416 local_timestamp_millis_field: i64,
417 }
418 let value = Test {
419 local_timestamp_millis_field: 1697445291056i64,
420 };
421 generate_test_case(schema, value, "local-timestamp_millis")
422}
423
424fn generate_avro_test_case_local_timestamp_micros() -> Result<()> {
425 let schema = r#"
426 {
427 "type": "record",
428 "name": "test",
429 "fields": [
430 {"name": "local_timestamp_micros_field", "type": "long", "logicalType": "local-timestamp-micros"}
431 ]
432 }
433 "#;
434 #[derive(Debug, Serialize, Deserialize, Clone)]
435 struct Test {
436 local_timestamp_micros_field: i64,
437 }
438 let value = Test {
439 local_timestamp_micros_field: 1697445291056567i64,
440 };
441 generate_test_case(schema, value, "local-timestamp_micros")
442}
443
444fn generate_avro_test_case_uuid() -> Result<()> {
445 let schema = r#"
446 {
447 "type": "record",
448 "name": "test",
449 "fields": [
450 {"name": "uuid_field", "type": "string",
451 "logicalType": "uuid"
452 }
453 ]
454 }
455 "#;
456 #[derive(Debug, Serialize, Deserialize, Clone)]
457 struct Test {
458 uuid_field: String,
459 }
460 let value = Test {
461 uuid_field: "550e8400-e29b-41d4-a716-446655440000".into(),
462 };
463 generate_test_case(schema, value, "uuid")
464}
465
466fn generate_test_case<S: Serialize>(schema: &str, value: S, filename: &str) -> Result<()> {
467 let value = apache_avro::to_value(value)?;
468 generate_test_case_from_value(schema, value, filename)
469}
470
471fn generate_test_case_from_value(schema: &str, value: Value, filename: &str) -> Result<()> {
472 let schema = Schema::parse_str(schema)?;
473
474 let value = value.resolve(&schema)?;
475 let bytes = apache_avro::to_avro_datum(&schema, value)?;
476
477 let mut schema_file = File::create(format!("{FIXTURES_PATH}/{filename}.avsc"))?;
478 let mut avro_file = File::create(format!("{FIXTURES_PATH}/{filename}.avro"))?;
479 schema_file.write_all(schema.canonical_form().as_bytes())?;
480 avro_file.write_all(&bytes)?;
481 Ok(())
482}
483
484fn main() -> Result<()> {
485 if !PathBuf::from(FIXTURES_PATH).is_dir() {
486 panic!("dir {FIXTURES_PATH} not exist\n");
487 }
488 generate_avro_test_case_array()?;
489 generate_avro_test_case_boolean()?;
490 generate_avro_test_case_bytes()?;
491 generate_avro_test_case_double()?;
492 generate_avro_test_case_enum()?;
493 generate_avro_test_case_float()?;
494 generate_avro_test_case_int()?;
495 generate_avro_test_case_long()?;
496 generate_avro_test_case_map()?;
497 generate_avro_test_case_record()?;
498 generate_avro_test_case_string()?;
499 generate_avro_test_case_time_micros()?;
500 generate_avro_test_case_timestamp_micros()?;
501 generate_avro_test_case_timestamp_millis()?;
502 generate_avro_test_case_local_timestamp_micros()?;
503 generate_avro_test_case_local_timestamp_millis()?;
504 generate_avro_test_case_union()?;
505 generate_avro_test_case_uuid()?;
506 Ok(())
507}