apache_avro/schema/
resolve.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::error::Details;
19use crate::schema::{
20    DecimalSchema, EnumSchema, FixedSchema, InnerDecimalSchema, NamesRef, NamespaceRef,
21    RecordSchema, UnionSchema, UuidSchema,
22};
23use crate::{AvroResult, Error, Schema};
24use std::collections::HashMap;
25
26#[derive(Debug, Clone)]
27pub struct ResolvedSchema<'s> {
28    pub(super) names_ref: NamesRef<'s>,
29    schemata: Vec<&'s Schema>,
30}
31
32impl<'s> ResolvedSchema<'s> {
33    pub fn get_schemata(&self) -> &[&'s Schema] {
34        &self.schemata
35    }
36
37    pub fn get_names(&self) -> &NamesRef<'s> {
38        &self.names_ref
39    }
40
41    /// Resolve all references in this schema.
42    ///
43    /// If some references are to other schemas, see [`ResolvedSchema::new_with_schemata`].
44    pub fn new(schema: &'s Schema) -> AvroResult<Self> {
45        Self::new_with_schemata(vec![schema])
46    }
47
48    /// Resolve all references in these schemas.
49    ///
50    // TODO: Support this
51    /// These schemas will be resolved in order, so references to schemas later in the
52    /// list is not supported.
53    pub fn new_with_schemata(schemata: Vec<&'s Schema>) -> AvroResult<Self> {
54        Self::new_with_known_schemata(schemata, None, &HashMap::new())
55    }
56
57    /// Creates `ResolvedSchema` with some already known schemas.
58    ///
59    /// Those schemata would be used to resolve references if needed.
60    pub fn new_with_known_schemata<'n>(
61        schemata_to_resolve: Vec<&'s Schema>,
62        enclosing_namespace: NamespaceRef,
63        known_schemata: &'n NamesRef<'n>,
64    ) -> AvroResult<Self> {
65        let mut names = HashMap::new();
66        resolve_names_with_schemata(
67            schemata_to_resolve.iter().copied(),
68            &mut names,
69            enclosing_namespace,
70            known_schemata,
71        )?;
72        Ok(ResolvedSchema {
73            names_ref: names,
74            schemata: schemata_to_resolve,
75        })
76    }
77}
78
79impl<'s> TryFrom<&'s Schema> for ResolvedSchema<'s> {
80    type Error = Error;
81
82    fn try_from(schema: &'s Schema) -> AvroResult<Self> {
83        Self::new(schema)
84    }
85}
86
87impl<'s> TryFrom<Vec<&'s Schema>> for ResolvedSchema<'s> {
88    type Error = Error;
89
90    fn try_from(schemata: Vec<&'s Schema>) -> AvroResult<Self> {
91        Self::new_with_schemata(schemata)
92    }
93}
94
95/// Implementation detail of [`ResolvedOwnedSchema`]
96///
97/// This struct is self-referencing. The references in `names` point to `root_schema`.
98/// This allows resolving an owned schema without having to clone all the named schemas.
99#[ouroboros::self_referencing]
100struct InnerResolvedOwnedSchema {
101    root_schema: Schema,
102    #[borrows(root_schema)]
103    #[covariant]
104    names: NamesRef<'this>,
105}
106
107/// A variant of [`ResolvedSchema`] that owns the schema
108pub struct ResolvedOwnedSchema {
109    inner: InnerResolvedOwnedSchema,
110}
111
112impl ResolvedOwnedSchema {
113    pub fn new(root_schema: Schema) -> AvroResult<Self> {
114        Ok(Self {
115            inner: InnerResolvedOwnedSchemaTryBuilder {
116                root_schema,
117                names_builder: |schema: &Schema| {
118                    let mut names = HashMap::new();
119                    resolve_names(schema, &mut names, None, &HashMap::new())?;
120                    Ok::<_, Error>(names)
121                },
122            }
123            .try_build()?,
124        })
125    }
126
127    pub fn get_root_schema(&self) -> &Schema {
128        self.inner.borrow_root_schema()
129    }
130    pub fn get_names(&self) -> &NamesRef<'_> {
131        self.inner.borrow_names()
132    }
133}
134
135impl TryFrom<Schema> for ResolvedOwnedSchema {
136    type Error = Error;
137
138    fn try_from(schema: Schema) -> AvroResult<Self> {
139        Self::new(schema)
140    }
141}
142
143/// Resolve all references in the schema, saving any named type found in `names`
144///
145/// `known_schemata` will be used to resolve references but they won't be added to `names`.
146pub fn resolve_names<'s, 'n>(
147    schema: &'s Schema,
148    names: &mut NamesRef<'s>,
149    enclosing_namespace: NamespaceRef,
150    known_schemata: &NamesRef<'n>,
151) -> AvroResult<()> {
152    match schema {
153        Schema::Array(schema) => {
154            resolve_names(&schema.items, names, enclosing_namespace, known_schemata)
155        }
156        Schema::Map(schema) => {
157            resolve_names(&schema.types, names, enclosing_namespace, known_schemata)
158        }
159        Schema::Union(UnionSchema { schemas, .. }) => {
160            for schema in schemas {
161                resolve_names(schema, names, enclosing_namespace, known_schemata)?
162            }
163            Ok(())
164        }
165        Schema::Enum(EnumSchema { name, .. })
166        | Schema::Fixed(FixedSchema { name, .. })
167        | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
168        | Schema::Decimal(DecimalSchema {
169            inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
170            ..
171        })
172        | Schema::Duration(FixedSchema { name, .. }) => {
173            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace).into_owned();
174            if names.contains_key(&fully_qualified_name)
175                || known_schemata.contains_key(&fully_qualified_name)
176            {
177                Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into())
178            } else {
179                names.insert(fully_qualified_name, schema);
180                Ok(())
181            }
182        }
183        Schema::Record(RecordSchema { name, fields, .. }) => {
184            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace).into_owned();
185            if names.contains_key(&fully_qualified_name)
186                || known_schemata.contains_key(&fully_qualified_name)
187            {
188                Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into())
189            } else {
190                let record_namespace = fully_qualified_name.namespace().map(ToString::to_string);
191                names.insert(fully_qualified_name, schema);
192                for field in fields {
193                    resolve_names(
194                        &field.schema,
195                        names,
196                        record_namespace.as_deref(),
197                        known_schemata,
198                    )?
199                }
200                Ok(())
201            }
202        }
203        Schema::Ref { name } => {
204            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
205            if names.contains_key(&fully_qualified_name)
206                || known_schemata.contains_key(&fully_qualified_name)
207            {
208                Ok(())
209            } else {
210                Err(Details::SchemaResolutionError(fully_qualified_name.into_owned()).into())
211            }
212        }
213        _ => Ok(()),
214    }
215}
216
217pub fn resolve_names_with_schemata<'s, 'n>(
218    schemata: impl IntoIterator<Item = &'s Schema>,
219    names: &mut NamesRef<'s>,
220    enclosing_namespace: NamespaceRef,
221    known_schemata: &NamesRef<'n>,
222) -> AvroResult<()> {
223    for schema in schemata {
224        resolve_names(schema, names, enclosing_namespace, known_schemata)?;
225    }
226    Ok(())
227}
228
229#[cfg(test)]
230mod tests {
231    use super::{ResolvedOwnedSchema, ResolvedSchema};
232    use crate::{
233        Schema,
234        schema::{Name, NamesRef},
235    };
236    use apache_avro_test_helper::TestResult;
237    use std::collections::HashMap;
238
239    #[test]
240    fn avro_3448_test_proper_resolution_inner_record_inherited_namespace() -> TestResult {
241        let schema = r#"
242        {
243          "name": "record_name",
244          "namespace": "space",
245          "type": "record",
246          "fields": [
247            {
248              "name": "outer_field_1",
249              "type": [
250                        "null",
251                        {
252                            "type":"record",
253                            "name":"inner_record_name",
254                            "fields":[
255                                {
256                                    "name":"inner_field_1",
257                                    "type":"double"
258                                }
259                            ]
260                        }
261                    ]
262            },
263            {
264                "name": "outer_field_2",
265                "type" : "inner_record_name"
266            }
267          ]
268        }
269        "#;
270        let schema = Schema::parse_str(schema)?;
271        let rs = ResolvedSchema::new(&schema)?;
272        assert_eq!(rs.get_names().len(), 2);
273        for s in ["space.record_name", "space.inner_record_name"] {
274            assert!(rs.get_names().contains_key(&Name::new(s)?));
275        }
276
277        Ok(())
278    }
279
280    #[test]
281    fn avro_3448_test_proper_resolution_inner_record_qualified_namespace() -> TestResult {
282        let schema = r#"
283        {
284          "name": "record_name",
285          "namespace": "space",
286          "type": "record",
287          "fields": [
288            {
289              "name": "outer_field_1",
290              "type": [
291                        "null",
292                        {
293                            "type":"record",
294                            "name":"inner_record_name",
295                            "fields":[
296                                {
297                                    "name":"inner_field_1",
298                                    "type":"double"
299                                }
300                            ]
301                        }
302                    ]
303            },
304            {
305                "name": "outer_field_2",
306                "type" : "space.inner_record_name"
307            }
308          ]
309        }
310        "#;
311        let schema = Schema::parse_str(schema)?;
312        let rs = ResolvedSchema::new(&schema)?;
313        assert_eq!(rs.get_names().len(), 2);
314        for s in ["space.record_name", "space.inner_record_name"] {
315            assert!(rs.get_names().contains_key(&Name::new(s)?));
316        }
317
318        Ok(())
319    }
320
321    #[test]
322    fn avro_3448_test_proper_resolution_inner_enum_inherited_namespace() -> TestResult {
323        let schema = r#"
324        {
325          "name": "record_name",
326          "namespace": "space",
327          "type": "record",
328          "fields": [
329            {
330              "name": "outer_field_1",
331              "type": [
332                        "null",
333                        {
334                            "type":"enum",
335                            "name":"inner_enum_name",
336                            "symbols":["Extensive","Testing"]
337                        }
338                    ]
339            },
340            {
341                "name": "outer_field_2",
342                "type" : "inner_enum_name"
343            }
344          ]
345        }
346        "#;
347        let schema = Schema::parse_str(schema)?;
348        let rs = ResolvedSchema::new(&schema)?;
349        assert_eq!(rs.get_names().len(), 2);
350        for s in ["space.record_name", "space.inner_enum_name"] {
351            assert!(rs.get_names().contains_key(&Name::new(s)?));
352        }
353
354        Ok(())
355    }
356
357    #[test]
358    fn avro_3448_test_proper_resolution_inner_enum_qualified_namespace() -> TestResult {
359        let schema = r#"
360        {
361          "name": "record_name",
362          "namespace": "space",
363          "type": "record",
364          "fields": [
365            {
366              "name": "outer_field_1",
367              "type": [
368                        "null",
369                        {
370                            "type":"enum",
371                            "name":"inner_enum_name",
372                            "symbols":["Extensive","Testing"]
373                        }
374                    ]
375            },
376            {
377                "name": "outer_field_2",
378                "type" : "space.inner_enum_name"
379            }
380          ]
381        }
382        "#;
383        let schema = Schema::parse_str(schema)?;
384        let rs = ResolvedSchema::new(&schema)?;
385        assert_eq!(rs.get_names().len(), 2);
386        for s in ["space.record_name", "space.inner_enum_name"] {
387            assert!(rs.get_names().contains_key(&Name::new(s)?));
388        }
389
390        Ok(())
391    }
392
393    #[test]
394    fn avro_3448_test_proper_resolution_inner_fixed_inherited_namespace() -> TestResult {
395        let schema = r#"
396        {
397          "name": "record_name",
398          "namespace": "space",
399          "type": "record",
400          "fields": [
401            {
402              "name": "outer_field_1",
403              "type": [
404                        "null",
405                        {
406                            "type":"fixed",
407                            "name":"inner_fixed_name",
408                            "size": 16
409                        }
410                    ]
411            },
412            {
413                "name": "outer_field_2",
414                "type" : "inner_fixed_name"
415            }
416          ]
417        }
418        "#;
419        let schema = Schema::parse_str(schema)?;
420        let rs = ResolvedSchema::new(&schema)?;
421        assert_eq!(rs.get_names().len(), 2);
422        for s in ["space.record_name", "space.inner_fixed_name"] {
423            assert!(rs.get_names().contains_key(&Name::new(s)?));
424        }
425
426        Ok(())
427    }
428
429    #[test]
430    fn avro_3448_test_proper_resolution_inner_fixed_qualified_namespace() -> TestResult {
431        let schema = r#"
432        {
433          "name": "record_name",
434          "namespace": "space",
435          "type": "record",
436          "fields": [
437            {
438              "name": "outer_field_1",
439              "type": [
440                        "null",
441                        {
442                            "type":"fixed",
443                            "name":"inner_fixed_name",
444                            "size": 16
445                        }
446                    ]
447            },
448            {
449                "name": "outer_field_2",
450                "type" : "space.inner_fixed_name"
451            }
452          ]
453        }
454        "#;
455        let schema = Schema::parse_str(schema)?;
456        let rs = ResolvedSchema::new(&schema)?;
457        assert_eq!(rs.get_names().len(), 2);
458        for s in ["space.record_name", "space.inner_fixed_name"] {
459            assert!(rs.get_names().contains_key(&Name::new(s)?));
460        }
461
462        Ok(())
463    }
464
465    #[test]
466    fn avro_3448_test_proper_resolution_inner_record_inner_namespace() -> TestResult {
467        let schema = r#"
468        {
469          "name": "record_name",
470          "namespace": "space",
471          "type": "record",
472          "fields": [
473            {
474              "name": "outer_field_1",
475              "type": [
476                        "null",
477                        {
478                            "type":"record",
479                            "name":"inner_record_name",
480                            "namespace":"inner_space",
481                            "fields":[
482                                {
483                                    "name":"inner_field_1",
484                                    "type":"double"
485                                }
486                            ]
487                        }
488                    ]
489            },
490            {
491                "name": "outer_field_2",
492                "type" : "inner_space.inner_record_name"
493            }
494          ]
495        }
496        "#;
497        let schema = Schema::parse_str(schema)?;
498        let rs = ResolvedSchema::new(&schema)?;
499        assert_eq!(rs.get_names().len(), 2);
500        for s in ["space.record_name", "inner_space.inner_record_name"] {
501            assert!(rs.get_names().contains_key(&Name::new(s)?));
502        }
503
504        Ok(())
505    }
506
507    #[test]
508    fn avro_3448_test_proper_resolution_inner_enum_inner_namespace() -> TestResult {
509        let schema = r#"
510        {
511          "name": "record_name",
512          "namespace": "space",
513          "type": "record",
514          "fields": [
515            {
516              "name": "outer_field_1",
517              "type": [
518                        "null",
519                        {
520                            "type":"enum",
521                            "name":"inner_enum_name",
522                            "namespace": "inner_space",
523                            "symbols":["Extensive","Testing"]
524                        }
525                    ]
526            },
527            {
528                "name": "outer_field_2",
529                "type" : "inner_space.inner_enum_name"
530            }
531          ]
532        }
533        "#;
534        let schema = Schema::parse_str(schema)?;
535        let rs = ResolvedSchema::new(&schema)?;
536        assert_eq!(rs.get_names().len(), 2);
537        for s in ["space.record_name", "inner_space.inner_enum_name"] {
538            assert!(rs.get_names().contains_key(&Name::new(s)?));
539        }
540
541        Ok(())
542    }
543
544    #[test]
545    fn avro_3448_test_proper_resolution_inner_fixed_inner_namespace() -> TestResult {
546        let schema = r#"
547        {
548          "name": "record_name",
549          "namespace": "space",
550          "type": "record",
551          "fields": [
552            {
553              "name": "outer_field_1",
554              "type": [
555                        "null",
556                        {
557                            "type":"fixed",
558                            "name":"inner_fixed_name",
559                            "namespace": "inner_space",
560                            "size": 16
561                        }
562                    ]
563            },
564            {
565                "name": "outer_field_2",
566                "type" : "inner_space.inner_fixed_name"
567            }
568          ]
569        }
570        "#;
571        let schema = Schema::parse_str(schema)?;
572        let rs = ResolvedSchema::new(&schema)?;
573        assert_eq!(rs.get_names().len(), 2);
574        for s in ["space.record_name", "inner_space.inner_fixed_name"] {
575            assert!(rs.get_names().contains_key(&Name::new(s)?));
576        }
577
578        Ok(())
579    }
580
581    #[test]
582    fn avro_3448_test_proper_multi_level_resolution_inner_record_outer_namespace() -> TestResult {
583        let schema = r#"
584        {
585          "name": "record_name",
586          "namespace": "space",
587          "type": "record",
588          "fields": [
589            {
590              "name": "outer_field_1",
591              "type": [
592                        "null",
593                        {
594                            "type":"record",
595                            "name":"middle_record_name",
596                            "fields":[
597                                {
598                                    "name":"middle_field_1",
599                                    "type":[
600                                        "null",
601                                        {
602                                            "type":"record",
603                                            "name":"inner_record_name",
604                                            "fields":[
605                                                {
606                                                    "name":"inner_field_1",
607                                                    "type":"double"
608                                                }
609                                            ]
610                                        }
611                                    ]
612                                }
613                            ]
614                        }
615                    ]
616            },
617            {
618                "name": "outer_field_2",
619                "type" : "space.inner_record_name"
620            }
621          ]
622        }
623        "#;
624        let schema = Schema::parse_str(schema)?;
625        let rs = ResolvedSchema::new(&schema)?;
626        assert_eq!(rs.get_names().len(), 3);
627        for s in [
628            "space.record_name",
629            "space.middle_record_name",
630            "space.inner_record_name",
631        ] {
632            assert!(rs.get_names().contains_key(&Name::new(s)?));
633        }
634
635        Ok(())
636    }
637
638    #[test]
639    fn avro_3448_test_proper_multi_level_resolution_inner_record_middle_namespace() -> TestResult {
640        let schema = r#"
641        {
642          "name": "record_name",
643          "namespace": "space",
644          "type": "record",
645          "fields": [
646            {
647              "name": "outer_field_1",
648              "type": [
649                        "null",
650                        {
651                            "type":"record",
652                            "name":"middle_record_name",
653                            "namespace":"middle_namespace",
654                            "fields":[
655                                {
656                                    "name":"middle_field_1",
657                                    "type":[
658                                        "null",
659                                        {
660                                            "type":"record",
661                                            "name":"inner_record_name",
662                                            "fields":[
663                                                {
664                                                    "name":"inner_field_1",
665                                                    "type":"double"
666                                                }
667                                            ]
668                                        }
669                                    ]
670                                }
671                            ]
672                        }
673                    ]
674            },
675            {
676                "name": "outer_field_2",
677                "type" : "middle_namespace.inner_record_name"
678            }
679          ]
680        }
681        "#;
682        let schema = Schema::parse_str(schema)?;
683        let rs = ResolvedSchema::new(&schema)?;
684        assert_eq!(rs.get_names().len(), 3);
685        for s in [
686            "space.record_name",
687            "middle_namespace.middle_record_name",
688            "middle_namespace.inner_record_name",
689        ] {
690            assert!(rs.get_names().contains_key(&Name::new(s)?));
691        }
692
693        Ok(())
694    }
695
696    #[test]
697    fn avro_3448_test_proper_multi_level_resolution_inner_record_inner_namespace() -> TestResult {
698        let schema = r#"
699        {
700          "name": "record_name",
701          "namespace": "space",
702          "type": "record",
703          "fields": [
704            {
705              "name": "outer_field_1",
706              "type": [
707                        "null",
708                        {
709                            "type":"record",
710                            "name":"middle_record_name",
711                            "namespace":"middle_namespace",
712                            "fields":[
713                                {
714                                    "name":"middle_field_1",
715                                    "type":[
716                                        "null",
717                                        {
718                                            "type":"record",
719                                            "name":"inner_record_name",
720                                            "namespace":"inner_namespace",
721                                            "fields":[
722                                                {
723                                                    "name":"inner_field_1",
724                                                    "type":"double"
725                                                }
726                                            ]
727                                        }
728                                    ]
729                                }
730                            ]
731                        }
732                    ]
733            },
734            {
735                "name": "outer_field_2",
736                "type" : "inner_namespace.inner_record_name"
737            }
738          ]
739        }
740        "#;
741        let schema = Schema::parse_str(schema)?;
742        let rs = ResolvedSchema::new(&schema)?;
743        assert_eq!(rs.get_names().len(), 3);
744        for s in [
745            "space.record_name",
746            "middle_namespace.middle_record_name",
747            "inner_namespace.inner_record_name",
748        ] {
749            assert!(rs.get_names().contains_key(&Name::new(s)?));
750        }
751
752        Ok(())
753    }
754
755    #[test]
756    fn avro_3448_test_proper_in_array_resolution_inherited_namespace() -> TestResult {
757        let schema = r#"
758        {
759          "name": "record_name",
760          "namespace": "space",
761          "type": "record",
762          "fields": [
763            {
764              "name": "outer_field_1",
765              "type": {
766                  "type":"array",
767                  "items":{
768                      "type":"record",
769                      "name":"in_array_record",
770                      "fields": [
771                          {
772                              "name":"array_record_field",
773                              "type":"string"
774                          }
775                      ]
776                  }
777              }
778            },
779            {
780                "name":"outer_field_2",
781                "type":"in_array_record"
782            }
783          ]
784        }
785        "#;
786        let schema = Schema::parse_str(schema)?;
787        let rs = ResolvedSchema::new(&schema)?;
788        assert_eq!(rs.get_names().len(), 2);
789        for s in ["space.record_name", "space.in_array_record"] {
790            assert!(rs.get_names().contains_key(&Name::new(s)?));
791        }
792
793        Ok(())
794    }
795
796    #[test]
797    fn avro_3448_test_proper_in_map_resolution_inherited_namespace() -> TestResult {
798        let schema = r#"
799        {
800          "name": "record_name",
801          "namespace": "space",
802          "type": "record",
803          "fields": [
804            {
805              "name": "outer_field_1",
806              "type": {
807                  "type":"map",
808                  "values":{
809                      "type":"record",
810                      "name":"in_map_record",
811                      "fields": [
812                          {
813                              "name":"map_record_field",
814                              "type":"string"
815                          }
816                      ]
817                  }
818              }
819            },
820            {
821                "name":"outer_field_2",
822                "type":"in_map_record"
823            }
824          ]
825        }
826        "#;
827        let schema = Schema::parse_str(schema)?;
828        let rs = ResolvedSchema::new(&schema)?;
829        assert_eq!(rs.get_names().len(), 2);
830        for s in ["space.record_name", "space.in_map_record"] {
831            assert!(rs.get_names().contains_key(&Name::new(s)?));
832        }
833
834        Ok(())
835    }
836
837    #[test]
838    fn avro_3466_test_to_json_inner_enum_inner_namespace() -> TestResult {
839        let schema = r#"
840        {
841        "name": "record_name",
842        "namespace": "space",
843        "type": "record",
844        "fields": [
845            {
846            "name": "outer_field_1",
847            "type": [
848                        "null",
849                        {
850                            "type":"enum",
851                            "name":"inner_enum_name",
852                            "namespace": "inner_space",
853                            "symbols":["Extensive","Testing"]
854                        }
855                    ]
856            },
857            {
858                "name": "outer_field_2",
859                "type" : "inner_space.inner_enum_name"
860            }
861        ]
862        }
863        "#;
864        let schema = Schema::parse_str(schema)?;
865        let rs = ResolvedSchema::new(&schema)?;
866
867        // confirm we have expected 2 full-names
868        assert_eq!(rs.get_names().len(), 2);
869        for s in ["space.record_name", "inner_space.inner_enum_name"] {
870            assert!(rs.get_names().contains_key(&Name::new(s)?));
871        }
872
873        // convert Schema back to JSON string
874        let schema_str = serde_json::to_string(&schema)?;
875        let _schema = Schema::parse_str(&schema_str)?;
876        assert_eq!(schema, _schema);
877
878        Ok(())
879    }
880
881    #[test]
882    fn avro_3466_test_to_json_inner_fixed_inner_namespace() -> TestResult {
883        let schema = r#"
884        {
885        "name": "record_name",
886        "namespace": "space",
887        "type": "record",
888        "fields": [
889            {
890            "name": "outer_field_1",
891            "type": [
892                        "null",
893                        {
894                            "type":"fixed",
895                            "name":"inner_fixed_name",
896                            "namespace": "inner_space",
897                            "size":54
898                        }
899                    ]
900            },
901            {
902                "name": "outer_field_2",
903                "type" : "inner_space.inner_fixed_name"
904            }
905        ]
906        }
907        "#;
908        let schema = Schema::parse_str(schema)?;
909        let rs = ResolvedSchema::new(&schema)?;
910
911        // confirm we have expected 2 full-names
912        assert_eq!(rs.get_names().len(), 2);
913        for s in ["space.record_name", "inner_space.inner_fixed_name"] {
914            assert!(rs.get_names().contains_key(&Name::new(s)?));
915        }
916
917        // convert Schema back to JSON string
918        let schema_str = serde_json::to_string(&schema)?;
919        let _schema = Schema::parse_str(&schema_str)?;
920        assert_eq!(schema, _schema);
921
922        Ok(())
923    }
924
925    #[test]
926    fn avro_rs_339_schema_ref_uuid() -> TestResult {
927        let schema = Schema::parse_str(
928            r#"{
929            "name": "foo",
930            "type": "record",
931            "fields": [
932                {
933                    "name": "a",
934                    "type": {
935                        "type": "fixed",
936                        "size": 16,
937                        "logicalType": "uuid",
938                        "name": "bar"
939                    }
940                },
941                {
942                    "name": "b",
943                    "type": "bar"
944                }
945            ]
946        }"#,
947        )?;
948        let _resolved = ResolvedSchema::new(&schema)?;
949        let _resolved_owned = ResolvedOwnedSchema::try_from(schema)?;
950
951        Ok(())
952    }
953
954    #[test]
955    fn avro_rs_339_schema_ref_decimal() -> TestResult {
956        let schema = Schema::parse_str(
957            r#"{
958            "name": "foo",
959            "type": "record",
960            "fields": [
961                {
962                    "name": "a",
963                    "type": {
964                        "type": "fixed",
965                        "size": 16,
966                        "logicalType": "decimal",
967                        "precision": 4,
968                        "scale": 2,
969                        "name": "bar"
970                    }
971                },
972                {
973                    "name": "b",
974                    "type": "bar"
975                }
976            ]
977        }"#,
978        )?;
979        let _resolved = ResolvedSchema::new(&schema)?;
980        let _resolved_owned = ResolvedOwnedSchema::try_from(schema)?;
981
982        Ok(())
983    }
984
985    #[test]
986    fn avro_rs_444_do_not_allow_duplicate_names_in_known_schemata() -> TestResult {
987        let schema = Schema::parse_str(
988            r#"{
989            "name": "foo",
990            "type": "record",
991            "fields": [
992                {
993                    "name": "a",
994                    "type": {
995                        "type": "fixed",
996                        "size": 16,
997                        "logicalType": "decimal",
998                        "precision": 4,
999                        "scale": 2,
1000                        "name": "bar"
1001                    }
1002                },
1003                {
1004                    "name": "b",
1005                    "type": "bar"
1006                },
1007                {
1008                    "name": "c",
1009                    "type": {
1010                        "type": "fixed",
1011                        "size": 16,
1012                        "logicalType": "uuid",
1013                        "name": "duplicated_name"
1014                    }
1015                }
1016            ]
1017        }"#,
1018        )?;
1019
1020        let mut known_schemata: NamesRef = HashMap::default();
1021        known_schemata.insert("duplicated_name".try_into()?, &Schema::Boolean);
1022
1023        let result = ResolvedSchema::new_with_known_schemata(vec![&schema], None, &known_schemata)
1024            .unwrap_err();
1025
1026        assert_eq!(
1027            result.to_string(),
1028            "Two named schema defined for same fullname: duplicated_name."
1029        );
1030
1031        Ok(())
1032    }
1033}