1use bon::bon;
19use serde::Serialize;
20use std::io::Write;
21
22use crate::{
23 AvroResult, Schema,
24 encode::encode_internal,
25 error::Details,
26 schema::{NamesRef, ResolvedSchema},
27 serde::ser_schema::{Config, SchemaAwareSerializer},
28 types::Value,
29 util::is_human_readable,
30};
31
32pub struct GenericDatumWriter<'s> {
38 schema: &'s Schema,
39 resolved: ResolvedSchema<'s>,
40 validate: bool,
41 human_readable: bool,
42 target_block_size: Option<usize>,
43}
44
45#[bon]
46impl<'s> GenericDatumWriter<'s> {
47 #[builder]
49 pub fn new(
50 #[builder(start_fn)]
52 schema: &'s Schema,
53 resolved_schemata: Option<ResolvedSchema<'s>>,
57 #[builder(default = true)]
64 validate: bool,
65 target_block_size: Option<usize>,
73 #[builder(default = is_human_readable())]
77 human_readable: bool,
78 ) -> AvroResult<Self> {
79 let resolved = if let Some(resolved) = resolved_schemata {
80 resolved
81 } else {
82 ResolvedSchema::try_from(schema)?
83 };
84 Ok(Self {
85 schema,
86 resolved,
87 validate,
88 human_readable,
89 target_block_size,
90 })
91 }
92}
93
94impl<'s, S: generic_datum_writer_builder::State> GenericDatumWriterBuilder<'s, S> {
95 pub fn schemata(
100 self,
101 schemata: Vec<&'s Schema>,
102 ) -> AvroResult<
103 GenericDatumWriterBuilder<'s, generic_datum_writer_builder::SetResolvedSchemata<S>>,
104 >
105 where
106 S::ResolvedSchemata: generic_datum_writer_builder::IsUnset,
107 {
108 let resolved = ResolvedSchema::new_with_schemata(schemata)?;
109 Ok(self.resolved_schemata(resolved))
110 }
111}
112
113impl GenericDatumWriter<'_> {
114 pub fn write_value<W: Write, V: Into<Value>>(
116 &self,
117 writer: &mut W,
118 value: V,
119 ) -> AvroResult<usize> {
120 let value = value.into();
121 self.write_value_ref(writer, &value)
122 }
123
124 pub fn write_value_ref<W: Write>(&self, writer: &mut W, value: &Value) -> AvroResult<usize> {
126 if self.validate
127 && value
128 .validate_internal(self.schema, self.resolved.get_names(), None)
129 .is_some()
130 {
131 return Err(Details::Validation.into());
132 }
133 encode_internal(value, self.schema, self.resolved.get_names(), None, writer)
134 }
135
136 pub fn write_value_to_vec<V: Into<Value>>(&self, value: V) -> AvroResult<Vec<u8>> {
138 let mut vec = Vec::new();
139 self.write_value(&mut vec, value)?;
140 Ok(vec)
141 }
142
143 pub fn write_ser<W: Write, T: Serialize>(
145 &self,
146 writer: &mut W,
147 value: &T,
148 ) -> AvroResult<usize> {
149 let config = Config {
150 names: self.resolved.get_names(),
151 target_block_size: self.target_block_size,
152 human_readable: self.human_readable,
153 };
154 value.serialize(SchemaAwareSerializer::new(writer, self.schema, config)?)
155 }
156
157 pub fn write_ser_to_vec<T: Serialize>(&self, value: &T) -> AvroResult<Vec<u8>> {
159 let mut vec = Vec::new();
160 self.write_ser(&mut vec, value)?;
161 Ok(vec)
162 }
163}
164
165#[deprecated(since = "0.22.0", note = "Use GenericDatumWriter instead")]
182pub fn to_avro_datum<T: Into<Value>>(schema: &Schema, value: T) -> AvroResult<Vec<u8>> {
183 GenericDatumWriter::builder(schema)
184 .build()?
185 .write_value_to_vec(value)
186}
187
188pub fn write_avro_datum_ref<T: Serialize, W: Write>(
200 schema: &Schema,
201 names: &NamesRef,
202 data: &T,
203 writer: &mut W,
204) -> AvroResult<usize> {
205 let config = Config {
206 names,
207 target_block_size: None,
208 human_readable: is_human_readable(),
209 };
210 data.serialize(SchemaAwareSerializer::new(writer, schema, config)?)
211}
212
213#[deprecated(since = "0.22.0", note = "Use GenericDatumWriter instead")]
228pub fn to_avro_datum_schemata<T: Into<Value>>(
229 schema: &Schema,
230 schemata: Vec<&Schema>,
231 value: T,
232) -> AvroResult<Vec<u8>> {
233 GenericDatumWriter::builder(schema)
234 .schemata(schemata)?
235 .build()?
236 .write_value_to_vec(value)
237}
238
239#[cfg(test)]
240mod tests {
241 use apache_avro_test_helper::TestResult;
242 use std::num::NonZero;
243
244 use super::*;
245 use crate::reader::datum::GenericDatumReader;
246 use crate::{
247 Days, Decimal, Duration, Millis, Months,
248 schema::{DecimalSchema, FixedSchema, InnerDecimalSchema, Name},
249 types::Record,
250 util::zig_i64,
251 };
252
253 const SCHEMA: &str = r#"
254 {
255 "type": "record",
256 "name": "test",
257 "fields": [
258 {
259 "name": "a",
260 "type": "long",
261 "default": 42
262 },
263 {
264 "name": "b",
265 "type": "string"
266 }
267 ]
268 }
269 "#;
270
271 const UNION_SCHEMA: &str = r#"["null", "long"]"#;
272
273 #[test]
274 fn test_to_avro_datum() -> TestResult {
275 let schema = Schema::parse_str(SCHEMA)?;
276 let mut record = Record::new(&schema).unwrap();
277 record.put("a", 27i64);
278 record.put("b", "foo");
279
280 let mut expected = Vec::new();
281 zig_i64(27, &mut expected)?;
282 zig_i64(3, &mut expected)?;
283 expected.extend(b"foo");
284
285 let written = GenericDatumWriter::builder(&schema)
286 .build()?
287 .write_value_to_vec(record)?;
288
289 assert_eq!(written, expected);
290
291 Ok(())
292 }
293
294 #[test]
295 fn avro_rs_193_write_avro_datum_ref() -> TestResult {
296 #[derive(Serialize)]
297 struct TestStruct {
298 a: i64,
299 b: String,
300 }
301
302 let schema = Schema::parse_str(SCHEMA)?;
303 let mut writer: Vec<u8> = Vec::new();
304 let data = TestStruct {
305 a: 27,
306 b: "foo".to_string(),
307 };
308
309 let mut expected = Vec::new();
310 zig_i64(27, &mut expected)?;
311 zig_i64(3, &mut expected)?;
312 expected.extend(b"foo");
313
314 let bytes = GenericDatumWriter::builder(&schema)
315 .build()?
316 .write_ser(&mut writer, &data)?;
317
318 assert_eq!(bytes, expected.len());
319 assert_eq!(writer, expected);
320
321 Ok(())
322 }
323
324 #[test]
325 fn test_union_not_null() -> TestResult {
326 let schema = Schema::parse_str(UNION_SCHEMA)?;
327 let union = Value::Union(1, Box::new(Value::Long(3)));
328
329 let mut expected = Vec::new();
330 zig_i64(1, &mut expected)?;
331 zig_i64(3, &mut expected)?;
332
333 let written = GenericDatumWriter::builder(&schema)
334 .build()?
335 .write_value_to_vec(union)?;
336 assert_eq!(written, expected);
337
338 Ok(())
339 }
340
341 #[test]
342 fn test_union_null() -> TestResult {
343 let schema = Schema::parse_str(UNION_SCHEMA)?;
344 let union = Value::Union(0, Box::new(Value::Null));
345
346 let mut expected = Vec::new();
347 zig_i64(0, &mut expected)?;
348
349 let written = GenericDatumWriter::builder(&schema)
350 .build()?
351 .write_value_to_vec(union)?;
352 assert_eq!(written, expected);
353
354 Ok(())
355 }
356
357 #[expect(
358 clippy::needless_pass_by_value,
359 reason = "Value does not implement PartialEq<&Value>"
360 )]
361 fn logical_type_test<T: Into<Value> + Clone>(
362 schema_str: &'static str,
363
364 expected_schema: &Schema,
365 value: Value,
366
367 raw_schema: &Schema,
368 raw_value: T,
369 ) -> TestResult {
370 let schema = Schema::parse_str(schema_str)?;
371 assert_eq!(&schema, expected_schema);
372 let ser = GenericDatumWriter::builder(&schema)
374 .build()?
375 .write_value_to_vec(value.clone())?;
376 let raw_ser = GenericDatumWriter::builder(raw_schema)
377 .build()?
378 .write_value_to_vec(raw_value)?;
379 assert_eq!(ser, raw_ser);
380
381 let mut r = ser.as_slice();
383 let de = GenericDatumReader::builder(&schema)
384 .build()?
385 .read_value(&mut r)?;
386 assert_eq!(de, value);
387 Ok(())
388 }
389
390 #[test]
391 fn date() -> TestResult {
392 logical_type_test(
393 r#"{"type": "int", "logicalType": "date"}"#,
394 &Schema::Date,
395 Value::Date(1_i32),
396 &Schema::Int,
397 1_i32,
398 )
399 }
400
401 #[test]
402 fn time_millis() -> TestResult {
403 logical_type_test(
404 r#"{"type": "int", "logicalType": "time-millis"}"#,
405 &Schema::TimeMillis,
406 Value::TimeMillis(1_i32),
407 &Schema::Int,
408 1_i32,
409 )
410 }
411
412 #[test]
413 fn time_micros() -> TestResult {
414 logical_type_test(
415 r#"{"type": "long", "logicalType": "time-micros"}"#,
416 &Schema::TimeMicros,
417 Value::TimeMicros(1_i64),
418 &Schema::Long,
419 1_i64,
420 )
421 }
422
423 #[test]
424 fn timestamp_millis() -> TestResult {
425 logical_type_test(
426 r#"{"type": "long", "logicalType": "timestamp-millis"}"#,
427 &Schema::TimestampMillis,
428 Value::TimestampMillis(1_i64),
429 &Schema::Long,
430 1_i64,
431 )
432 }
433
434 #[test]
435 fn timestamp_micros() -> TestResult {
436 logical_type_test(
437 r#"{"type": "long", "logicalType": "timestamp-micros"}"#,
438 &Schema::TimestampMicros,
439 Value::TimestampMicros(1_i64),
440 &Schema::Long,
441 1_i64,
442 )
443 }
444
445 #[test]
446 fn decimal_fixed() -> TestResult {
447 let size = 30;
448 let fixed = FixedSchema {
449 name: Name::new("decimal")?,
450 aliases: None,
451 doc: None,
452 size,
453 attributes: Default::default(),
454 };
455 let inner = InnerDecimalSchema::Fixed(fixed.clone());
456 let value = vec![0u8; size];
457 logical_type_test(
458 r#"{"type": {"type": "fixed", "size": 30, "name": "decimal"}, "logicalType": "decimal", "precision": 20, "scale": 5}"#,
459 &Schema::Decimal(DecimalSchema {
460 precision: NonZero::new(20).unwrap(),
461 scale: 5,
462 inner,
463 }),
464 Value::Decimal(Decimal::from(value.clone())),
465 &Schema::Fixed(fixed),
466 Value::Fixed(size, value),
467 )
468 }
469
470 #[test]
471 fn decimal_bytes() -> TestResult {
472 let value = vec![0u8; 10];
473 logical_type_test(
474 r#"{"type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 3}"#,
475 &Schema::Decimal(DecimalSchema {
476 precision: NonZero::new(4).unwrap(),
477 scale: 3,
478 inner: InnerDecimalSchema::Bytes,
479 }),
480 Value::Decimal(Decimal::from(value.clone())),
481 &Schema::Bytes,
482 value,
483 )
484 }
485
486 #[test]
487 fn duration() -> TestResult {
488 let inner = Schema::Fixed(FixedSchema {
489 name: Name::new("duration")?,
490 aliases: None,
491 doc: None,
492 size: 12,
493 attributes: Default::default(),
494 });
495 let value = Value::Duration(Duration::new(
496 Months::new(256),
497 Days::new(512),
498 Millis::new(1024),
499 ));
500 logical_type_test(
501 r#"{"type": {"type": "fixed", "name": "duration", "size": 12}, "logicalType": "duration"}"#,
502 &Schema::Duration(FixedSchema {
503 name: Name::try_from("duration").expect("Name is valid"),
504 aliases: None,
505 doc: None,
506 size: 12,
507 attributes: Default::default(),
508 }),
509 value,
510 &inner,
511 Value::Fixed(12, vec![0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0]),
512 )
513 }
514}