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