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, Namespace, RecordSchema,
21    UnionSchema, UuidSchema,
22};
23use crate::{AvroResult, Error, Schema};
24use std::collections::HashMap;
25
26#[derive(Debug)]
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: &Namespace,
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: &Namespace,
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);
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);
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.clone();
191                names.insert(fully_qualified_name, schema);
192                for field in fields {
193                    resolve_names(&field.schema, names, &record_namespace, known_schemata)?
194                }
195                Ok(())
196            }
197        }
198        Schema::Ref { name } => {
199            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
200            if names.contains_key(&fully_qualified_name)
201                || known_schemata.contains_key(&fully_qualified_name)
202            {
203                Ok(())
204            } else {
205                Err(Details::SchemaResolutionError(fully_qualified_name).into())
206            }
207        }
208        _ => Ok(()),
209    }
210}
211
212pub fn resolve_names_with_schemata<'s, 'n>(
213    schemata: impl IntoIterator<Item = &'s Schema>,
214    names: &mut NamesRef<'s>,
215    enclosing_namespace: &Namespace,
216    known_schemata: &NamesRef<'n>,
217) -> AvroResult<()> {
218    for schema in schemata {
219        resolve_names(schema, names, enclosing_namespace, known_schemata)?;
220    }
221    Ok(())
222}
223
224#[cfg(test)]
225mod tests {
226    use super::{ResolvedOwnedSchema, ResolvedSchema};
227    use crate::{
228        Schema,
229        schema::{Name, NamesRef},
230    };
231    use apache_avro_test_helper::TestResult;
232    use std::collections::HashMap;
233
234    #[test]
235    fn avro_3448_test_proper_resolution_inner_record_inherited_namespace() -> TestResult {
236        let schema = r#"
237        {
238          "name": "record_name",
239          "namespace": "space",
240          "type": "record",
241          "fields": [
242            {
243              "name": "outer_field_1",
244              "type": [
245                        "null",
246                        {
247                            "type":"record",
248                            "name":"inner_record_name",
249                            "fields":[
250                                {
251                                    "name":"inner_field_1",
252                                    "type":"double"
253                                }
254                            ]
255                        }
256                    ]
257            },
258            {
259                "name": "outer_field_2",
260                "type" : "inner_record_name"
261            }
262          ]
263        }
264        "#;
265        let schema = Schema::parse_str(schema)?;
266        let rs = ResolvedSchema::new(&schema)?;
267        assert_eq!(rs.get_names().len(), 2);
268        for s in &["space.record_name", "space.inner_record_name"] {
269            assert!(rs.get_names().contains_key(&Name::new(s)?));
270        }
271
272        Ok(())
273    }
274
275    #[test]
276    fn avro_3448_test_proper_resolution_inner_record_qualified_namespace() -> TestResult {
277        let schema = r#"
278        {
279          "name": "record_name",
280          "namespace": "space",
281          "type": "record",
282          "fields": [
283            {
284              "name": "outer_field_1",
285              "type": [
286                        "null",
287                        {
288                            "type":"record",
289                            "name":"inner_record_name",
290                            "fields":[
291                                {
292                                    "name":"inner_field_1",
293                                    "type":"double"
294                                }
295                            ]
296                        }
297                    ]
298            },
299            {
300                "name": "outer_field_2",
301                "type" : "space.inner_record_name"
302            }
303          ]
304        }
305        "#;
306        let schema = Schema::parse_str(schema)?;
307        let rs = ResolvedSchema::new(&schema)?;
308        assert_eq!(rs.get_names().len(), 2);
309        for s in &["space.record_name", "space.inner_record_name"] {
310            assert!(rs.get_names().contains_key(&Name::new(s)?));
311        }
312
313        Ok(())
314    }
315
316    #[test]
317    fn avro_3448_test_proper_resolution_inner_enum_inherited_namespace() -> TestResult {
318        let schema = r#"
319        {
320          "name": "record_name",
321          "namespace": "space",
322          "type": "record",
323          "fields": [
324            {
325              "name": "outer_field_1",
326              "type": [
327                        "null",
328                        {
329                            "type":"enum",
330                            "name":"inner_enum_name",
331                            "symbols":["Extensive","Testing"]
332                        }
333                    ]
334            },
335            {
336                "name": "outer_field_2",
337                "type" : "inner_enum_name"
338            }
339          ]
340        }
341        "#;
342        let schema = Schema::parse_str(schema)?;
343        let rs = ResolvedSchema::new(&schema)?;
344        assert_eq!(rs.get_names().len(), 2);
345        for s in &["space.record_name", "space.inner_enum_name"] {
346            assert!(rs.get_names().contains_key(&Name::new(s)?));
347        }
348
349        Ok(())
350    }
351
352    #[test]
353    fn avro_3448_test_proper_resolution_inner_enum_qualified_namespace() -> TestResult {
354        let schema = r#"
355        {
356          "name": "record_name",
357          "namespace": "space",
358          "type": "record",
359          "fields": [
360            {
361              "name": "outer_field_1",
362              "type": [
363                        "null",
364                        {
365                            "type":"enum",
366                            "name":"inner_enum_name",
367                            "symbols":["Extensive","Testing"]
368                        }
369                    ]
370            },
371            {
372                "name": "outer_field_2",
373                "type" : "space.inner_enum_name"
374            }
375          ]
376        }
377        "#;
378        let schema = Schema::parse_str(schema)?;
379        let rs = ResolvedSchema::new(&schema)?;
380        assert_eq!(rs.get_names().len(), 2);
381        for s in &["space.record_name", "space.inner_enum_name"] {
382            assert!(rs.get_names().contains_key(&Name::new(s)?));
383        }
384
385        Ok(())
386    }
387
388    #[test]
389    fn avro_3448_test_proper_resolution_inner_fixed_inherited_namespace() -> TestResult {
390        let schema = r#"
391        {
392          "name": "record_name",
393          "namespace": "space",
394          "type": "record",
395          "fields": [
396            {
397              "name": "outer_field_1",
398              "type": [
399                        "null",
400                        {
401                            "type":"fixed",
402                            "name":"inner_fixed_name",
403                            "size": 16
404                        }
405                    ]
406            },
407            {
408                "name": "outer_field_2",
409                "type" : "inner_fixed_name"
410            }
411          ]
412        }
413        "#;
414        let schema = Schema::parse_str(schema)?;
415        let rs = ResolvedSchema::new(&schema)?;
416        assert_eq!(rs.get_names().len(), 2);
417        for s in &["space.record_name", "space.inner_fixed_name"] {
418            assert!(rs.get_names().contains_key(&Name::new(s)?));
419        }
420
421        Ok(())
422    }
423
424    #[test]
425    fn avro_3448_test_proper_resolution_inner_fixed_qualified_namespace() -> TestResult {
426        let schema = r#"
427        {
428          "name": "record_name",
429          "namespace": "space",
430          "type": "record",
431          "fields": [
432            {
433              "name": "outer_field_1",
434              "type": [
435                        "null",
436                        {
437                            "type":"fixed",
438                            "name":"inner_fixed_name",
439                            "size": 16
440                        }
441                    ]
442            },
443            {
444                "name": "outer_field_2",
445                "type" : "space.inner_fixed_name"
446            }
447          ]
448        }
449        "#;
450        let schema = Schema::parse_str(schema)?;
451        let rs = ResolvedSchema::new(&schema)?;
452        assert_eq!(rs.get_names().len(), 2);
453        for s in &["space.record_name", "space.inner_fixed_name"] {
454            assert!(rs.get_names().contains_key(&Name::new(s)?));
455        }
456
457        Ok(())
458    }
459
460    #[test]
461    fn avro_3448_test_proper_resolution_inner_record_inner_namespace() -> TestResult {
462        let schema = r#"
463        {
464          "name": "record_name",
465          "namespace": "space",
466          "type": "record",
467          "fields": [
468            {
469              "name": "outer_field_1",
470              "type": [
471                        "null",
472                        {
473                            "type":"record",
474                            "name":"inner_record_name",
475                            "namespace":"inner_space",
476                            "fields":[
477                                {
478                                    "name":"inner_field_1",
479                                    "type":"double"
480                                }
481                            ]
482                        }
483                    ]
484            },
485            {
486                "name": "outer_field_2",
487                "type" : "inner_space.inner_record_name"
488            }
489          ]
490        }
491        "#;
492        let schema = Schema::parse_str(schema)?;
493        let rs = ResolvedSchema::new(&schema)?;
494        assert_eq!(rs.get_names().len(), 2);
495        for s in &["space.record_name", "inner_space.inner_record_name"] {
496            assert!(rs.get_names().contains_key(&Name::new(s)?));
497        }
498
499        Ok(())
500    }
501
502    #[test]
503    fn avro_3448_test_proper_resolution_inner_enum_inner_namespace() -> TestResult {
504        let schema = r#"
505        {
506          "name": "record_name",
507          "namespace": "space",
508          "type": "record",
509          "fields": [
510            {
511              "name": "outer_field_1",
512              "type": [
513                        "null",
514                        {
515                            "type":"enum",
516                            "name":"inner_enum_name",
517                            "namespace": "inner_space",
518                            "symbols":["Extensive","Testing"]
519                        }
520                    ]
521            },
522            {
523                "name": "outer_field_2",
524                "type" : "inner_space.inner_enum_name"
525            }
526          ]
527        }
528        "#;
529        let schema = Schema::parse_str(schema)?;
530        let rs = ResolvedSchema::new(&schema)?;
531        assert_eq!(rs.get_names().len(), 2);
532        for s in &["space.record_name", "inner_space.inner_enum_name"] {
533            assert!(rs.get_names().contains_key(&Name::new(s)?));
534        }
535
536        Ok(())
537    }
538
539    #[test]
540    fn avro_3448_test_proper_resolution_inner_fixed_inner_namespace() -> TestResult {
541        let schema = r#"
542        {
543          "name": "record_name",
544          "namespace": "space",
545          "type": "record",
546          "fields": [
547            {
548              "name": "outer_field_1",
549              "type": [
550                        "null",
551                        {
552                            "type":"fixed",
553                            "name":"inner_fixed_name",
554                            "namespace": "inner_space",
555                            "size": 16
556                        }
557                    ]
558            },
559            {
560                "name": "outer_field_2",
561                "type" : "inner_space.inner_fixed_name"
562            }
563          ]
564        }
565        "#;
566        let schema = Schema::parse_str(schema)?;
567        let rs = ResolvedSchema::new(&schema)?;
568        assert_eq!(rs.get_names().len(), 2);
569        for s in &["space.record_name", "inner_space.inner_fixed_name"] {
570            assert!(rs.get_names().contains_key(&Name::new(s)?));
571        }
572
573        Ok(())
574    }
575
576    #[test]
577    fn avro_3448_test_proper_multi_level_resolution_inner_record_outer_namespace() -> TestResult {
578        let schema = r#"
579        {
580          "name": "record_name",
581          "namespace": "space",
582          "type": "record",
583          "fields": [
584            {
585              "name": "outer_field_1",
586              "type": [
587                        "null",
588                        {
589                            "type":"record",
590                            "name":"middle_record_name",
591                            "fields":[
592                                {
593                                    "name":"middle_field_1",
594                                    "type":[
595                                        "null",
596                                        {
597                                            "type":"record",
598                                            "name":"inner_record_name",
599                                            "fields":[
600                                                {
601                                                    "name":"inner_field_1",
602                                                    "type":"double"
603                                                }
604                                            ]
605                                        }
606                                    ]
607                                }
608                            ]
609                        }
610                    ]
611            },
612            {
613                "name": "outer_field_2",
614                "type" : "space.inner_record_name"
615            }
616          ]
617        }
618        "#;
619        let schema = Schema::parse_str(schema)?;
620        let rs = ResolvedSchema::new(&schema)?;
621        assert_eq!(rs.get_names().len(), 3);
622        for s in &[
623            "space.record_name",
624            "space.middle_record_name",
625            "space.inner_record_name",
626        ] {
627            assert!(rs.get_names().contains_key(&Name::new(s)?));
628        }
629
630        Ok(())
631    }
632
633    #[test]
634    fn avro_3448_test_proper_multi_level_resolution_inner_record_middle_namespace() -> TestResult {
635        let schema = r#"
636        {
637          "name": "record_name",
638          "namespace": "space",
639          "type": "record",
640          "fields": [
641            {
642              "name": "outer_field_1",
643              "type": [
644                        "null",
645                        {
646                            "type":"record",
647                            "name":"middle_record_name",
648                            "namespace":"middle_namespace",
649                            "fields":[
650                                {
651                                    "name":"middle_field_1",
652                                    "type":[
653                                        "null",
654                                        {
655                                            "type":"record",
656                                            "name":"inner_record_name",
657                                            "fields":[
658                                                {
659                                                    "name":"inner_field_1",
660                                                    "type":"double"
661                                                }
662                                            ]
663                                        }
664                                    ]
665                                }
666                            ]
667                        }
668                    ]
669            },
670            {
671                "name": "outer_field_2",
672                "type" : "middle_namespace.inner_record_name"
673            }
674          ]
675        }
676        "#;
677        let schema = Schema::parse_str(schema)?;
678        let rs = ResolvedSchema::new(&schema)?;
679        assert_eq!(rs.get_names().len(), 3);
680        for s in &[
681            "space.record_name",
682            "middle_namespace.middle_record_name",
683            "middle_namespace.inner_record_name",
684        ] {
685            assert!(rs.get_names().contains_key(&Name::new(s)?));
686        }
687
688        Ok(())
689    }
690
691    #[test]
692    fn avro_3448_test_proper_multi_level_resolution_inner_record_inner_namespace() -> TestResult {
693        let schema = r#"
694        {
695          "name": "record_name",
696          "namespace": "space",
697          "type": "record",
698          "fields": [
699            {
700              "name": "outer_field_1",
701              "type": [
702                        "null",
703                        {
704                            "type":"record",
705                            "name":"middle_record_name",
706                            "namespace":"middle_namespace",
707                            "fields":[
708                                {
709                                    "name":"middle_field_1",
710                                    "type":[
711                                        "null",
712                                        {
713                                            "type":"record",
714                                            "name":"inner_record_name",
715                                            "namespace":"inner_namespace",
716                                            "fields":[
717                                                {
718                                                    "name":"inner_field_1",
719                                                    "type":"double"
720                                                }
721                                            ]
722                                        }
723                                    ]
724                                }
725                            ]
726                        }
727                    ]
728            },
729            {
730                "name": "outer_field_2",
731                "type" : "inner_namespace.inner_record_name"
732            }
733          ]
734        }
735        "#;
736        let schema = Schema::parse_str(schema)?;
737        let rs = ResolvedSchema::new(&schema)?;
738        assert_eq!(rs.get_names().len(), 3);
739        for s in &[
740            "space.record_name",
741            "middle_namespace.middle_record_name",
742            "inner_namespace.inner_record_name",
743        ] {
744            assert!(rs.get_names().contains_key(&Name::new(s)?));
745        }
746
747        Ok(())
748    }
749
750    #[test]
751    fn avro_3448_test_proper_in_array_resolution_inherited_namespace() -> TestResult {
752        let schema = r#"
753        {
754          "name": "record_name",
755          "namespace": "space",
756          "type": "record",
757          "fields": [
758            {
759              "name": "outer_field_1",
760              "type": {
761                  "type":"array",
762                  "items":{
763                      "type":"record",
764                      "name":"in_array_record",
765                      "fields": [
766                          {
767                              "name":"array_record_field",
768                              "type":"string"
769                          }
770                      ]
771                  }
772              }
773            },
774            {
775                "name":"outer_field_2",
776                "type":"in_array_record"
777            }
778          ]
779        }
780        "#;
781        let schema = Schema::parse_str(schema)?;
782        let rs = ResolvedSchema::new(&schema)?;
783        assert_eq!(rs.get_names().len(), 2);
784        for s in &["space.record_name", "space.in_array_record"] {
785            assert!(rs.get_names().contains_key(&Name::new(s)?));
786        }
787
788        Ok(())
789    }
790
791    #[test]
792    fn avro_3448_test_proper_in_map_resolution_inherited_namespace() -> TestResult {
793        let schema = r#"
794        {
795          "name": "record_name",
796          "namespace": "space",
797          "type": "record",
798          "fields": [
799            {
800              "name": "outer_field_1",
801              "type": {
802                  "type":"map",
803                  "values":{
804                      "type":"record",
805                      "name":"in_map_record",
806                      "fields": [
807                          {
808                              "name":"map_record_field",
809                              "type":"string"
810                          }
811                      ]
812                  }
813              }
814            },
815            {
816                "name":"outer_field_2",
817                "type":"in_map_record"
818            }
819          ]
820        }
821        "#;
822        let schema = Schema::parse_str(schema)?;
823        let rs = ResolvedSchema::new(&schema)?;
824        assert_eq!(rs.get_names().len(), 2);
825        for s in &["space.record_name", "space.in_map_record"] {
826            assert!(rs.get_names().contains_key(&Name::new(s)?));
827        }
828
829        Ok(())
830    }
831
832    #[test]
833    fn avro_3466_test_to_json_inner_enum_inner_namespace() -> TestResult {
834        let schema = r#"
835        {
836        "name": "record_name",
837        "namespace": "space",
838        "type": "record",
839        "fields": [
840            {
841            "name": "outer_field_1",
842            "type": [
843                        "null",
844                        {
845                            "type":"enum",
846                            "name":"inner_enum_name",
847                            "namespace": "inner_space",
848                            "symbols":["Extensive","Testing"]
849                        }
850                    ]
851            },
852            {
853                "name": "outer_field_2",
854                "type" : "inner_space.inner_enum_name"
855            }
856        ]
857        }
858        "#;
859        let schema = Schema::parse_str(schema)?;
860        let rs = ResolvedSchema::new(&schema)?;
861
862        // confirm we have expected 2 full-names
863        assert_eq!(rs.get_names().len(), 2);
864        for s in &["space.record_name", "inner_space.inner_enum_name"] {
865            assert!(rs.get_names().contains_key(&Name::new(s)?));
866        }
867
868        // convert Schema back to JSON string
869        let schema_str = serde_json::to_string(&schema)?;
870        let _schema = Schema::parse_str(&schema_str)?;
871        assert_eq!(schema, _schema);
872
873        Ok(())
874    }
875
876    #[test]
877    fn avro_3466_test_to_json_inner_fixed_inner_namespace() -> TestResult {
878        let schema = r#"
879        {
880        "name": "record_name",
881        "namespace": "space",
882        "type": "record",
883        "fields": [
884            {
885            "name": "outer_field_1",
886            "type": [
887                        "null",
888                        {
889                            "type":"fixed",
890                            "name":"inner_fixed_name",
891                            "namespace": "inner_space",
892                            "size":54
893                        }
894                    ]
895            },
896            {
897                "name": "outer_field_2",
898                "type" : "inner_space.inner_fixed_name"
899            }
900        ]
901        }
902        "#;
903        let schema = Schema::parse_str(schema)?;
904        let rs = ResolvedSchema::new(&schema)?;
905
906        // confirm we have expected 2 full-names
907        assert_eq!(rs.get_names().len(), 2);
908        for s in &["space.record_name", "inner_space.inner_fixed_name"] {
909            assert!(rs.get_names().contains_key(&Name::new(s)?));
910        }
911
912        // convert Schema back to JSON string
913        let schema_str = serde_json::to_string(&schema)?;
914        let _schema = Schema::parse_str(&schema_str)?;
915        assert_eq!(schema, _schema);
916
917        Ok(())
918    }
919
920    #[test]
921    fn avro_rs_339_schema_ref_uuid() -> TestResult {
922        let schema = Schema::parse_str(
923            r#"{
924            "name": "foo",
925            "type": "record",
926            "fields": [
927                {
928                    "name": "a",
929                    "type": {
930                        "type": "fixed",
931                        "size": 16,
932                        "logicalType": "uuid",
933                        "name": "bar"
934                    }
935                },
936                {
937                    "name": "b",
938                    "type": "bar"
939                }
940            ]
941        }"#,
942        )?;
943        let _resolved = ResolvedSchema::new(&schema)?;
944        let _resolved_owned = ResolvedOwnedSchema::try_from(schema)?;
945
946        Ok(())
947    }
948
949    #[test]
950    fn avro_rs_339_schema_ref_decimal() -> TestResult {
951        let schema = Schema::parse_str(
952            r#"{
953            "name": "foo",
954            "type": "record",
955            "fields": [
956                {
957                    "name": "a",
958                    "type": {
959                        "type": "fixed",
960                        "size": 16,
961                        "logicalType": "decimal",
962                        "precision": 4,
963                        "scale": 2,
964                        "name": "bar"
965                    }
966                },
967                {
968                    "name": "b",
969                    "type": "bar"
970                }
971            ]
972        }"#,
973        )?;
974        let _resolved = ResolvedSchema::new(&schema)?;
975        let _resolved_owned = ResolvedOwnedSchema::try_from(schema)?;
976
977        Ok(())
978    }
979
980    #[test]
981    fn avro_rs_444_do_not_allow_duplicate_names_in_known_schemata() -> TestResult {
982        let schema = Schema::parse_str(
983            r#"{
984            "name": "foo",
985            "type": "record",
986            "fields": [
987                {
988                    "name": "a",
989                    "type": {
990                        "type": "fixed",
991                        "size": 16,
992                        "logicalType": "decimal",
993                        "precision": 4,
994                        "scale": 2,
995                        "name": "bar"
996                    }
997                },
998                {
999                    "name": "b",
1000                    "type": "bar"
1001                },
1002                {
1003                    "name": "c",
1004                    "type": {
1005                        "type": "fixed",
1006                        "size": 16,
1007                        "logicalType": "uuid",
1008                        "name": "duplicated_name"
1009                    }
1010                }
1011            ]
1012        }"#,
1013        )?;
1014
1015        let mut known_schemata: NamesRef = HashMap::default();
1016        known_schemata.insert("duplicated_name".try_into()?, &Schema::Boolean);
1017
1018        let result = ResolvedSchema::new_with_known_schemata(vec![&schema], &None, &known_schemata)
1019            .unwrap_err();
1020
1021        assert_eq!(
1022            result.to_string(),
1023            "Two named schema defined for same fullname: duplicated_name."
1024        );
1025
1026        Ok(())
1027    }
1028}