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}