apache_avro/serde/derive.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::Schema;
19use crate::schema::{
20 FixedSchema, Name, Namespace, RecordField, RecordSchema, UnionSchema, UuidSchema,
21};
22use std::borrow::Cow;
23use std::collections::{HashMap, HashSet};
24
25/// Trait for types that serve as an Avro data model.
26///
27/// **Do not implement directly!** Either derive it or implement [`AvroSchemaComponent`] to get this trait
28/// through a blanket implementation.
29///
30/// ## Deriving `AvroSchema`
31///
32/// Using the custom derive requires that you enable the `"derive"` cargo
33/// feature in your `Cargo.toml`:
34///
35/// ```toml
36/// [dependencies]
37/// apache-avro = { version = "..", features = ["derive"] }
38/// ```
39///
40/// Then, you add the `#[derive(AvroSchema)]` annotation to your `struct` and
41/// `enum` type definition:
42///
43/// ```
44/// # use serde::{Serialize, Deserialize};
45/// # use apache_avro::AvroSchema;
46/// #[derive(AvroSchema, Serialize, Deserialize)]
47/// pub struct Foo {
48/// bar: Vec<Bar>,
49/// }
50///
51/// #[derive(AvroSchema, Serialize, Deserialize)]
52/// pub enum Bar {
53/// Spam,
54/// Maps
55/// }
56/// ```
57///
58/// This will implement [`AvroSchemaComponent`] for the type, and `AvroSchema`
59/// through the blanket implementation for `T: AvroSchemaComponent`.
60///
61/// When deriving `struct`s, every member must also implement `AvroSchemaComponent`.
62///
63/// ## Changing the generated schema
64///
65/// The derive macro will read both the `avro` and `serde` attributes to modify the generated schema.
66/// It will also check for compatibility between the various attributes.
67///
68/// #### Container attributes
69///
70/// - `#[serde(rename = "name")]`
71///
72// TODO: Should we check if `name` contains any dots? As that would imply a namespace
73/// Set the `name` of the schema to the given string. Defaults to the name of the type.
74///
75/// - `#[avro(namespace = "some.name.space")]`
76///
77/// Set the `namespace` of the schema. This will be the relative namespace if the schema is included
78/// in another schema.
79///
80/// - `#[avro(doc = "Some documentation")]`
81///
82/// Set the `doc` attribute of the schema. Defaults to the documentation of the type.
83///
84/// - `#[avro(alias = "name")]`
85///
86/// Set the `alias` attribute of the schema. Can be specified multiple times.
87///
88/// - `#[serde(rename_all = "camelCase")]`
89///
90/// Rename all the fields or variants in the schema to follow the given case convention. The possible values
91/// are `"lowercase"`, `"UPPERCASE"`, `"PascalCase"`, `"camelCase"`, `"snake_case"`, `"kebab-case"`,
92/// `"SCREAMING_SNAKE_CASE"`, `"SCREAMING-KEBAB-CASE"`.
93///
94/// - `#[serde(transparent)]`
95///
96/// Use the schema of the inner field directly. Is only allowed on structs with only one unskipped field.
97///
98///
99/// #### Variant attributes
100///
101/// - `#[serde(rename = "name")]`
102///
103/// Rename the variant to the given name.
104///
105///
106/// #### Field attributes
107///
108/// - `#[serde(rename = "name")]`
109///
110/// Rename the field name to the given name.
111///
112/// - `#[avro(doc = "Some documentation")]`
113///
114/// Set the `doc` attribute of the field. Defaults to the documentation of the field.
115///
116/// - `#[avro(default = "null")]`
117///
118/// Set the `default` attribute of the field.
119///
120/// _Note:_ This is a JSON value not a Rust value, as this is put in the schema itself.
121///
122/// - `#[serde(alias = "name")]`
123///
124/// Set the `alias` attribute of the field. Can be specified multiple times.
125///
126/// - `#[serde(flatten)]`
127///
128/// Flatten the content of this field into the container it is defined in.
129///
130/// - `#[serde(skip)]`
131///
132/// Do not include this field in the schema.
133///
134/// - `#[serde(skip_serializing)]`
135///
136/// When combined with `#[serde(skip_deserializing)]`, don't include this field in the schema.
137/// Otherwise, it will be included in the schema and the `#[avro(default)]` attribute **must** be
138/// set. That value will be used for serializing.
139///
140/// - `#[serde(skip_serializing_if)]`
141///
142/// Conditionally use the value of the field or the value provided by `#[avro(default)]`. The
143/// `#[avro(default)]` attribute **must** be set.
144///
145/// - `#[avro(with)]` and `#[serde(with = "module")]`
146///
147/// Override the schema used for this field. See [Working with foreign types](#working-with-foreign-types).
148///
149/// #### Incompatible Serde attributes
150///
151/// The derive macro is compatible with most Serde attributes, but it is incompatible with
152/// the following attributes:
153///
154/// - Container attributes
155/// - `tag`
156/// - `content`
157/// - `untagged`
158/// - `variant_identifier`
159/// - `field_identifier`
160/// - `remote`
161/// - `rename_all(serialize = "..", deserialize = "..")` where `serialize` != `deserialize`
162/// - Variant attributes
163/// - `other`
164/// - `untagged`
165/// - Field attributes
166/// - `getter`
167///
168/// ## Working with foreign types
169///
170/// Most foreign types won't have a [`AvroSchema`] implementation. This crate implements it only
171/// for built-in types and [`uuid::Uuid`].
172///
173/// To still be able to derive schemas for fields of foreign types, the `#[avro(with)`]
174/// attribute can be used to get the schema for those fields. It can be used in two ways:
175///
176/// 1. In combination with `#[serde(with = "path::to::module)]`
177///
178/// To get the schema, it will call the functions `fn get_schema_in_ctxt(&mut HashSet<Name>, &Namespace) -> Schema`
179/// and `fn get_record_fields_in_ctxt(usize, &mut HashSet<Name>, &Namespace) -> Option<Vec<RecordField>>` in the module provided
180/// to the Serde attribute. See [`AvroSchemaComponent`] for details on how to implement those
181/// functions.
182///
183/// 2. By providing a function directly, `#[avro(with = some_fn)]`.
184///
185/// To get the schema, it will call the function provided. It must have the signature
186/// `fn(&mut HashSet<Name>, &Namespace) -> Schema`. When this is used for a `transparent` struct, the
187/// default implementation of [`AvroSchemaComponent::get_record_fields_in_ctxt`] will be used.
188/// This is only recommended for primitive types, as the default implementation cannot be efficiently
189/// implemented for complex types.
190///
191pub trait AvroSchema {
192 /// Construct the full schema that represents this type.
193 ///
194 /// The returned schema is fully independent and contains only `Schema::Ref` to named types defined
195 /// earlier in the schema.
196 fn get_schema() -> Schema;
197}
198
199/// Trait for types that serve as fully defined components inside an Avro data model.
200///
201/// This trait can be derived with [`#[derive(AvroSchema)]`](AvroSchema) when the `derive` feature is enabled.
202///
203/// # Implementation guide
204///
205/// ### Implementation for returning primitive types
206/// When the schema you want to return is a primitive type (a type without a name), the function
207/// arguments can be ignored.
208///
209/// For example, you have a custom integer type:
210/// ```
211/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField}};
212/// # use std::collections::HashSet;
213/// // Make sure to implement `Serialize` and `Deserialize` to use the right serialization methods
214/// pub struct U24([u8; 3]);
215/// impl AvroSchemaComponent for U24 {
216/// fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
217/// Schema::Int
218/// }
219///
220/// fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
221/// None // A Schema::Int is not a Schema::Record so there are no fields to return
222/// }
223///}
224/// ```
225///
226/// ### Passthrough implementation
227///
228/// To construct a schema for a type is "transparent", such as for smart pointers, simply
229/// pass through the arguments to the inner type:
230/// ```
231/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField}};
232/// # use serde::{Serialize, Deserialize};
233/// # use std::collections::HashSet;
234/// #[derive(Serialize, Deserialize)]
235/// #[serde(transparent)] // This attribute is important for all passthrough implementations!
236/// pub struct Transparent<T>(T);
237/// impl<T: AvroSchemaComponent> AvroSchemaComponent for Transparent<T> {
238/// fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
239/// T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
240/// }
241///
242/// fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
243/// T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace)
244/// }
245///}
246/// ```
247///
248/// ### Implementation for complex types
249/// When the schema you want to return is a complex type (a type with a name), special care has to
250/// be taken to avoid duplicate type definitions and getting the correct namespace.
251///
252/// Things to keep in mind:
253/// - If the fully qualified name already exists, return a [`Schema::Ref`]
254/// - Use the `AvroSchemaComponent` implementations to get the schemas for the subtypes
255/// - The ordering of fields in the schema **must** match with the ordering in Serde
256/// - Implement `get_record_fields_in_ctxt` as the default implementation has to be implemented
257/// with backtracking and a lot of cloning.
258/// - Even if your schema is not a record, still implement the function and just return `None`
259///
260/// ```
261/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField, RecordSchema}};
262/// # use serde::{Serialize, Deserialize};
263/// # use std::{time::Duration, collections::HashSet};
264/// pub struct Foo {
265/// one: String,
266/// two: i32,
267/// three: Option<Duration>
268/// }
269///
270/// impl AvroSchemaComponent for Foo {
271/// fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
272/// // Create the fully qualified name for your type given the enclosing namespace
273/// let name = Name::new("Foo").unwrap().fully_qualified_name(enclosing_namespace);
274/// if named_schemas.contains(&name) {
275/// Schema::Ref { name }
276/// } else {
277/// let enclosing_namespace = &name.namespace;
278/// // Do this before you start creating the schema, as otherwise recursive types will cause infinite recursion.
279/// named_schemas.insert(name.clone());
280/// let schema = Schema::Record(RecordSchema::builder()
281/// .name(name.clone())
282/// .fields(Self::get_record_fields_in_ctxt(0, named_schemas, enclosing_namespace).expect("Impossible!"))
283/// .build()
284/// );
285/// schema
286/// }
287/// }
288///
289/// fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
290/// Some(vec![
291/// RecordField::builder()
292/// .name("one")
293/// .schema(String::get_schema_in_ctxt(named_schemas, enclosing_namespace))
294/// .position(first_field_position)
295/// .build(),
296/// RecordField::builder()
297/// .name("two")
298/// .schema(i32::get_schema_in_ctxt(named_schemas, enclosing_namespace))
299/// .position(first_field_position+1)
300/// .build(),
301/// RecordField::builder()
302/// .name("three")
303/// .schema(<Option<Duration>>::get_schema_in_ctxt(named_schemas, enclosing_namespace))
304/// .position(first_field_position+2)
305/// .build(),
306/// ])
307/// }
308///}
309/// ```
310pub trait AvroSchemaComponent {
311 /// Get the schema for this component
312 fn get_schema_in_ctxt(
313 named_schemas: &mut HashSet<Name>,
314 enclosing_namespace: &Namespace,
315 ) -> Schema;
316
317 /// Get the fields of this schema if it is a record.
318 ///
319 /// This returns `None` if the schema is not a record.
320 ///
321 /// The default implementation has to do a lot of extra work, so it is strongly recommended to
322 /// implement this function when manually implementing this trait.
323 fn get_record_fields_in_ctxt(
324 first_field_position: usize,
325 named_schemas: &mut HashSet<Name>,
326 enclosing_namespace: &Namespace,
327 ) -> Option<Vec<RecordField>> {
328 get_record_fields_in_ctxt(
329 first_field_position,
330 named_schemas,
331 enclosing_namespace,
332 Self::get_schema_in_ctxt,
333 )
334 }
335}
336
337/// Get the record fields from `schema_fn` without polluting `named_schemas` or causing duplicate names
338///
339/// This is public so the derive macro can use it for `#[avro(with = ||)]` and `#[avro(with = path)]`
340pub fn get_record_fields_in_ctxt(
341 first_field_position: usize,
342 named_schemas: &mut HashSet<Name>,
343 enclosing_namespace: &Namespace,
344 schema_fn: fn(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema,
345) -> Option<Vec<RecordField>> {
346 let mut record = match schema_fn(named_schemas, enclosing_namespace) {
347 Schema::Record(record) => record,
348 Schema::Ref { name } => {
349 // This schema already exists in `named_schemas` so temporarily remove it so we can
350 // get the actual schema.
351 assert!(
352 named_schemas.remove(&name),
353 "Name '{name}' should exist in `named_schemas` otherwise Ref is invalid: {named_schemas:?}"
354 );
355 // Get the schema
356 let schema = schema_fn(named_schemas, enclosing_namespace);
357 // Reinsert the old value
358 named_schemas.insert(name);
359
360 // Now check if we actually got a record and return the fields if that is the case
361 let Schema::Record(record) = schema else {
362 return None;
363 };
364 let fields = record
365 .fields
366 .into_iter()
367 .map(|mut f| {
368 f.position += first_field_position;
369 f
370 })
371 .collect();
372 return Some(fields);
373 }
374 _ => return None,
375 };
376 // This schema did not yet exist in `named_schemas`, so we need to remove it if and only if
377 // it isn't used somewhere in the schema (recursive type).
378
379 // Find the first Schema::Ref that has the target name
380 fn find_first_ref<'a>(schema: &'a mut Schema, target: &Name) -> Option<&'a mut Schema> {
381 match schema {
382 Schema::Ref { name } if name == target => Some(schema),
383 Schema::Array(array) => find_first_ref(&mut array.items, target),
384 Schema::Map(map) => find_first_ref(&mut map.types, target),
385 Schema::Union(union) => {
386 for schema in &mut union.schemas {
387 if let Some(schema) = find_first_ref(schema, target) {
388 return Some(schema);
389 }
390 }
391 None
392 }
393 Schema::Record(record) => {
394 assert_ne!(
395 &record.name, target,
396 "Only expecting a Ref named {target:?}"
397 );
398 for field in &mut record.fields {
399 if let Some(schema) = find_first_ref(&mut field.schema, target) {
400 return Some(schema);
401 }
402 }
403 None
404 }
405 _ => None,
406 }
407 }
408
409 // Prepare the fields for the new record. All named types will become references.
410 let new_fields = record
411 .fields
412 .iter()
413 .map(|field| RecordField {
414 name: field.name.clone(),
415 doc: field.doc.clone(),
416 aliases: field.aliases.clone(),
417 default: field.default.clone(),
418 schema: if field.schema.is_named() {
419 Schema::Ref {
420 name: field.schema.name().expect("Schema is named").clone(),
421 }
422 } else {
423 field.schema.clone()
424 },
425 order: field.order.clone(),
426 position: field.position,
427 custom_attributes: field.custom_attributes.clone(),
428 })
429 .collect();
430
431 // Remove the name in case it is not used
432 named_schemas.remove(&record.name);
433
434 // Find the first reference to this schema so we can replace it with the actual schema
435 for field in &mut record.fields {
436 if let Some(schema) = find_first_ref(&mut field.schema, &record.name) {
437 let new_schema = RecordSchema {
438 name: record.name,
439 aliases: record.aliases,
440 doc: record.doc,
441 fields: new_fields,
442 lookup: record.lookup,
443 attributes: record.attributes,
444 };
445
446 let name = match std::mem::replace(schema, Schema::Record(new_schema)) {
447 Schema::Ref { name } => name,
448 schema => {
449 panic!("Only expected `Schema::Ref` from `find_first_ref`, got: {schema:?}")
450 }
451 };
452
453 // The schema is used, so reinsert it
454 named_schemas.insert(name.clone());
455
456 break;
457 }
458 }
459
460 let fields = record
461 .fields
462 .into_iter()
463 .map(|mut f| {
464 f.position += first_field_position;
465 f
466 })
467 .collect();
468 Some(fields)
469}
470
471impl<T> AvroSchema for T
472where
473 T: AvroSchemaComponent + ?Sized,
474{
475 fn get_schema() -> Schema {
476 T::get_schema_in_ctxt(&mut HashSet::default(), &None)
477 }
478}
479
480macro_rules! impl_schema (
481 ($type:ty, $variant_constructor:expr) => (
482 impl AvroSchemaComponent for $type {
483 fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
484 $variant_constructor
485 }
486
487 fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
488 None
489 }
490 }
491 );
492);
493
494impl_schema!(bool, Schema::Boolean);
495impl_schema!(i8, Schema::Int);
496impl_schema!(i16, Schema::Int);
497impl_schema!(i32, Schema::Int);
498impl_schema!(i64, Schema::Long);
499impl_schema!(u8, Schema::Int);
500impl_schema!(u16, Schema::Int);
501impl_schema!(u32, Schema::Long);
502impl_schema!(f32, Schema::Float);
503impl_schema!(f64, Schema::Double);
504impl_schema!(String, Schema::String);
505impl_schema!(str, Schema::String);
506impl_schema!(char, Schema::String);
507
508macro_rules! impl_passthrough_schema (
509 ($type:ty where T: AvroSchemaComponent + ?Sized $(+ $bound:tt)*) => (
510 impl<T: AvroSchemaComponent $(+ $bound)* + ?Sized> AvroSchemaComponent for $type {
511 fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
512 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
513 }
514
515 fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
516 T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace)
517 }
518 }
519 );
520);
521
522impl_passthrough_schema!(&T where T: AvroSchemaComponent + ?Sized);
523impl_passthrough_schema!(&mut T where T: AvroSchemaComponent + ?Sized);
524impl_passthrough_schema!(Box<T> where T: AvroSchemaComponent + ?Sized);
525impl_passthrough_schema!(Cow<'_, T> where T: AvroSchemaComponent + ?Sized + ToOwned);
526impl_passthrough_schema!(std::sync::Mutex<T> where T: AvroSchemaComponent + ?Sized);
527
528macro_rules! impl_array_schema (
529 ($type:ty where T: AvroSchemaComponent) => (
530 impl<T: AvroSchemaComponent> AvroSchemaComponent for $type {
531 fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
532 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
533 }
534
535 fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
536 None
537 }
538 }
539 );
540);
541
542impl_array_schema!([T] where T: AvroSchemaComponent);
543impl_array_schema!(Vec<T> where T: AvroSchemaComponent);
544// This doesn't work as the macro doesn't allow specifying the N parameter
545// impl_array_schema!([T; N] where T: AvroSchemaComponent);
546
547impl<const N: usize, T> AvroSchemaComponent for [T; N]
548where
549 T: AvroSchemaComponent,
550{
551 fn get_schema_in_ctxt(
552 named_schemas: &mut HashSet<Name>,
553 enclosing_namespace: &Namespace,
554 ) -> Schema {
555 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
556 }
557
558 fn get_record_fields_in_ctxt(
559 _: usize,
560 _: &mut HashSet<Name>,
561 _: &Namespace,
562 ) -> Option<Vec<RecordField>> {
563 None
564 }
565}
566
567impl<T> AvroSchemaComponent for HashMap<String, T>
568where
569 T: AvroSchemaComponent,
570{
571 fn get_schema_in_ctxt(
572 named_schemas: &mut HashSet<Name>,
573 enclosing_namespace: &Namespace,
574 ) -> Schema {
575 Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
576 }
577
578 fn get_record_fields_in_ctxt(
579 _: usize,
580 _: &mut HashSet<Name>,
581 _: &Namespace,
582 ) -> Option<Vec<RecordField>> {
583 None
584 }
585}
586
587impl<T> AvroSchemaComponent for Option<T>
588where
589 T: AvroSchemaComponent,
590{
591 fn get_schema_in_ctxt(
592 named_schemas: &mut HashSet<Name>,
593 enclosing_namespace: &Namespace,
594 ) -> Schema {
595 let variants = vec![
596 Schema::Null,
597 T::get_schema_in_ctxt(named_schemas, enclosing_namespace),
598 ];
599
600 Schema::Union(
601 UnionSchema::new(variants).expect("Option<T> must produce a valid (non-nested) union"),
602 )
603 }
604
605 fn get_record_fields_in_ctxt(
606 _: usize,
607 _: &mut HashSet<Name>,
608 _: &Namespace,
609 ) -> Option<Vec<RecordField>> {
610 None
611 }
612}
613
614impl AvroSchemaComponent for core::time::Duration {
615 /// The schema is [`Schema::Duration`] with the name `duration`.
616 ///
617 /// This is a lossy conversion as this Avro type does not store the amount of nanoseconds.
618 fn get_schema_in_ctxt(
619 named_schemas: &mut HashSet<Name>,
620 enclosing_namespace: &Namespace,
621 ) -> Schema {
622 let name = Name::new("duration")
623 .expect("Name is valid")
624 .fully_qualified_name(enclosing_namespace);
625 if named_schemas.contains(&name) {
626 Schema::Ref { name }
627 } else {
628 let schema = Schema::Duration(FixedSchema {
629 name: name.clone(),
630 aliases: None,
631 doc: None,
632 size: 12,
633 attributes: Default::default(),
634 });
635 named_schemas.insert(name);
636 schema
637 }
638 }
639
640 fn get_record_fields_in_ctxt(
641 _: usize,
642 _: &mut HashSet<Name>,
643 _: &Namespace,
644 ) -> Option<Vec<RecordField>> {
645 None
646 }
647}
648
649impl AvroSchemaComponent for uuid::Uuid {
650 /// The schema is [`Schema::Uuid`] with the name `uuid`.
651 ///
652 /// The underlying schema is [`Schema::Fixed`] with a size of 16.
653 fn get_schema_in_ctxt(
654 named_schemas: &mut HashSet<Name>,
655 enclosing_namespace: &Namespace,
656 ) -> Schema {
657 let name = Name::new("uuid")
658 .expect("Name is valid")
659 .fully_qualified_name(enclosing_namespace);
660 if named_schemas.contains(&name) {
661 Schema::Ref { name }
662 } else {
663 let schema = Schema::Uuid(UuidSchema::Fixed(FixedSchema {
664 name: name.clone(),
665 aliases: None,
666 doc: None,
667 size: 16,
668 attributes: Default::default(),
669 }));
670 named_schemas.insert(name);
671 schema
672 }
673 }
674
675 fn get_record_fields_in_ctxt(
676 _: usize,
677 _: &mut HashSet<Name>,
678 _: &Namespace,
679 ) -> Option<Vec<RecordField>> {
680 None
681 }
682}
683
684impl AvroSchemaComponent for u64 {
685 /// The schema is [`Schema::Fixed`] of size 8 with the name `u64`.
686 fn get_schema_in_ctxt(
687 named_schemas: &mut HashSet<Name>,
688 enclosing_namespace: &Namespace,
689 ) -> Schema {
690 let name = Name::new("u64")
691 .expect("Name is valid")
692 .fully_qualified_name(enclosing_namespace);
693 if named_schemas.contains(&name) {
694 Schema::Ref { name }
695 } else {
696 let schema = Schema::Fixed(FixedSchema {
697 name: name.clone(),
698 aliases: None,
699 doc: None,
700 size: 8,
701 attributes: Default::default(),
702 });
703 named_schemas.insert(name);
704 schema
705 }
706 }
707
708 fn get_record_fields_in_ctxt(
709 _: usize,
710 _: &mut HashSet<Name>,
711 _: &Namespace,
712 ) -> Option<Vec<RecordField>> {
713 None
714 }
715}
716
717impl AvroSchemaComponent for u128 {
718 /// The schema is [`Schema::Fixed`] of size 16 with the name `u128`.
719 fn get_schema_in_ctxt(
720 named_schemas: &mut HashSet<Name>,
721 enclosing_namespace: &Namespace,
722 ) -> Schema {
723 let name = Name::new("u128")
724 .expect("Name is valid")
725 .fully_qualified_name(enclosing_namespace);
726 if named_schemas.contains(&name) {
727 Schema::Ref { name }
728 } else {
729 let schema = Schema::Fixed(FixedSchema {
730 name: name.clone(),
731 aliases: None,
732 doc: None,
733 size: 16,
734 attributes: Default::default(),
735 });
736 named_schemas.insert(name);
737 schema
738 }
739 }
740
741 fn get_record_fields_in_ctxt(
742 _: usize,
743 _: &mut HashSet<Name>,
744 _: &Namespace,
745 ) -> Option<Vec<RecordField>> {
746 None
747 }
748}
749
750impl AvroSchemaComponent for i128 {
751 /// The schema is [`Schema::Fixed`] of size 16 with the name `i128`.
752 fn get_schema_in_ctxt(
753 named_schemas: &mut HashSet<Name>,
754 enclosing_namespace: &Namespace,
755 ) -> Schema {
756 let name = Name::new("i128")
757 .expect("Name is valid")
758 .fully_qualified_name(enclosing_namespace);
759 if named_schemas.contains(&name) {
760 Schema::Ref { name }
761 } else {
762 let schema = Schema::Fixed(FixedSchema {
763 name: name.clone(),
764 aliases: None,
765 doc: None,
766 size: 16,
767 attributes: Default::default(),
768 });
769 named_schemas.insert(name);
770 schema
771 }
772 }
773
774 fn get_record_fields_in_ctxt(
775 _: usize,
776 _: &mut HashSet<Name>,
777 _: &Namespace,
778 ) -> Option<Vec<RecordField>> {
779 None
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use crate::schema::{FixedSchema, Name};
786 use crate::{AvroSchema, Schema};
787 use apache_avro_test_helper::TestResult;
788
789 #[test]
790 fn avro_rs_401_str() -> TestResult {
791 let schema = str::get_schema();
792 assert_eq!(schema, Schema::String);
793
794 Ok(())
795 }
796
797 #[test]
798 fn avro_rs_401_references() -> TestResult {
799 let schema_ref = <&str>::get_schema();
800 let schema_ref_mut = <&mut str>::get_schema();
801
802 assert_eq!(schema_ref, Schema::String);
803 assert_eq!(schema_ref_mut, Schema::String);
804
805 Ok(())
806 }
807
808 #[test]
809 fn avro_rs_401_slice() -> TestResult {
810 let schema = <[u8]>::get_schema();
811 assert_eq!(schema, Schema::array(Schema::Int).build());
812
813 Ok(())
814 }
815
816 #[test]
817 fn avro_rs_401_array() -> TestResult {
818 let schema = <[u8; 55]>::get_schema();
819 assert_eq!(schema, Schema::array(Schema::Int).build());
820
821 Ok(())
822 }
823
824 #[test]
825 fn avro_rs_401_option_ref_slice_array() -> TestResult {
826 let schema = <Option<&[[u8; 55]]>>::get_schema();
827 assert_eq!(
828 schema,
829 Schema::union(vec![
830 Schema::Null,
831 Schema::array(Schema::array(Schema::Int).build()).build()
832 ])?
833 );
834
835 Ok(())
836 }
837
838 #[test]
839 fn avro_rs_414_char() -> TestResult {
840 let schema = char::get_schema();
841 assert_eq!(schema, Schema::String);
842
843 Ok(())
844 }
845
846 #[test]
847 fn avro_rs_414_u64() -> TestResult {
848 let schema = u64::get_schema();
849 assert_eq!(
850 schema,
851 Schema::Fixed(FixedSchema {
852 name: Name::new("u64")?,
853 aliases: None,
854 doc: None,
855 size: 8,
856 attributes: Default::default(),
857 })
858 );
859
860 Ok(())
861 }
862
863 #[test]
864 fn avro_rs_414_i128() -> TestResult {
865 let schema = i128::get_schema();
866 assert_eq!(
867 schema,
868 Schema::Fixed(FixedSchema {
869 name: Name::new("i128")?,
870 aliases: None,
871 doc: None,
872 size: 16,
873 attributes: Default::default(),
874 })
875 );
876
877 Ok(())
878 }
879
880 #[test]
881 fn avro_rs_414_u128() -> TestResult {
882 let schema = u128::get_schema();
883 assert_eq!(
884 schema,
885 Schema::Fixed(FixedSchema {
886 name: Name::new("u128")?,
887 aliases: None,
888 doc: None,
889 size: 16,
890 attributes: Default::default(),
891 })
892 );
893
894 Ok(())
895 }
896}