typify_impl/
convert.rs

1// Copyright 2025 Oxide Computer Company
2
3use std::collections::BTreeSet;
4
5use crate::merge::{merge_all, try_merge_with_subschemas};
6use crate::type_entry::{
7    EnumTagType, TypeEntry, TypeEntryDetails, TypeEntryEnum, TypeEntryNewtype, TypeEntryStruct,
8    Variant, VariantDetails,
9};
10use crate::util::{all_mutually_exclusive, ref_key, StringValidator};
11use log::{debug, info};
12use schemars::schema::{
13    ArrayValidation, InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SingleOrVec,
14    StringValidation, SubschemaValidation,
15};
16
17use crate::util::get_type_name;
18
19use crate::{Error, Name, Result, TypeSpace, TypeSpaceImpl};
20
21pub const STD_NUM_NONZERO_PREFIX: &str = "::std::num::NonZero";
22
23impl TypeSpace {
24    pub(crate) fn convert_schema<'a>(
25        &mut self,
26        type_name: Name,
27        schema: &'a Schema,
28    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
29        info!(
30            "convert_schema {:?} {}",
31            type_name,
32            serde_json::to_string_pretty(schema).unwrap()
33        );
34        match schema {
35            Schema::Object(obj) => {
36                if let Some(type_entry) = self.cache.lookup(obj) {
37                    Ok((type_entry, &obj.metadata))
38                } else {
39                    self.convert_schema_object(type_name, schema, obj)
40                }
41            }
42
43            Schema::Bool(true) => self.convert_permissive(&None),
44            Schema::Bool(false) => self.convert_never(type_name, schema),
45        }
46    }
47
48    pub(crate) fn convert_schema_object<'a>(
49        &mut self,
50        type_name: Name,
51        original_schema: &'a Schema,
52        schema: &'a SchemaObject,
53    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
54        if let Some(type_entry) = self.convert_rust_extension(schema) {
55            return Ok((type_entry, &schema.metadata));
56        }
57
58        match schema {
59            // If we have a schema that has an instance type array that's
60            // exactly two elements and one of them is Null, we have the
61            // equivalent of an Option<T> where T is the type defined by the
62            // rest of the schema.
63            SchemaObject {
64                metadata,
65                instance_type: Some(SingleOrVec::Vec(multiple)),
66                enum_values,
67                ..
68            } if multiple.len() == 2 && multiple.contains(&InstanceType::Null) => {
69                let only_null = enum_values
70                    .as_ref()
71                    .is_some_and(|values| values.iter().all(serde_json::Value::is_null));
72
73                if only_null {
74                    // If there are enumerated values and they're all null,
75                    // it's just a null.
76                    self.convert_null(metadata)
77                } else if let Some(other_type) = multiple.iter().find(|t| t != &&InstanceType::Null)
78                {
79                    // In the sensible case where only one of the instance
80                    // types is null.
81                    let enum_values = enum_values.clone().map(|values| {
82                        values
83                            .iter()
84                            .filter(|&value| !value.is_null())
85                            .cloned()
86                            .collect()
87                    });
88                    let ss = Schema::Object(SchemaObject {
89                        instance_type: Some(SingleOrVec::from(*other_type)),
90                        enum_values,
91                        ..schema.clone()
92                    });
93                    // An Option type won't usually get a name--unless one is
94                    // required (in which case we'll generated a newtype
95                    // wrapper to give it a name). In such a case, we invent a
96                    // new name for the inner type; otherwise, the inner type
97                    // can just have this name.
98                    let inner_type_name = match &type_name {
99                        Name::Required(name) => Name::Suggested(format!("{}Inner", name)),
100                        _ => type_name,
101                    };
102                    self.convert_option(inner_type_name, metadata, &ss)
103                } else {
104                    // .. otherwise we try again with a simpler type.
105                    let new_schema = SchemaObject {
106                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
107                        ..schema.clone()
108                    };
109                    self.convert_schema_object(type_name, original_schema, &new_schema)
110                        .map(|(te, m)| match m {
111                            Some(_) if m == metadata => (te, metadata),
112                            Some(_) => panic!("unexpected metadata value"),
113                            None => (te, &None),
114                        })
115                }
116            }
117
118            // Strings
119            SchemaObject {
120                metadata,
121                instance_type: Some(SingleOrVec::Single(single)),
122                format,
123                enum_values: None,
124                const_value: None,
125                subschemas: None,
126                number: _,
127                string,
128                array: _,
129                object: _,
130                reference: None,
131                extensions: _,
132            } if single.as_ref() == &InstanceType::String => self.convert_string(
133                type_name,
134                original_schema,
135                metadata,
136                format,
137                string.as_ref().map(Box::as_ref),
138            ),
139
140            // Strings with the type omitted, but validation present
141            SchemaObject {
142                metadata,
143                instance_type: None,
144                format,
145                enum_values: None,
146                const_value: None,
147                subschemas: None,
148                number: None,
149                string: string @ Some(_),
150                array: None,
151                object: None,
152                reference: None,
153                extensions: _,
154            } => self.convert_string(
155                type_name,
156                original_schema,
157                metadata,
158                format,
159                string.as_ref().map(Box::as_ref),
160            ),
161
162            // Enumerated string type
163            SchemaObject {
164                metadata,
165                instance_type: Some(SingleOrVec::Single(single)),
166                // One could imagine wanting to honor the format field in this
167                // case, perhaps to generate an impl From<T> for Uuid, say that
168                // allowed for fluid conversion from the enum to a type
169                // corresponding to the format string. But that seems uncommon
170                // enough to ignore for the moment.
171                format: _,
172                enum_values: Some(enum_values),
173                const_value: None,
174                subschemas: None,
175                number: _,
176                string,
177                array: _,
178                object: _,
179                reference: None,
180                extensions: _,
181            } if single.as_ref() == &InstanceType::String => self.convert_enum_string(
182                type_name,
183                original_schema,
184                metadata,
185                enum_values,
186                string.as_ref().map(Box::as_ref),
187            ),
188
189            // Integers
190            SchemaObject {
191                metadata,
192                instance_type: Some(SingleOrVec::Single(single)),
193                format,
194                enum_values: None,
195                const_value: None,
196                subschemas: None,
197                number: validation,
198                string: _,
199                array: _,
200                object: _,
201                reference: None,
202                extensions: _,
203            } if single.as_ref() == &InstanceType::Integer => {
204                self.convert_integer(metadata, validation, format)
205            }
206
207            // Numbers
208            SchemaObject {
209                metadata,
210                instance_type: Some(SingleOrVec::Single(single)),
211                format,
212                enum_values: None,
213                const_value: None,
214                subschemas: None,
215                number: validation,
216                string: _,
217                array: _,
218                object: _,
219                reference: None,
220                extensions: _,
221            } if single.as_ref() == &InstanceType::Number => {
222                self.convert_number(metadata, validation, format)
223            }
224
225            // Boolean
226            SchemaObject {
227                metadata,
228                instance_type: Some(SingleOrVec::Single(single)),
229                format: None,
230                enum_values: _,
231                const_value: None,
232                subschemas: None,
233                number: _,
234                string: _,
235                array: _,
236                object: _,
237                reference: None,
238                extensions: _,
239            } if single.as_ref() == &InstanceType::Boolean => self.convert_bool(metadata),
240
241            // Object
242            SchemaObject {
243                metadata,
244                instance_type: Some(SingleOrVec::Single(single)),
245                format: None,
246                enum_values: None,
247                const_value: None,
248                subschemas: None,
249                number: _,
250                string: _,
251                array: _,
252                object: validation,
253                reference: None,
254                extensions: _,
255            } if single.as_ref() == &InstanceType::Object => {
256                self.convert_object(type_name, original_schema, metadata, validation)
257            }
258
259            // Object with the type omitted, but validation present
260            SchemaObject {
261                metadata,
262                instance_type: None,
263                format: None,
264                enum_values: None,
265                const_value: None,
266                subschemas: None,
267                number: None,
268                string: None,
269                array: None,
270                object: validation @ Some(_),
271                reference: None,
272                extensions: _,
273            } => self.convert_object(type_name, original_schema, metadata, validation),
274
275            // Array
276            SchemaObject {
277                metadata,
278                instance_type: Some(SingleOrVec::Single(single)),
279                format: None,
280                enum_values: None,
281                const_value: None,
282                subschemas: None,
283                number: _,
284                string: _,
285                array: Some(validation),
286                object: _,
287                reference: None,
288                extensions: _,
289            } if single.as_ref() == &InstanceType::Array => {
290                self.convert_array(type_name, metadata, validation)
291            }
292
293            // Array with the type omitted, but validation present
294            SchemaObject {
295                metadata,
296                instance_type: None,
297                format: None,
298                enum_values: None,
299                const_value: None,
300                subschemas: None,
301                number: None,
302                string: None,
303                array: Some(validation),
304                object: None,
305                reference: None,
306                extensions: _,
307            } => self.convert_array(type_name, metadata, validation),
308
309            // Arrays of anything
310            SchemaObject {
311                metadata,
312                instance_type: Some(SingleOrVec::Single(single)),
313                format: None,
314                enum_values: None,
315                const_value: None,
316                subschemas: None,
317                number: _,
318                string: _,
319                array: None,
320                object: _,
321                reference: None,
322                extensions: _,
323            } if single.as_ref() == &InstanceType::Array => self.convert_array_of_any(metadata),
324
325            // The permissive schema
326            SchemaObject {
327                metadata,
328                instance_type: None,
329                format: None,
330                enum_values: None,
331                const_value: None,
332                subschemas: None,
333                number: None,
334                string: None,
335                array: None,
336                object: None,
337                reference: None,
338                extensions: _,
339            } => self.convert_permissive(metadata),
340
341            // Null
342            SchemaObject {
343                metadata,
344                instance_type: Some(SingleOrVec::Single(single)),
345                format: _,
346                enum_values: None,
347                const_value: None,
348                subschemas: None,
349                number: _,
350                string: _,
351                array: _,
352                object: _,
353                reference: None,
354                extensions: _,
355            } if single.as_ref() == &InstanceType::Null => self.convert_null(metadata),
356
357            // Reference
358            SchemaObject {
359                metadata,
360                instance_type: None,
361                format: None,
362                enum_values: None,
363                const_value: None,
364                subschemas: None,
365                number: None,
366                string: None,
367                array: None,
368                object: None,
369                reference: Some(reference),
370                extensions: _,
371            } => self.convert_reference(metadata, reference),
372
373            // Accept references that... for some reason... include the type.
374            // TODO this could be generalized to validate any redundant
375            // validation here or could be used to compute a new, more
376            // constrained type.
377            // TODO the strictest interpretation might be to ignore any fields
378            // that appear alongside "$ref" per
379            // https://json-schema.org/understanding-json-schema/structuring.html#ref
380            SchemaObject {
381                metadata,
382                instance_type,
383                format: None,
384                enum_values: None,
385                const_value: None,
386                subschemas: None,
387                number: None,
388                string: None,
389                array: None,
390                object: None,
391                reference: Some(reference),
392                extensions: _,
393            } => {
394                let ref_schema = self.definitions.get(&ref_key(reference)).unwrap();
395                assert!(matches!(ref_schema, Schema::Object(SchemaObject {
396                        instance_type: it, ..
397                    }) if instance_type == it));
398
399                self.convert_reference(metadata, reference)
400            }
401
402            SchemaObject {
403                metadata,
404                instance_type: _,
405                format: _,
406                enum_values: _,
407                const_value: _,
408                subschemas: _,
409                number: _,
410                string: _,
411                array: _,
412                object: _,
413                reference: Some(reference),
414                extensions: _,
415            } => {
416                let mut def = self.definitions.get(&ref_key(reference)).unwrap();
417                let mut new_schema = Schema::Object(SchemaObject {
418                    reference: None,
419                    ..schema.clone()
420                });
421                while let Schema::Object(SchemaObject { reference: r, .. }) = def {
422                    let schema_only_ref = Schema::Object(SchemaObject {
423                        reference: r.clone(),
424                        ..Default::default()
425                    });
426                    let schema_without_ref = Schema::Object(SchemaObject {
427                        reference: None,
428                        ..def.clone().into_object()
429                    });
430                    new_schema = merge_all(
431                        &[schema_without_ref, schema_only_ref, new_schema],
432                        &self.definitions,
433                    );
434                    if let Some(r) = r {
435                        def = self.definitions.get(&ref_key(r)).unwrap();
436                    } else {
437                        break;
438                    }
439                }
440                let (type_entry, _) = self.convert_schema(type_name, &new_schema).unwrap();
441                Ok((type_entry, metadata))
442            }
443
444            // Enum of a single, known, non-String type (strings above).
445            SchemaObject {
446                instance_type: Some(SingleOrVec::Single(_)),
447                enum_values: Some(enum_values),
448                ..
449            } => self.convert_typed_enum(type_name, original_schema, schema, enum_values),
450
451            // Enum of unknown type
452            SchemaObject {
453                metadata,
454                instance_type: None,
455                format: None,
456                enum_values: Some(enum_values),
457                const_value: None,
458                subschemas: None,
459                number: None,
460                string: None,
461                array: None,
462                object: None,
463                reference: None,
464                extensions: _,
465            } => self.convert_unknown_enum(type_name, original_schema, metadata, enum_values),
466
467            // Subschemas
468            SchemaObject {
469                metadata,
470                // TODO we probably shouldn't ignore this...
471                instance_type: _,
472                format: None,
473                enum_values: None,
474                const_value: None,
475                subschemas: Some(subschemas),
476                number: None,
477                string: None,
478                array: None,
479                object: None,
480                reference: None,
481                extensions: _,
482            } => match subschemas.as_ref() {
483                SubschemaValidation {
484                    all_of: Some(subschemas),
485                    any_of: None,
486                    one_of: None,
487                    not: None,
488                    if_schema: None,
489                    then_schema: None,
490                    else_schema: None,
491                } => self.convert_all_of(type_name, original_schema, metadata, subschemas),
492                SubschemaValidation {
493                    all_of: None,
494                    any_of: Some(subschemas),
495                    one_of: None,
496                    not: None,
497                    if_schema: None,
498                    then_schema: None,
499                    else_schema: None,
500                } => self.convert_any_of(type_name, original_schema, metadata, subschemas),
501                SubschemaValidation {
502                    all_of: None,
503                    any_of: None,
504                    one_of: Some(subschemas),
505                    not: None,
506                    if_schema: None,
507                    then_schema: None,
508                    else_schema: None,
509                } => self.convert_one_of(type_name, original_schema, metadata, subschemas),
510                SubschemaValidation {
511                    all_of: None,
512                    any_of: None,
513                    one_of: None,
514                    not: Some(subschema),
515                    if_schema: None,
516                    then_schema: None,
517                    else_schema: None,
518                } => self.convert_not(type_name, original_schema, metadata, subschema),
519
520                // Multiple subschemas may be present at the same time; attempt
521                // to merge and then convert.
522                subschemas => {
523                    // Remove the subschemas so we can merge into the rest.
524                    let schema_object = SchemaObject {
525                        subschemas: None,
526                        ..schema.clone()
527                    };
528                    let merged_schema = try_merge_with_subschemas(
529                        schema_object,
530                        Some(subschemas),
531                        &self.definitions,
532                    );
533                    match merged_schema {
534                        Ok(s) => {
535                            let (type_entry, _) =
536                                self.convert_schema_object(type_name, original_schema, &s)?;
537                            Ok((type_entry, &None))
538                        }
539                        // An error indicates that the schema is unresolvable.
540                        Err(_) => self.convert_never(type_name, original_schema),
541                    }
542                }
543            },
544
545            // Subschemas with other stuff.
546            SchemaObject {
547                metadata,
548                subschemas: subschemas @ Some(_),
549                ..
550            } => {
551                let without_subschemas = SchemaObject {
552                    subschemas: None,
553                    metadata: None,
554                    ..schema.clone()
555                };
556                debug!(
557                    "pre merged schema {}",
558                    serde_json::to_string_pretty(schema).unwrap(),
559                );
560                match try_merge_with_subschemas(
561                    without_subschemas,
562                    subschemas.as_deref(),
563                    &self.definitions,
564                ) {
565                    Ok(merged_schema) => {
566                        // Preserve metadata from the outer schema.
567                        let merged_schema = SchemaObject {
568                            metadata: metadata.clone(),
569                            ..merged_schema
570                        };
571                        debug!(
572                            "merged schema {}",
573                            serde_json::to_string_pretty(&merged_schema).unwrap(),
574                        );
575
576                        let (type_entry, _) =
577                            self.convert_schema_object(type_name, original_schema, &merged_schema)?;
578                        Ok((type_entry, &None))
579                    }
580
581                    Err(_) => self.convert_never(type_name, original_schema),
582                }
583            }
584
585            // TODO let's not bother with const values at the moment. In the
586            // future we could create types that have a single value with a
587            // newtype wrapper, but it's too much of a mess for too little
588            // value at the moment. Instead, we act as though this const_value
589            // field were None.
590            SchemaObject {
591                metadata,
592                const_value: Some(_),
593                ..
594            } => {
595                let new_schema = SchemaObject {
596                    const_value: None,
597                    ..schema.clone()
598                };
599                self.convert_schema_object(type_name, original_schema, &new_schema)
600                    .map(|(te, m)| match m {
601                        Some(_) if m == metadata => (te, metadata),
602                        Some(_) => panic!("unexpected metadata value"),
603                        None => (te, &None),
604                    })
605            }
606
607            // In actual, not-made-up, in-the-wild specs, I've seen the type
608            // field enumerate all possibilities... perhaps to emphasize their
609            // seriousness of the schema representing **anything**. In that
610            // case, we can strip it out and try again.
611            SchemaObject {
612                instance_type: Some(SingleOrVec::Vec(instance_types)),
613                metadata,
614                ..
615            } if instance_types.contains(&InstanceType::Null)
616                && instance_types.contains(&InstanceType::Boolean)
617                && instance_types.contains(&InstanceType::Object)
618                && instance_types.contains(&InstanceType::Array)
619                && instance_types.contains(&InstanceType::Number)
620                && instance_types.contains(&InstanceType::String)
621                && instance_types.contains(&InstanceType::Integer) =>
622            {
623                let (type_entry, _) = self.convert_schema_object(
624                    type_name,
625                    original_schema,
626                    &SchemaObject {
627                        instance_type: None,
628                        ..schema.clone()
629                    },
630                )?;
631                Ok((type_entry, metadata))
632            }
633
634            // Treat a singleton type array like a singleton type.
635            SchemaObject {
636                metadata,
637                instance_type: Some(SingleOrVec::Vec(instance_types)),
638                ..
639            } if instance_types.len() == 1 => {
640                let [it] = instance_types.as_slice() else {
641                    unreachable!()
642                };
643                let (type_entry, _) = self.convert_schema_object(
644                    type_name,
645                    original_schema,
646                    &SchemaObject {
647                        instance_type: Some(SingleOrVec::Single((*it).into())),
648                        ..schema.clone()
649                    },
650                )?;
651                Ok((type_entry, metadata))
652            }
653
654            // Turn schemas with multiple types into an untagged enum labeled
655            // according to the given type. We associate any validation with
656            // the appropriate type. Note that the case of a 2-type list with
657            // one of them Null is already handled more specifically above (and
658            // rendered into an Option type).
659            SchemaObject {
660                metadata,
661                instance_type: Some(SingleOrVec::Vec(instance_types)),
662                format,
663                enum_values: None,
664                const_value: None,
665                subschemas: None,
666                number,
667                string,
668                array,
669                object,
670                reference: None,
671                extensions: _,
672            } => {
673                // Eliminate duplicates (they hold no significance); they
674                // aren't supposed to be there, but we can still handle it.
675                let unique_types = instance_types.iter().collect::<BTreeSet<_>>();
676
677                // Massage the data into labeled subschemas with the following
678                // format:
679                //
680                // {
681                //     "title": <instance type name>,
682                //     "allOf": [
683                //         {
684                //             "type": <instance type>,
685                //             <validation relevant to the type>
686                //         }
687                //     ]
688                // }
689                //
690                // We can then take these and construct an untagged enum. The
691                // outer "allOf" schema lets name the variant.
692                //
693                // Note that we *could* simply copy the full schema, trusting
694                // recursive calls to pull out the appropriate components...
695                // but why do tomorrow what we could easily to today?
696                let subschemas = unique_types
697                    .into_iter()
698                    .map(|it| {
699                        let instance_type = Some(SingleOrVec::Single(Box::new(*it)));
700                        let (label, inner_schema) = match it {
701                            InstanceType::Null => (
702                                "null",
703                                SchemaObject {
704                                    instance_type,
705                                    ..Default::default()
706                                },
707                            ),
708                            InstanceType::Boolean => (
709                                "boolean",
710                                SchemaObject {
711                                    instance_type,
712                                    ..Default::default()
713                                },
714                            ),
715                            InstanceType::Object => (
716                                "object",
717                                SchemaObject {
718                                    instance_type,
719                                    object: object.clone(),
720                                    ..Default::default()
721                                },
722                            ),
723                            InstanceType::Array => (
724                                "array",
725                                SchemaObject {
726                                    instance_type,
727                                    array: array.clone(),
728                                    ..Default::default()
729                                },
730                            ),
731                            InstanceType::Number => (
732                                "number",
733                                SchemaObject {
734                                    instance_type,
735                                    format: format.clone(),
736                                    number: number.clone(),
737                                    ..Default::default()
738                                },
739                            ),
740                            InstanceType::String => (
741                                "string",
742                                SchemaObject {
743                                    instance_type,
744                                    format: format.clone(),
745                                    string: string.clone(),
746                                    ..Default::default()
747                                },
748                            ),
749                            InstanceType::Integer => (
750                                "integer",
751                                SchemaObject {
752                                    instance_type,
753                                    format: format.clone(),
754                                    number: number.clone(),
755                                    ..Default::default()
756                                },
757                            ),
758                        };
759                        // Make the wrapping schema.
760                        Schema::Object(SchemaObject {
761                            metadata: Some(Box::new(Metadata {
762                                title: Some(label.to_string()),
763                                ..Default::default()
764                            })),
765                            subschemas: Some(Box::new(SubschemaValidation {
766                                all_of: Some(vec![inner_schema.into()]),
767                                ..Default::default()
768                            })),
769                            ..Default::default()
770                        })
771                    })
772                    .collect::<Vec<_>>();
773
774                let type_entry =
775                    self.untagged_enum(type_name, original_schema, metadata, &subschemas)?;
776                Ok((type_entry, metadata))
777            }
778
779            // Unknown
780            SchemaObject { .. } => todo!(
781                "invalid (or unexpected) schema:\n{}",
782                serde_json::to_string_pretty(schema).unwrap()
783            ),
784        }
785    }
786
787    fn convert_string<'a>(
788        &mut self,
789        type_name: Name,
790        original_schema: &'a Schema,
791        metadata: &'a Option<Box<Metadata>>,
792        format: &Option<String>,
793        validation: Option<&StringValidation>,
794    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
795        match format.as_ref().map(String::as_str) {
796            None => match validation {
797                // It should not be possible for the StringValidation to be
798                // Some, but all its fields to be None, but... just to be sure.
799                None
800                | Some(schemars::schema::StringValidation {
801                    max_length: None,
802                    min_length: None,
803                    pattern: None,
804                }) => Ok((TypeEntryDetails::String.into(), metadata)),
805
806                Some(validation) => {
807                    if let Some(pattern) = &validation.pattern {
808                        let _ = regress::Regex::new(pattern).map_err(|e| Error::InvalidSchema {
809                            type_name: type_name.clone().into_option(),
810                            reason: format!("invalid pattern '{}' {}", pattern, e),
811                        })?;
812                        self.uses_regress = true;
813                    }
814
815                    let string = TypeEntryDetails::String.into();
816                    let type_id = self.assign_type(string);
817                    Ok((
818                        TypeEntryNewtype::from_metadata_with_string_validation(
819                            self,
820                            type_name,
821                            metadata,
822                            type_id,
823                            validation,
824                            original_schema.clone(),
825                        ),
826                        metadata,
827                    ))
828                }
829            },
830
831            Some("uuid") => {
832                self.uses_uuid = true;
833                Ok((
834                    TypeEntry::new_native(
835                        "::uuid::Uuid",
836                        &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
837                    ),
838                    metadata,
839                ))
840            }
841
842            Some("date") => {
843                self.uses_chrono = true;
844                Ok((
845                    TypeEntry::new_native(
846                        "::chrono::naive::NaiveDate",
847                        &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
848                    ),
849                    metadata,
850                ))
851            }
852            Some("date-time") => {
853                self.uses_chrono = true;
854                Ok((
855                    TypeEntry::new_native(
856                        "::chrono::DateTime<::chrono::offset::Utc>",
857                        &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
858                    ),
859                    metadata,
860                ))
861            }
862
863            Some("ip") => Ok((
864                TypeEntry::new_native(
865                    "::std::net::IpAddr",
866                    &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
867                ),
868                metadata,
869            )),
870            Some("ipv4") => Ok((
871                TypeEntry::new_native(
872                    "::std::net::Ipv4Addr",
873                    &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
874                ),
875                metadata,
876            )),
877            Some("ipv6") => Ok((
878                TypeEntry::new_native(
879                    "::std::net::Ipv6Addr",
880                    &[TypeSpaceImpl::Display, TypeSpaceImpl::FromStr],
881                ),
882                metadata,
883            )),
884
885            Some(unhandled) => {
886                info!("treating a string format '{}' as a String", unhandled);
887                Ok((TypeEntryDetails::String.into(), metadata))
888            }
889        }
890    }
891
892    pub(crate) fn convert_enum_string<'a>(
893        &mut self,
894        type_name: Name,
895        original_schema: &'a Schema,
896        metadata: &'a Option<Box<Metadata>>,
897        enum_values: &[serde_json::Value],
898        validation: Option<&StringValidation>,
899    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
900        // We expect all enum values to be either a string **or** a null. We
901        // gather them all up and then choose to either be an enum of simple
902        // variants, or an Option of an enum of string variants depending on if
903        // a null is absent or present. Note that it's actually invalid JSON
904        // Schema if we do see a null here. In this code path the instance
905        // types should exclusively be "string" making null invalid. We
906        // intentionally handle instance types of ["string", "null"] prior to
907        // this case and strip out the null in both enum values and instance
908        // type. Nevertheless, we do our best to interpret even incorrect
909        // JSON schema.
910        let mut has_null = false;
911
912        let validator = StringValidator::new(&type_name, validation)?;
913
914        let variants = enum_values
915            .iter()
916            .flat_map(|value| match value {
917                // It would be odd to have multiple null values, but we don't
918                // need to worry about it.
919                serde_json::Value::Null => {
920                    has_null = true;
921                    None
922                }
923                serde_json::Value::String(variant_name) if validator.is_valid(variant_name) => {
924                    Some(Ok(Variant::new(
925                        variant_name.clone(),
926                        None,
927                        VariantDetails::Simple,
928                    )))
929                }
930
931                // Ignore enum variants whose strings don't match the given
932                // constraints. If we wanted to get fancy we could include
933                // these variants in the enum but exclude them from the FromStr
934                // conversion... but that seems like unnecessary swag.
935                serde_json::Value::String(_) => None,
936
937                _ => Some(Err(Error::BadValue("string".to_string(), value.clone()))),
938            })
939            .collect::<Result<Vec<Variant>>>()?;
940
941        if variants.is_empty() {
942            if has_null {
943                self.convert_null(metadata)
944            } else {
945                self.convert_never(type_name, original_schema)
946            }
947        } else {
948            let mut ty = TypeEntryEnum::from_metadata(
949                self,
950                type_name,
951                metadata,
952                EnumTagType::External,
953                variants,
954                false,
955                original_schema.clone(),
956            );
957
958            if has_null {
959                ty = self.type_to_option(ty);
960            }
961
962            Ok((ty, metadata))
963        }
964    }
965
966    fn convert_integer<'a>(
967        &self,
968        metadata: &'a Option<Box<Metadata>>,
969        validation: &Option<Box<schemars::schema::NumberValidation>>,
970        format: &Option<String>,
971    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
972        let (mut min, mut max, multiple) = if let Some(validation) = validation {
973            let min = match (&validation.minimum, &validation.exclusive_minimum) {
974                (None, None) => None,
975                (None, Some(value)) => Some(value + 1.0),
976                (Some(value), None) => Some(*value),
977                (Some(min), Some(emin)) => Some(min.max(emin + 1.0)),
978            };
979            let max = match (&validation.maximum, &validation.exclusive_maximum) {
980                (None, None) => None,
981                (None, Some(value)) => Some(value - 1.0),
982                (Some(value), None) => Some(*value),
983                (Some(max), Some(emax)) => Some(max.min(emax - 1.0)),
984            };
985            (min, max, validation.multiple_of)
986        } else {
987            (None, None, None)
988        };
989
990        // Ordered from most- to least-restrictive.
991        // JSONSchema format, Rust Type, Rust NonZero Type, Rust type min, Rust type max
992        let formats: &[(&str, &str, &str, f64, f64)] = &[
993            (
994                "int8",
995                "i8",
996                "::std::num::NonZeroU8",
997                i8::MIN as f64,
998                i8::MAX as f64,
999            ),
1000            (
1001                "uint8",
1002                "u8",
1003                "::std::num::NonZeroU8",
1004                u8::MIN as f64,
1005                u8::MAX as f64,
1006            ),
1007            (
1008                "int16",
1009                "i16",
1010                "::std::num::NonZeroU16",
1011                i16::MIN as f64,
1012                i16::MAX as f64,
1013            ),
1014            (
1015                "uint16",
1016                "u16",
1017                "::std::num::NonZeroU16",
1018                u16::MIN as f64,
1019                u16::MAX as f64,
1020            ),
1021            (
1022                "int",
1023                "i32",
1024                "::std::num::NonZeroU32",
1025                i32::MIN as f64,
1026                i32::MAX as f64,
1027            ),
1028            (
1029                "int32",
1030                "i32",
1031                "::std::num::NonZeroU32",
1032                i32::MIN as f64,
1033                i32::MAX as f64,
1034            ),
1035            (
1036                "uint",
1037                "u32",
1038                "::std::num::NonZeroU32",
1039                u32::MIN as f64,
1040                u32::MAX as f64,
1041            ),
1042            (
1043                "uint32",
1044                "u32",
1045                "::std::num::NonZeroU32",
1046                u32::MIN as f64,
1047                u32::MAX as f64,
1048            ),
1049            // TODO all these are wrong as casting to an f64 loses precision.
1050            // However, schemars stores everything as an f64 so... meh for now.
1051            (
1052                "int64",
1053                "i64",
1054                "::std::num::NonZeroU64",
1055                i64::MIN as f64,
1056                i64::MAX as f64,
1057            ),
1058            (
1059                "uint64",
1060                "u64",
1061                "::std::num::NonZeroU64",
1062                u64::MIN as f64,
1063                u64::MAX as f64,
1064            ),
1065        ];
1066
1067        if let Some(format) = format {
1068            if let Some((_fmt, ty, nz_ty, imin, imax)) = formats
1069                .iter()
1070                .find(|(int_format, _, _, _, _)| int_format == format)
1071            {
1072                let valid_min = min.is_none() || min.map(|fmin| fmin.ge(imin)).unwrap_or(false);
1073                let valid_max = max.is_none() || max.map(|fmax| fmax.le(imax)).unwrap_or(false);
1074                if multiple.is_none() && valid_min && valid_max {
1075                    // If there's a default value and it's either not a number
1076                    // or outside of the range for this format, return an
1077                    // error.
1078                    if let Some(default) = metadata
1079                        .as_ref()
1080                        .and_then(|m| m.default.as_ref())
1081                        .and_then(|v| v.as_f64())
1082                    {
1083                        if default < *imin || default > *imax {
1084                            return Err(Error::InvalidValue);
1085                        }
1086                    }
1087
1088                    // Use NonZero types for minimum 1
1089                    if min == Some(1.) {
1090                        return Ok((TypeEntry::new_integer(nz_ty), metadata));
1091                    } else {
1092                        return Ok((TypeEntry::new_integer(ty), metadata));
1093                    }
1094                }
1095
1096                if min.is_none() {
1097                    min = Some(*imin);
1098                }
1099                if max.is_none() {
1100                    max = Some(*imax);
1101                }
1102            }
1103        }
1104
1105        // We check the default value here since we have the min and max
1106        // close at hand.
1107        if let Some(default) = metadata.as_ref().and_then(|m| m.default.as_ref()) {
1108            // TODO it's imprecise (in every sense of the word) to use an
1109            // f64 here, but we're already constrained by the schemars
1110            // representation so ... it's probably the best we can do at
1111            // the moment.
1112            match (default.as_f64(), min, max) {
1113                (Some(_), None, None) => Some(()),
1114                (Some(value), None, Some(fmax)) if value <= fmax => Some(()),
1115                (Some(value), Some(fmin), None) if value >= fmin => Some(()),
1116                (Some(value), Some(fmin), Some(fmax)) if value >= fmin && value <= fmax => Some(()),
1117                _ => None,
1118            }
1119            .ok_or(Error::InvalidValue)?;
1120        }
1121
1122        // See if the value bounds fit within a known type.
1123        let maybe_type = match (min, max) {
1124            (None, Some(max)) => formats.iter().rev().find_map(|(_, ty, _nz_ty, _, imax)| {
1125                if (imax - max).abs() <= f64::EPSILON {
1126                    Some(ty.to_string())
1127                } else {
1128                    None
1129                }
1130            }),
1131            (Some(min), None) => formats.iter().rev().find_map(|(_, ty, nz_ty, imin, _)| {
1132                if min == 1. {
1133                    Some(nz_ty.to_string())
1134                } else if (imin - min).abs() <= f64::EPSILON {
1135                    Some(ty.to_string())
1136                } else {
1137                    None
1138                }
1139            }),
1140            (Some(min), Some(max)) => {
1141                formats.iter().rev().find_map(|(_, ty, nz_ty, imin, imax)| {
1142                    if min == 1. {
1143                        Some(nz_ty.to_string())
1144                    } else if (imax - max).abs() <= f64::EPSILON
1145                        && (imin - min).abs() <= f64::EPSILON
1146                    {
1147                        Some(ty.to_string())
1148                    } else {
1149                        None
1150                    }
1151                })
1152            }
1153            (None, None) => None,
1154        };
1155
1156        // TODO we should do something with `multiple`
1157        if let Some(ty) = maybe_type {
1158            Ok((TypeEntry::new_integer(ty), metadata))
1159        } else {
1160            // TODO we could construct a type that itself enforces the various
1161            // bounds.
1162            // TODO failing that, we should find the type that most tightly
1163            // matches these bounds.
1164            Ok((TypeEntry::new_integer("i64"), metadata))
1165        }
1166    }
1167
1168    // TODO deal with metadata
1169    fn convert_number<'a>(
1170        &self,
1171        _metadata: &'a Option<Box<Metadata>>,
1172        _validation: &Option<Box<schemars::schema::NumberValidation>>,
1173        format: &Option<String>,
1174    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1175        /*
1176        See https://github.com/oxidecomputer/typify/issues/169
1177        if let Some(validation) = validation {
1178            assert!(validation.multiple_of.is_none());
1179            assert!(validation.maximum.is_none());
1180            assert!(validation.exclusive_maximum.is_none());
1181            assert!(validation.minimum.is_none());
1182            assert!(validation.exclusive_minimum.is_none());
1183        }
1184        */
1185
1186        match format.as_deref() {
1187            Some("float") => Ok((TypeEntry::new_float("f32"), &None)),
1188            _ => Ok((TypeEntry::new_float("f64"), &None)),
1189        }
1190    }
1191
1192    /// If we have a schema that's just the Null instance type, it represents a
1193    /// solitary value so we model that with the unit type.
1194    fn convert_null<'a>(
1195        &self,
1196        metadata: &'a Option<Box<Metadata>>,
1197    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1198        Ok((TypeEntryDetails::Unit.into(), metadata))
1199    }
1200
1201    /// Determine whether a schema's property name validation constraints can be handled
1202    fn can_handle_pattern_properties(validation: &ObjectValidation) -> bool {
1203        if !validation.required.is_empty() {
1204            return false;
1205        }
1206
1207        if !validation.properties.is_empty() {
1208            return false;
1209        }
1210
1211        // Ensure we have at least one pattern property and all pattern property
1212        // schemas are the same
1213        let Some(first_schema) = validation.pattern_properties.values().next() else {
1214            return false;
1215        };
1216
1217        if !validation
1218            .pattern_properties
1219            .values()
1220            .all(|schema| schema == first_schema)
1221        {
1222            return false;
1223        }
1224
1225        // Ensure any additional properties are a false or null schema
1226        if validation.additional_properties.as_ref().map(AsRef::as_ref) == Some(&Schema::Bool(true))
1227            || matches!(
1228                validation.additional_properties.as_ref().map(AsRef::as_ref),
1229                Some(&Schema::Object(_))
1230            )
1231        {
1232            return false;
1233        }
1234
1235        // Ensure there are no additional property names constraints, to avoid a
1236        // collision between different types of constraints interacting unexpectedly
1237        if validation.property_names.is_some() {
1238            return false;
1239        }
1240
1241        true
1242    }
1243
1244    fn convert_object<'a>(
1245        &mut self,
1246        type_name: Name,
1247        original_schema: &'a Schema,
1248        metadata: &'a Option<Box<Metadata>>,
1249        validation: &Option<Box<ObjectValidation>>,
1250    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1251        match validation.as_ref().map(Box::as_ref) {
1252            // Maps have an empty properties set, and a non-null schema for the
1253            // additional_properties field.
1254            Some(ObjectValidation {
1255                max_properties: _,
1256                min_properties: _,
1257                required,
1258                properties,
1259                pattern_properties,
1260                additional_properties,
1261                property_names,
1262            }) if required.is_empty()
1263                && properties.is_empty()
1264                && pattern_properties.is_empty()
1265                && additional_properties.as_ref().map(AsRef::as_ref)
1266                    != Some(&Schema::Bool(false)) =>
1267            {
1268                let type_entry = self.make_map(
1269                    type_name.into_option(),
1270                    property_names,
1271                    additional_properties,
1272                )?;
1273                Ok((type_entry, metadata))
1274            }
1275
1276            Some(validation) if Self::can_handle_pattern_properties(validation) => {
1277                let pattern = validation
1278                    .pattern_properties
1279                    .keys()
1280                    .cloned()
1281                    .collect::<Vec<_>>()
1282                    .join("|");
1283
1284                // Construct a schema to use for property name validation
1285                let property_names = Some(Box::new(Schema::Object(SchemaObject {
1286                    string: Some(Box::new(StringValidation {
1287                        max_length: None,
1288                        min_length: None,
1289                        pattern: Some(pattern),
1290                    })),
1291                    ..Default::default()
1292                })));
1293
1294                // Construct schema to use for property value validation
1295                let additional_properties = Some(Box::new(
1296                    validation
1297                        .pattern_properties
1298                        .values()
1299                        .next()
1300                        .cloned()
1301                        .unwrap_or_else(|| unreachable!("pattern_properties cannot be empty here")),
1302                ));
1303
1304                let type_entry = self.make_map(
1305                    type_name.into_option(),
1306                    &property_names,
1307                    &additional_properties,
1308                )?;
1309
1310                Ok((type_entry, metadata))
1311            }
1312
1313            None => {
1314                let type_entry = self.make_map(type_name.into_option(), &None, &None)?;
1315                Ok((type_entry, metadata))
1316            }
1317
1318            // The typical case
1319            Some(validation) => {
1320                let tmp_type_name = get_type_name(&type_name, metadata);
1321                let (properties, deny_unknown_fields) =
1322                    self.struct_members(tmp_type_name, validation)?;
1323
1324                Ok((
1325                    TypeEntryStruct::from_metadata(
1326                        self,
1327                        type_name,
1328                        metadata,
1329                        properties,
1330                        deny_unknown_fields,
1331                        original_schema.clone(),
1332                    ),
1333                    &None,
1334                ))
1335            }
1336        }
1337    }
1338
1339    fn convert_reference<'a>(
1340        &self,
1341        metadata: &'a Option<Box<Metadata>>,
1342        ref_name: &str,
1343    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1344        if !ref_name.starts_with('#') {
1345            panic!("external references are not supported: {}", ref_name);
1346        }
1347        let key = ref_key(ref_name);
1348        let type_id = self
1349            .ref_to_id
1350            .get(&key)
1351            .unwrap_or_else(|| panic!("$ref {} is missing", ref_name));
1352        Ok((
1353            TypeEntryDetails::Reference(type_id.clone()).into(),
1354            metadata,
1355        ))
1356    }
1357
1358    fn convert_all_of<'a>(
1359        &mut self,
1360        type_name: Name,
1361        original_schema: &'a Schema,
1362        metadata: &'a Option<Box<Metadata>>,
1363        subschemas: &[Schema],
1364    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1365        debug!(
1366            "all_of {}",
1367            serde_json::to_string_pretty(subschemas).unwrap()
1368        );
1369        if let Some(ty) =
1370            self.maybe_singleton_subschema(type_name.clone(), original_schema, subschemas)
1371        {
1372            return Ok((ty, metadata));
1373        }
1374
1375        // In the general case, we merge all schemas in the array. The merged
1376        // schema will reflect all definitions and constraints, the
1377        // intersection of all schemas. For example, it will have the union of
1378        // all properties for an object, recursively merging properties defined
1379        // in multiple schemas; it will have the union of all required object
1380        // properties; and it will enforce the greater of all numeric
1381        // constraints (e.g. the greater of all specified minimum values).
1382        //
1383        // Sometimes merging types will produce a result for which no data is
1384        // valid, the schema is unsatisfiable. Consider this trivial case:
1385        //
1386        // {
1387        //   "allOf": [
1388        //     { "type": "integer", "minimum": 5 },
1389        //     { "type": "integer", "maximum": 3 }
1390        //   ]
1391        // }
1392        //
1393        // No number is >= 5 *and* <= 3! Good luck with that schema! Similarly
1394        // for this case:
1395        //
1396        // {
1397        //   "allOf": [
1398        //     { "type": "string" },
1399        //     { "type": "object" }
1400        //   ]
1401        // }
1402        //
1403        // A value cannot be both a string and an object!
1404        //
1405        // Note that we will effectively "embed" any referenced types. Consider
1406        // a construction like this:
1407        //
1408        // "allOf": [
1409        //     { "$ref": "#/definitions/SuperClass" },
1410        //     { "type": "object", "properties": { "another_prop: {} }}
1411        // ]
1412        //
1413        // The resulting merged schema will include all properties of
1414        // "SuperClass" as well as "another_prop" (which we assume for this
1415        // example to not have been present in the original). This is
1416        // suboptimal in that we would like the generated types to reflect some
1417        // association with the original sub-type.
1418        //
1419        // TODO
1420        // In cases where we have a named type, we would like to provide
1421        // conversion methods to extract the named type from the merged type.
1422        // In the "SuperClass" example above, we would provide an
1423        // Into<SuperClass> implementation to discard the additional properties
1424        // and produce an instance of "SuperClass".
1425        //
1426        // We can do something similar for types that become additionally
1427        // constrained: a field that becomes required can be converted to an
1428        // optional field; a number whose value is limited can be converted to
1429        // the more expansive numeric type.
1430
1431        let merged_schema = merge_all(subschemas, &self.definitions);
1432        if let Schema::Bool(false) = &merged_schema {
1433            self.convert_never(type_name, original_schema)
1434        } else {
1435            let mut merged_schema = merged_schema.into_object();
1436            assert!(merged_schema.metadata.is_none());
1437            merged_schema.metadata = metadata.clone();
1438
1439            let (type_entry, _) =
1440                self.convert_schema_object(type_name, original_schema, &merged_schema)?;
1441            Ok((type_entry, &None))
1442        }
1443    }
1444
1445    fn convert_any_of<'a>(
1446        &mut self,
1447        type_name: Name,
1448        original_schema: &'a Schema,
1449        metadata: &'a Option<Box<Metadata>>,
1450        subschemas: &'a [Schema],
1451    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1452        // Rust can emit "anyOf":[{"$ref":"#/definitions/C"},{"type":"null"}
1453        // for Option. We match this here because the mutual exclusion check
1454        // below may fail for cases such as Option<T> where T is defined to be,
1455        // say, (). In such a case, both variants are actually null.
1456        if let Some(ty) = self.maybe_option(type_name.clone(), metadata, subschemas) {
1457            return Ok((ty, metadata));
1458        }
1459
1460        // Check if this could be more precisely handled as a "one-of". This
1461        // occurs if each subschema is mutually exclusive i.e. so that exactly
1462        // one of them can match.
1463        if all_mutually_exclusive(subschemas, &self.definitions) {
1464            self.convert_one_of(type_name, original_schema, metadata, subschemas)
1465        } else {
1466            // We'll want to build a struct that looks like this:
1467            // struct Name {
1468            //     #[serde(flatten)]
1469            //     schema1: Option<Schema1Type>,
1470            //     #[serde(flatten)]
1471            //     schema2: Option<Schema2Type>,
1472            //     ...
1473            // }
1474
1475            self.flattened_union_struct(type_name, original_schema, metadata, subschemas, true)
1476        }
1477    }
1478
1479    /// A "one of" may reasonably be converted into a Rust enum, but there are
1480    /// several cases to consider:
1481    ///
1482    /// Options expressed as enums are uncommon since { "type": [ "null",
1483    /// "<type>"], ... } is a much simpler construction. Nevertheless, an
1484    /// option may be expressed as a "one of" with two subschemas where one is
1485    /// null.
1486    ///
1487    /// Externally tagged enums are comprised of either an enumerated set of
1488    /// string values or objects that have a single required member. The
1489    /// variant is either the enumerated value with no data or the required
1490    /// member with its type as the associated data. Note that this is the
1491    /// serde default.
1492    ///
1493    /// Internally tagged enums are comprised exclusively of objects where each
1494    /// object has a required property in common and this required property
1495    /// must be a string with a single fixed value. The property becomes the
1496    /// serde tag and the value becomes the variant. Any additional properties
1497    /// on that object become the data associated with the given variant.
1498    ///
1499    /// Adjacently tagged enums are comprised exclusively of objects that have
1500    /// a tag and content field in common (though the content field will only
1501    /// be present for variants that include data). The value of the tag
1502    /// should, as above, be a string with a single enumerated value. The value
1503    /// of the content field, if it exists, becomes the data payload for the
1504    /// variant.
1505    ///
1506    /// Untagged enums intentionally omit a named tag. There are no constraints
1507    /// on untagged enums so this is our fallback if the tagging schemes above
1508    /// don't apply. While untagged enums are not always strictly exclusive by
1509    /// construction, we know that *these* variants must be mutually exclusive
1510    /// if we've ended up here. Note that untagged variants are distinguished
1511    /// by their data, so a single variant may exist with no associated data,
1512    /// but we'd expect that variant to be null or an empty struct. This case
1513    /// requires us to invent variant names since that information is not
1514    /// included in the schema data.
1515    ///
1516    /// Note that the order of checking for tagging schemes must be carefully
1517    /// considered. Adjacent tagging must be checked before internal tagging as
1518    /// the former is a subset of the latter: the content field could be
1519    /// interpreted as a struct variant with a single field:
1520    ///
1521    /// ```ignore
1522    /// enum MyEnum {
1523    ///     Variant1 { content: MyObj },
1524    ///     Variant2 { content: MyObj },
1525    /// }
1526    /// ```
1527    ///
1528    /// Fortunately, external tagging can't be confused with internal or
1529    /// adjacent tagging except in reductive cases such as enums with a single
1530    /// variant.
1531    ///
1532    /// Untagged enums apply to any set of subschemas so must be applied last.
1533    pub(crate) fn convert_one_of<'a>(
1534        &mut self,
1535        type_name: Name,
1536        original_schema: &'a Schema,
1537        metadata: &'a Option<Box<schemars::schema::Metadata>>,
1538        subschemas: &'a [Schema],
1539    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1540        debug!(
1541            "one_of {}",
1542            serde_json::to_string_pretty(subschemas).unwrap()
1543        );
1544
1545        // TODO it would probably be smart to do a pass through the schema
1546        // given to us and either put it into some canonical form or move to
1547        // some sort of intermediate representation.
1548        //
1549        // Each of the enum types does some similar exploration of each
1550        // variant-schema--it should be possible to do that once. In addition
1551        // the various enum types rely on some heuristics around how schemas
1552        // are laid out; it would be nice to eliminate some of the guesswork,
1553        // but putting schemas into a predictable form.
1554
1555        let ty = self
1556            .maybe_option(type_name.clone(), metadata, subschemas)
1557            .or_else(|| {
1558                self.maybe_externally_tagged_enum(
1559                    type_name.clone(),
1560                    original_schema,
1561                    metadata,
1562                    subschemas,
1563                )
1564            })
1565            .or_else(|| {
1566                self.maybe_adjacently_tagged_enum(
1567                    type_name.clone(),
1568                    original_schema,
1569                    metadata,
1570                    subschemas,
1571                )
1572            })
1573            .or_else(|| {
1574                self.maybe_internally_tagged_enum(
1575                    type_name.clone(),
1576                    original_schema,
1577                    metadata,
1578                    subschemas,
1579                )
1580            })
1581            .or_else(|| {
1582                self.maybe_singleton_subschema(type_name.clone(), original_schema, subschemas)
1583            })
1584            .map_or_else(
1585                || self.untagged_enum(type_name, original_schema, metadata, subschemas),
1586                Ok,
1587            )?;
1588
1589        Ok((ty, metadata))
1590    }
1591
1592    /// The "not" construction is pretty challenging to handle in the general
1593    /// case: what is the appropriate rust structure for a type that is merely
1594    /// the exclusion of another? This is tractable, however, in some special
1595    /// cases that occur frequently enough in the wild to consider them.
1596    ///
1597    /// The simplest is for the boolean schemas: true to accept everything;
1598    /// false to accept nothing. These we can simply invert. Why someone would
1599    /// specify a type in this fashion... hard to say.
1600    ///
1601    /// The next we consider is that of enumerated values: if the schema
1602    /// explicitly enumerates its valid values, we can construct a type that
1603    /// disallows those values (just as we have a type that may only be one of
1604    /// several specific values). We either use the specified type (e.g.
1605    /// string) or infer the type from the enumerated values. These are
1606    /// represented as a newtype that contains a deny list (rather than an
1607    /// allow list as is the case for non-string enumerated values).
1608    pub(crate) fn convert_not<'a>(
1609        &mut self,
1610        type_name: Name,
1611        original_schema: &'a Schema,
1612        metadata: &'a Option<Box<schemars::schema::Metadata>>,
1613        subschema: &'a Schema,
1614    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1615        match subschema {
1616            // This is a weird construct, but simple enough to handle.
1617            Schema::Bool(b) => {
1618                let (type_entry, _) = self.convert_schema(type_name, &Schema::Bool(!b))?;
1619                Ok((type_entry, metadata))
1620            }
1621
1622            // The permissive schema is equivalent to the `true` schema.
1623            Schema::Object(SchemaObject {
1624                metadata: _,
1625                instance_type: None,
1626                format: None,
1627                enum_values: None,
1628                const_value: None,
1629                subschemas: None,
1630                number: None,
1631                string: None,
1632                array: None,
1633                object: None,
1634                reference: None,
1635                extensions: _,
1636            }) => {
1637                let (type_entry, _) = self.convert_schema(type_name, &Schema::Bool(false))?;
1638                Ok((type_entry, metadata))
1639            }
1640
1641            // An explicit type and enumerated values.
1642            Schema::Object(
1643                schema @ SchemaObject {
1644                    instance_type: Some(SingleOrVec::Single(_)),
1645                    enum_values: Some(enum_values),
1646                    ..
1647                },
1648            ) => {
1649                let type_schema = SchemaObject {
1650                    enum_values: None,
1651                    ..schema.clone()
1652                };
1653
1654                let (type_entry, _) =
1655                    self.convert_schema_object(Name::Unknown, original_schema, &type_schema)?;
1656
1657                // Make sure all the values are valid.
1658                // TODO this isn't strictly legal since we may not yet have
1659                // resolved references.
1660                enum_values
1661                    .iter()
1662                    .try_for_each(|value| type_entry.validate_value(self, value).map(|_| ()))?;
1663
1664                let type_id = self.assign_type(type_entry);
1665
1666                let newtype_entry = TypeEntryNewtype::from_metadata_with_deny_values(
1667                    self,
1668                    type_name,
1669                    metadata,
1670                    type_id,
1671                    enum_values,
1672                    original_schema.clone(),
1673                );
1674
1675                Ok((newtype_entry, metadata))
1676            }
1677
1678            // No type so we infer it from the values.
1679            Schema::Object(SchemaObject {
1680                metadata,
1681                instance_type: None,
1682                format: None,
1683                enum_values: Some(enum_values),
1684                const_value: None,
1685                subschemas: None,
1686                number: None,
1687                string: None,
1688                array: None,
1689                object: None,
1690                reference: None,
1691                extensions: _,
1692            }) => {
1693                // All the values need to be of the same type.
1694                let instance_types = enum_values
1695                    .iter()
1696                    .map(|v| match v {
1697                        serde_json::Value::Bool(_) => InstanceType::Boolean,
1698                        serde_json::Value::Number(_) => InstanceType::Number,
1699                        serde_json::Value::String(_) => InstanceType::String,
1700
1701                        serde_json::Value::Null
1702                        | serde_json::Value::Array(_)
1703                        | serde_json::Value::Object(_) => {
1704                            panic!("unhandled type for `not` construction: {}", v)
1705                        }
1706                    })
1707                    .collect::<BTreeSet<_>>();
1708
1709                match (instance_types.len(), instance_types.iter().next()) {
1710                    (1, Some(instance_type)) => {
1711                        let typed_schema = SchemaObject {
1712                            instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
1713                                *instance_type,
1714                            ))),
1715                            ..Default::default()
1716                        };
1717
1718                        let (type_entry, _) = self.convert_schema_object(
1719                            Name::Unknown,
1720                            original_schema,
1721                            &typed_schema,
1722                        )?;
1723                        // Make sure all the values are valid.
1724                        // TODO this isn't strictly legal since we may not yet
1725                        // have resolved references.
1726                        enum_values.iter().try_for_each(|value| {
1727                            type_entry.validate_value(self, value).map(|_| ())
1728                        })?;
1729
1730                        let type_id = self.assign_type(type_entry);
1731
1732                        let newtype_entry = TypeEntryNewtype::from_metadata_with_deny_values(
1733                            self,
1734                            type_name,
1735                            metadata,
1736                            type_id,
1737                            enum_values,
1738                            original_schema.clone(),
1739                        );
1740
1741                        Ok((newtype_entry, metadata))
1742                    }
1743
1744                    _ => panic!(
1745                        "multiple implied types for an un-typed enum {:?} {:?}",
1746                        instance_types, enum_values,
1747                    ),
1748                }
1749            }
1750
1751            _ => todo!("unhandled not schema {:#?}", subschema),
1752        }
1753    }
1754
1755    fn convert_array<'a>(
1756        &mut self,
1757        type_name: Name,
1758        metadata: &'a Option<Box<Metadata>>,
1759        validation: &ArrayValidation,
1760    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1761        match validation {
1762            // Tuples and fixed-length arrays satisfy the condition that the
1763            // max and min lengths are equal (and greater than zero). When
1764            // the `item` is an array, we produce a tuple; when it is a single
1765            // element, we produce a fixed-length array.
1766            ArrayValidation {
1767                items,
1768                additional_items,
1769                max_items: Some(max_items),
1770                min_items: Some(min_items),
1771                unique_items: None,
1772                contains: None,
1773            } if max_items == min_items && *max_items > 0 => match items {
1774                // Tuple with fewer types specified than required items.
1775                Some(SingleOrVec::Vec(items)) if items.len() < *max_items as usize => {
1776                    let rest_name = type_name.append("additional");
1777                    let rest_id = if let Some(rest_schema) = additional_items {
1778                        self.id_for_schema(rest_name, rest_schema)?.0
1779                    } else {
1780                        self.id_for_schema(rest_name, &Schema::Bool(true))?.0
1781                    };
1782                    let start = items.iter().enumerate().map(|(ii, item_schema)| {
1783                        let item_name = type_name.append(&format!("item{}", ii));
1784                        Ok(self.id_for_schema(item_name, item_schema)?.0)
1785                    });
1786                    let rest = (items.len()..*max_items as usize).map(|_| Ok(rest_id.clone()));
1787                    let types = start.chain(rest).collect::<Result<Vec<_>>>()?;
1788                    Ok((TypeEntryDetails::Tuple(types).into(), metadata))
1789                }
1790                // Tuple with at least as many items as required.
1791                Some(SingleOrVec::Vec(items)) => {
1792                    let types = items
1793                        .iter()
1794                        .take(*max_items as usize)
1795                        .enumerate()
1796                        .map(|(ii, item_schema)| {
1797                            let item_name = type_name.append(&format!("item{}", ii));
1798                            Ok(self.id_for_schema(item_name, item_schema)?.0)
1799                        })
1800                        .collect::<Result<_>>()?;
1801                    Ok((TypeEntryDetails::Tuple(types).into(), metadata))
1802                }
1803
1804                // Array with a schema for the item.
1805                Some(SingleOrVec::Single(item_schema)) => {
1806                    let item_id = self.id_for_schema(type_name.append("item"), item_schema)?.0;
1807                    Ok((
1808                        TypeEntryDetails::Array(item_id, *max_items as usize).into(),
1809                        metadata,
1810                    ))
1811                }
1812                // Array with no schema for the item.
1813                None => {
1814                    let any_id = self
1815                        .id_for_schema(type_name.append("item"), &Schema::Bool(true))?
1816                        .0;
1817                    Ok((
1818                        TypeEntryDetails::Array(any_id, *max_items as usize).into(),
1819                        metadata,
1820                    ))
1821                }
1822            },
1823
1824            // Arrays and sets.
1825            ArrayValidation {
1826                items: Some(SingleOrVec::Single(item)),
1827                additional_items: _, // By spec: ignored for single items
1828                max_items: _,        // TODO enforce size limitations
1829                min_items: _,        // TODO enforce size limitations
1830                unique_items,
1831                contains: None,
1832            } => {
1833                let item_type_name = match get_type_name(&type_name, metadata) {
1834                    Some(s) => Name::Suggested(format!("{}Item", s)),
1835                    None => Name::Unknown,
1836                };
1837                let (type_id, _) = self.id_for_schema(item_type_name, item.as_ref())?;
1838
1839                // If items are unique, this is a Set; otherwise it's an Array.
1840                match unique_items {
1841                    Some(true) => Ok((TypeEntryDetails::Set(type_id).into(), metadata)),
1842                    _ => Ok((TypeEntryDetails::Vec(type_id).into(), metadata)),
1843                }
1844            }
1845
1846            // Arrays and sets with no specified items.
1847            ArrayValidation {
1848                items: None,
1849                additional_items: _, // By spec: ignored for missing items
1850                max_items: _,        // TODO enforce size limitations
1851                min_items: _,        // TODO enforce size limitations
1852                unique_items,
1853                contains: None,
1854            } => {
1855                self.uses_serde_json = true;
1856                let type_id = self.assign_type(TypeEntryDetails::JsonValue.into());
1857
1858                // If items are unique, this is a Set; otherwise it's an Array.
1859                match unique_items {
1860                    Some(true) => Ok((TypeEntryDetails::Set(type_id).into(), metadata)),
1861                    _ => Ok((TypeEntryDetails::Vec(type_id).into(), metadata)),
1862                }
1863            }
1864
1865            _ => Err(Error::InvalidSchema {
1866                type_name: type_name.into_option(),
1867                reason: format!("unhandled array validation {:#?}", validation),
1868            }),
1869        }
1870    }
1871
1872    fn convert_array_of_any<'a>(
1873        &mut self,
1874        metadata: &'a Option<Box<Metadata>>,
1875    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1876        self.uses_serde_json = true;
1877        let type_id = self.assign_type(TypeEntryDetails::JsonValue.into());
1878        Ok((TypeEntryDetails::Vec(type_id).into(), metadata))
1879    }
1880
1881    // TODO not sure if I want to deal with enum_values here, but we'll see...
1882    fn convert_bool<'a>(
1883        &self,
1884        metadata: &'a Option<Box<Metadata>>,
1885    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1886        Ok((TypeEntry::new_boolean(), metadata))
1887    }
1888
1889    fn convert_permissive<'a>(
1890        &mut self,
1891        metadata: &'a Option<Box<Metadata>>,
1892    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1893        self.uses_serde_json = true;
1894        Ok((TypeEntryDetails::JsonValue.into(), metadata))
1895    }
1896
1897    fn convert_never<'a>(
1898        &mut self,
1899        type_name: Name,
1900        schema: &'a Schema,
1901    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1902        let ty = TypeEntryEnum::from_metadata(
1903            self,
1904            type_name,
1905            &None,
1906            EnumTagType::External,
1907            vec![],
1908            true,
1909            schema.clone(),
1910        );
1911        Ok((ty, &None))
1912    }
1913
1914    fn convert_typed_enum<'a>(
1915        &mut self,
1916        type_name: Name,
1917        original_schema: &'a Schema,
1918        schema: &'a SchemaObject,
1919        enum_values: &[serde_json::Value],
1920    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1921        let type_schema = SchemaObject {
1922            enum_values: None,
1923            ..schema.clone()
1924        };
1925
1926        let inner_type_name = match get_type_name(&type_name, &schema.metadata) {
1927            Some(s) => Name::Suggested(format!("{}Inner", s)),
1928            None => Name::Unknown,
1929        };
1930
1931        let (type_entry, metadata) =
1932            self.convert_schema_object(inner_type_name, original_schema, &type_schema)?;
1933
1934        // Make sure all the values are valid.
1935        enum_values
1936            .iter()
1937            .try_for_each(|value| type_entry.validate_value(self, value).map(|_| ()))?;
1938
1939        let type_id = self.assign_type(type_entry);
1940
1941        let newtype_entry = TypeEntryNewtype::from_metadata_with_enum_values(
1942            self,
1943            type_name,
1944            metadata,
1945            type_id,
1946            enum_values,
1947            original_schema.clone(),
1948        );
1949
1950        Ok((
1951            newtype_entry,
1952            if metadata.is_some() {
1953                &schema.metadata
1954            } else {
1955                &None
1956            },
1957        ))
1958    }
1959
1960    fn convert_unknown_enum<'a>(
1961        &mut self,
1962        type_name: Name,
1963        original_schema: &'a Schema,
1964        metadata: &'a Option<Box<Metadata>>,
1965        enum_values: &[serde_json::Value],
1966    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
1967        if enum_values.is_empty() {
1968            return self.convert_never(type_name, original_schema);
1969        }
1970
1971        // We're here because the schema didn't have a type; that's a bummer,
1972        // but we'll do our best to roll with the punches.
1973
1974        // Let's hope all these values are the same type.
1975        let mut instance_types = enum_values
1976            .iter()
1977            .map(|v| match v {
1978                serde_json::Value::Null => InstanceType::Null,
1979                serde_json::Value::Bool(_) => InstanceType::Boolean,
1980                serde_json::Value::Number(_) => InstanceType::Number,
1981                serde_json::Value::String(_) => InstanceType::String,
1982                serde_json::Value::Array(_) => InstanceType::Array,
1983                serde_json::Value::Object(_) => InstanceType::Object,
1984            })
1985            .collect::<BTreeSet<_>>();
1986
1987        let has_null = instance_types.remove(&InstanceType::Null);
1988
1989        if has_null {
1990            // If there's a null-value, recur with the null value removed; then
1991            // convert the resulting type to be optional.
1992            let enum_values = enum_values
1993                .iter()
1994                .filter(|v| !v.is_null())
1995                .cloned()
1996                .collect::<Vec<_>>();
1997
1998            if enum_values.is_empty() {
1999                self.convert_null(metadata)
2000            } else {
2001                let (type_entry, metadata) =
2002                    self.convert_unknown_enum(type_name, original_schema, metadata, &enum_values)?;
2003                let type_entry = self.type_to_option(type_entry);
2004                Ok((type_entry, metadata))
2005            }
2006        } else {
2007            match (instance_types.len(), instance_types.iter().next()) {
2008                (1, Some(InstanceType::String)) => self.convert_enum_string(
2009                    type_name,
2010                    original_schema,
2011                    metadata,
2012                    enum_values,
2013                    None,
2014                ),
2015
2016                // TODO We're ignoring enumerated values for the boolean
2017                // type--at least for the moment--because some of the tests
2018                // show that this may require more careful consideration.
2019                (1, Some(InstanceType::Boolean)) => self.convert_bool(metadata),
2020
2021                (1, Some(instance_type)) => {
2022                    let typed_schema = SchemaObject {
2023                        instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
2024                            *instance_type,
2025                        ))),
2026                        ..Default::default()
2027                    };
2028                    let (type_entry, new_metadata) = self.convert_typed_enum(
2029                        type_name,
2030                        original_schema,
2031                        &typed_schema,
2032                        enum_values,
2033                    )?;
2034                    Ok((
2035                        type_entry,
2036                        if new_metadata.is_some() {
2037                            metadata
2038                        } else {
2039                            &None
2040                        },
2041                    ))
2042                }
2043                (1, None) => unreachable!(),
2044                _ => panic!(
2045                    "multiple implied types for an un-typed enum {:?} {:?}",
2046                    instance_types, enum_values,
2047                ),
2048            }
2049        }
2050    }
2051
2052    pub(crate) fn convert_option<'a>(
2053        &mut self,
2054        type_name: Name,
2055        metadata: &'a Option<Box<Metadata>>,
2056        schema: &'_ Schema,
2057    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
2058        let (ty, _) = self.convert_schema(type_name, schema)?;
2059        let ty = self.type_to_option(ty);
2060
2061        Ok((ty, metadata))
2062    }
2063
2064    /// We'll often see this if the subschema was just to provide an additional
2065    /// level for annotation such as a "title" or "description".
2066    pub(crate) fn maybe_singleton_subschema(
2067        &mut self,
2068        type_name: Name,
2069        _original_schema: &Schema,
2070        subschemas: &[Schema],
2071    ) -> Option<TypeEntry> {
2072        match (subschemas.len(), subschemas.first()) {
2073            (1, Some(subschema)) => Some(self.convert_schema(type_name, subschema).ok()?.0),
2074            _ => None,
2075        }
2076    }
2077}
2078
2079#[cfg(test)]
2080mod tests {
2081    use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};
2082
2083    use paste::paste;
2084    use quote::{quote, ToTokens};
2085    use schema::Schema;
2086    use schemars::{
2087        schema::{InstanceType, Metadata, NumberValidation, RootSchema, SchemaObject},
2088        schema_for, JsonSchema,
2089    };
2090    use serde_json::json;
2091
2092    use crate::{
2093        test_util::validate_output, Error, Name, TypeSpace, TypeSpaceImpl, TypeSpaceSettings,
2094    };
2095
2096    #[track_caller]
2097    fn int_helper<T: JsonSchema>(type_name: &'static str) {
2098        let schema = schema_for!(T);
2099
2100        let mut type_space = TypeSpace::default();
2101        type_space
2102            .add_ref_types(schema.definitions.clone())
2103            .unwrap();
2104        let (ty, _) = type_space
2105            .convert_schema_object(
2106                Name::Unknown,
2107                &schemars::schema::Schema::Object(schema.schema.clone()),
2108                &schema.schema,
2109            )
2110            .unwrap();
2111        let output = ty.type_name(&type_space);
2112        let actual = output.split("::").last().unwrap().trim();
2113        let expected = type_name.split("::").last().unwrap();
2114        assert_eq!(actual, expected);
2115    }
2116
2117    macro_rules! int_test {
2118        ($t:ty) => {
2119            paste! {
2120                #[test]
2121                fn [<test_int_ $t:lower>]() {
2122                    int_helper::<$t>(stringify!($t))
2123                }
2124            }
2125        };
2126    }
2127
2128    int_test!(u8);
2129    int_test!(u16);
2130    int_test!(u32);
2131    int_test!(u64);
2132    int_test!(i8);
2133    int_test!(i16);
2134    int_test!(i32);
2135    int_test!(i64);
2136    int_test!(NonZeroU8);
2137    int_test!(NonZeroU16);
2138    int_test!(NonZeroU32);
2139    int_test!(NonZeroU64);
2140
2141    #[test]
2142    fn test_redundant_types() {
2143        #[derive(JsonSchema)]
2144        #[allow(dead_code)]
2145        struct Alphabet {
2146            a: u32,
2147            b: u32,
2148            c: u32,
2149            d: Option<u32>,
2150            e: Option<u32>,
2151            f: (u32, u32, u32, Option<u32>),
2152        }
2153
2154        let schema = schema_for!(Alphabet);
2155
2156        let mut type_space = TypeSpace::default();
2157        type_space
2158            .add_ref_types(schema.definitions.clone())
2159            .unwrap();
2160        let _ = type_space
2161            .add_type_with_name(&schema.schema.into(), Some("Alphabet".to_string()))
2162            .unwrap();
2163
2164        // We expect a total of 4 types:
2165        // 1. u32
2166        // 2. option -> 1
2167        // 3. tuple -> 1, 1, 1, 2
2168        // 4. struct -> 1, 1, 1, 2, 2, 3
2169        assert_eq!(type_space.iter_types().count(), 4);
2170    }
2171
2172    #[test]
2173    fn test_basic_option_flat() {
2174        #[derive(JsonSchema, Schema)]
2175        #[allow(dead_code)]
2176        struct C {}
2177
2178        #[derive(JsonSchema, Schema)]
2179        #[allow(dead_code)]
2180        struct A {
2181            a: Option<C>,
2182        }
2183
2184        validate_output::<A>();
2185    }
2186
2187    #[test]
2188    fn test_unit_option() {
2189        #[derive(JsonSchema, Schema)]
2190        #[allow(dead_code)]
2191        struct Foo;
2192
2193        #[derive(JsonSchema, Schema)]
2194        #[allow(dead_code)]
2195        struct Bar {
2196            a: Option<Foo>,
2197        }
2198
2199        validate_output::<Bar>();
2200    }
2201
2202    #[test]
2203    fn test_low_default() {
2204        let schema = SchemaObject {
2205            instance_type: Some(InstanceType::Integer.into()),
2206            format: Some("uint".to_string()),
2207            metadata: Some(
2208                Metadata {
2209                    default: Some(json!(-1i32)),
2210                    ..Default::default()
2211                }
2212                .into(),
2213            ),
2214            number: Some(NumberValidation::default().into()),
2215            ..Default::default()
2216        };
2217
2218        let mut type_space = TypeSpace::default();
2219        match type_space.convert_schema_object(
2220            Name::Unknown,
2221            &schemars::schema::Schema::Object(schema.clone()),
2222            &schema,
2223        ) {
2224            Err(Error::InvalidValue) => (),
2225            _ => panic!("unexpected result"),
2226        }
2227    }
2228
2229    #[test]
2230    fn test_high_default() {
2231        let schema = SchemaObject {
2232            instance_type: Some(InstanceType::Integer.into()),
2233            metadata: Some(
2234                Metadata {
2235                    default: Some(json!(867_5309_u32)),
2236                    ..Default::default()
2237                }
2238                .into(),
2239            ),
2240            number: Some(
2241                NumberValidation {
2242                    maximum: Some(256.0),
2243                    ..Default::default()
2244                }
2245                .into(),
2246            ),
2247            ..Default::default()
2248        };
2249
2250        let mut type_space = TypeSpace::default();
2251        match type_space.convert_schema_object(
2252            Name::Unknown,
2253            &schemars::schema::Schema::Object(schema.clone()),
2254            &schema,
2255        ) {
2256            Err(Error::InvalidValue) => (),
2257            _ => panic!("unexpected result"),
2258        }
2259    }
2260
2261    #[test]
2262    fn test_null() {
2263        let schema_json = r#"
2264        {
2265            "title": "Null",
2266            "type": "string",
2267            "enum": [null]
2268        }
2269        "#;
2270
2271        let schema: RootSchema = serde_json::from_str(schema_json).unwrap();
2272
2273        let mut type_space = TypeSpace::default();
2274        let _ = type_space.add_type(&schema.schema.into()).unwrap();
2275
2276        let actual = type_space.to_stream();
2277        let file = syn::parse2::<syn::File>(actual).expect("type space should emit a valid file");
2278        match file.items.as_slice() {
2279            [syn::Item::Mod(error)] if error.ident == "error" => {}
2280            _ => panic!("unexpected file contents {}", file.to_token_stream()),
2281        }
2282    }
2283
2284    #[test]
2285    fn test_overridden_conversion() {
2286        let schema_json = r#"
2287        {
2288            "description": "don't let this fool you",
2289            "type": "string",
2290            "format": "uuid"
2291        }
2292        "#;
2293
2294        let schema: RootSchema = serde_json::from_str(schema_json).unwrap();
2295
2296        let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_conversion(
2297            SchemaObject {
2298                instance_type: Some(InstanceType::String.into()),
2299                format: Some("uuid".to_string()),
2300                ..Default::default()
2301            },
2302            "not::a::real::library::Uuid",
2303            [TypeSpaceImpl::Display].into_iter(),
2304        ));
2305        let type_id = type_space.add_type(&schema.schema.into()).unwrap();
2306        let typ = type_space.get_type(&type_id).unwrap();
2307
2308        let actual = typ.ident();
2309        let expected = quote! { not::a::real::library::Uuid };
2310        assert_eq!(actual.to_string(), expected.to_string());
2311    }
2312}