Repository: oxidecomputer/typify (rust)

Issue:
JSON Schema fails to codegen with "called `Option::unwrap()` on a `None` value"
When trying to typify the [PG-JSONL schema](https://raw.githubusercontent.com/pg-format/specification/main/schema/pg-jsonl.json), the following error is throw:

```
❯ RUST_BACKTRACE=full cargo typify pg-jsonl.json
The application panicked (crashed).
Message:  called `Option::unwrap()` on a `None` value
Location: /Users/inakiarango/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/typify-impl-0.4.0/src/type_entry.rs:365

  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                                ⋮ 9 frames hidden ⋮                               
  10: core::panicking::panic::h3ce9043ca357f318
      at <unknown source file>:<unknown line>
  11: core::option::unwrap_failed::h84f662fc2717d129
      at <unknown source file>:<unknown line>
  12: typify_impl::type_entry::TypeEntryStruct::from_metadata::h2933f54c4b3a6b21
      at <unknown source file>:<unknown line>
  13: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_any_of::h653428b8fa2a19e4
      at <unknown source file>:<unknown line>
  14: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema_object::h5e09a9039efc668c
      at <unknown source file>:<unknown line>
  15: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema::hd3d6f34ba7400a7f
      at <unknown source file>:<unknown line>
  16: typify_impl::TypeSpace::id_for_schema::h04ce706d47c27b97
      at <unknown source file>:<unknown line>
  17: typify_impl::structs::<impl typify_impl::TypeSpace>::struct_property::hd6813ba27f7d8966
      at <unknown source file>:<unknown line>
  18: core::iter::adapters::filter_map::filter_map_try_fold::{{closure}}::h4eb2ed510d10220c
      at <unknown source file>:<unknown line>
  19: <core::iter::adapters::chain::Chain<A,B> as core::iter::traits::iterator::Iterator>::try_fold::h86d852bf637287ab
      at <unknown source file>:<unknown line>
  20: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter::hd44877539127a5fb
      at <unknown source file>:<unknown line>
  21: core::iter::adapters::try_process::hd47a9e8cf09eba52
      at <unknown source file>:<unknown line>
  22: typify_impl::structs::<impl typify_impl::TypeSpace>::struct_members::h2e3be4c7e7da6141
      at <unknown source file>:<unknown line>
  23: typify_impl::enums::<impl typify_impl::TypeSpace>::internal_variant::hcad4a8f320e0171f
      at <unknown source file>:<unknown line>
  24: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::try_fold::hcaa4a3cf4f44ee98
      at <unknown source file>:<unknown line>
  25: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter::h8f60e5c9d497918e
      at <unknown source file>:<unknown line>
  26: core::iter::adapters::try_process::hbdff5e1d285b6834
      at <unknown source file>:<unknown line>
  27: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_one_of::h2da4d9ac006a14b5
      at <unknown source file>:<unknown line>
  28: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema_object::h5e09a9039efc668c
      at <unknown source file>:<unknown line>
  29: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema::hd3d6f34ba7400a7f
      at <unknown source file>:<unknown line>
  30: typify_impl::TypeSpace::convert_ref_type::h642858592f31d108
      at <unknown source file>:<unknown line>
  31: typify_impl::TypeSpace::add_root_schema::hb96f45d1715d6ee4
      at <unknown source file>:<unknown line>
  32: cargo_typify::convert::hc5805e08a0626ee6
      at <unknown source file>:<unknown line>
  33: cargo_typify::main::ha92ae23742a161d4
      at <unknown source file>:<unknown line>
  34: std::sys::backtrace::__rust_begin_short_backtrace::hbc92dc7fcad25bc5
      at <unknown source file>:<unknown line>
  35: std::rt::lang_start::{{closure}}::hc5e84ccb7e92da85
      at <unknown source file>:<unknown line>
  36: std::rt::lang_start_internal::h95cf27b851151b9c
      at <unknown source file>:<unknown line>
  37: _main<unknown>
      at <unknown source file>:<unknown line>

Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
```

The line being referenced appears to be the following one:

```rust
impl TypeEntryStruct {
    pub(crate) fn from_metadata(
        type_space: &TypeSpace,
        type_name: Name,
        metadata: &Option<Box<Metadata>>,
        properties: Vec<StructProperty>,
        deny_unknown_fields: bool,
        schema: Schema,
    ) -> TypeEntry {
        let name = get_type_name(&type_name, metadata).unwrap(); // This one right here!
        let rename = None;
        let description = metadata_description(metadata);
        let default = metadata
            .as_ref()
            .and_then(|m| m.default.as_ref())
            .cloned()
            .map(WrappedValue::new);

        let (name, extra_derives) = type_patch(type_space, name);

        let details = TypeEntryDetails::Struct(Self {
            name,
            rename,
            description,
            default,
            properties,
            deny_unknown_fields,
            schema: SchemaWrapper(schema),
        });

        TypeEntry {
            details,
            extra_derives,
        }
    }
}

```

From #278 I assume that the issue is coming from the JSON schema not satisfying one of the assumptions being made by `typify`, but the file seems to contain a valid schema. Interestingly enough, the [PG-JSON (no L) schema](https://raw.githubusercontent.com/pg-format/specification/main/schema/pg-json.json) runs without issues.

Do you have any idea of what this could be? Thank you!

Pull request description:
improve `null` recognition and identenifcation of mutually incompatible `anyOf` subschemas
Closes #816

This does a couple of things.

What's a null schema? Currently it's just schema with `type: null`. This PR expands that definition to include `enum: [null]` and `const: null`.

While our `anyOf` handling is essentially [broken](https://ahl.dtrace.org/2024/01/22/rust-and-json-schema/#anyof), we check to see if we we can treat it as a `oneOf` (which we do handle well) in the situation where each subschema is mutually exclusive (i.e. if `oneOf` and `anyOf` would be equivalent from the perspective of schema validation). That check for mutual exclusivity is intentionally conservative--it would be very bad to have a false positive whereas a false negative applies our `anyOf` handling (which, again, is fundamentally broken, but `c'est la vie`). This PR modifies that check to do a schema validation against enumerated values (`enum` or `const`).

The culmination of these two is that the following schema doesn't panic and produces an idiomatic type:

```json
{
  "anyOf": [
    {
      "type": "string"
    },
    {
      "const": null
    }
  ]
}
```

```rust
pub struct TypeName(Option<String>);
```

Patch:
diff --git a/typify-impl/src/enums.rs b/typify-impl/src/enums.rs
index f5bfbbe..27901ea 100644
--- a/typify-impl/src/enums.rs
+++ b/typify-impl/src/enums.rs
@@ -34,15 +34,10 @@ impl TypeSpace {
             return None;
         }
         // Let's be as general as possible and consider the possibility that
-        // more than one subschema is the simple null.
+        // more than one subschema is a null.
         let non_nulls = subschemas
             .iter()
-            .filter(|schema| {
-                !matches!(schema, Schema::Object(SchemaObject {
-                instance_type: Some(SingleOrVec::Single(single)),
-                ..
-            }) if single.as_ref() == &InstanceType::Null)
-            })
+            .filter(|schema| !is_null_schema(schema))
             .collect::<Vec<_>>();
 
         if non_nulls.len() != 1 {
@@ -687,6 +682,44 @@ impl TypeSpace {
     }
 }
 
+/// Check if the schema can only be satisfied by a null. Since this is JSON
+/// schema, there are an infinity of schemas for which this might be true; in
+/// the spirit of pragmatism we check:
+/// - type: null (or [null] or [null, null, etc])
+/// - enum: [null] (or [null, null, etc])
+/// - const: null
+fn is_null_schema(schema: &Schema) -> bool {
+    match schema {
+        // Null instance type singleton
+        Schema::Object(SchemaObject {
+            instance_type: Some(SingleOrVec::Single(instance_type)),
+            ..
+        }) => **instance_type == InstanceType::Null,
+
+        // Null instance type array
+        Schema::Object(SchemaObject {
+            instance_type: Some(SingleOrVec::Vec(types)),
+            ..
+        }) => types
+            .iter()
+            .all(|instance_type| *instance_type == InstanceType::Null),
+
+        // Null constant value
+        Schema::Object(SchemaObject {
+            const_value: Some(value),
+            ..
+        }) => value.is_null(),
+
+        // Enumerated values where all are null
+        Schema::Object(SchemaObject {
+            enum_values: Some(values),
+            ..
+        }) => values.iter().all(serde_json::Value::is_null),
+
+        _ => false,
+    }
+}
+
 /// Get the string that represents the common prefix, considering only
 /// case-relevant boundaries.
 fn get_common_prefix(name: &str, prefix: &str) -> String {
diff --git a/typify-impl/src/util.rs b/typify-impl/src/util.rs
index 087fb95..b14be99 100644
--- a/typify-impl/src/util.rs
+++ b/typify-impl/src/util.rs
@@ -1,6 +1,6 @@
 // Copyright 2025 Oxide Computer Company
 
-use std::collections::{BTreeSet, HashSet};
+use std::collections::{BTreeMap, BTreeSet, HashSet};
 
 use log::debug;
 use schemars::schema::{
@@ -9,7 +9,7 @@ use schemars::schema::{
 };
 use unicode_ident::{is_xid_continue, is_xid_start};
 
-use crate::{Error, Name, RefKey, Result, TypeSpace};
+use crate::{validate::schema_value_validate, Error, Name, RefKey, Result, TypeSpace};
 
 pub(crate) fn metadata_description(metadata: &Option<Box<Metadata>>) -> Option<String> {
     metadata
@@ -44,7 +44,7 @@ pub(crate) fn metadata_title_and_description(metadata: &Option<Box<Metadata>>) -
 /// **could** be merged (i.e. if they're compatible).
 pub(crate) fn all_mutually_exclusive(
     subschemas: &[Schema],
-    definitions: &std::collections::BTreeMap<RefKey, Schema>,
+    definitions: &BTreeMap<RefKey, Schema>,
 ) -> bool {
     let len = subschemas.len();
     // Consider all pairs
@@ -53,13 +53,17 @@ pub(crate) fn all_mutually_exclusive(
         .all(|(ii, jj)| {
             let a = resolve(&subschemas[ii], definitions);
             let b = resolve(&subschemas[jj], definitions);
-            schemas_mutually_exclusive(a, b)
+            schemas_mutually_exclusive(a, b, definitions)
         })
 }
 
 /// This function needs to necessarily be conservative. We'd much prefer a
 /// false negative than a false positive.
-fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
+fn schemas_mutually_exclusive(
+    a: &Schema,
+    b: &Schema,
+    definitions: &BTreeMap<RefKey, Schema>,
+) -> bool {
     match (a, b) {
         // If either matches nothing then they are exclusive.
         (Schema::Bool(false), _) => true,
@@ -114,7 +118,9 @@ fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
                 if_schema: None,
                 then_schema: None,
                 else_schema: None,
-            } => s.iter().any(|sub| schemas_mutually_exclusive(sub, other)),
+            } => s
+                .iter()
+                .any(|sub| schemas_mutually_exclusive(sub, other, definitions)),
 
             // For a oneOf or anyOf, *all* subschemas need to be incompatible.
             SubschemaValidation {
@@ -134,7 +140,9 @@ fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
                 if_schema: None,
                 then_schema: None,
                 else_schema: None,
-            } => s.iter().all(|sub| schemas_mutually_exclusive(sub, other)),
+            } => s
+                .iter()
+                .all(|sub| schemas_mutually_exclusive(sub, other, definitions)),
 
             // For a not, they're mutually exclusive if they *do* match.
             SubschemaValidation {
@@ -145,21 +153,20 @@ fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
                 if_schema: None,
                 then_schema: None,
                 else_schema: None,
-            } => !schemas_mutually_exclusive(sub, other),
+            } => !schemas_mutually_exclusive(sub, other, definitions),
 
             // Assume other subschemas are complex to understand and may
             // therefore be compatible.
             _ => false,
         },
 
-        // If one type has an explicit type and has enumerated value (but no
-        // type), we can check to see if every enumerated value is incompatible
-        // with the given type.
+        // If one schema has enumerated values, it's incompatible if all values
+        // fail to validate against the other schema. (Conversely, if all
+        // values *do* validate against the other schema, it simply seems
+        // redundant--the most interesting case is if *some* values validate
+        // and others do not.)
         (
-            Schema::Object(SchemaObject {
-                instance_type: Some(SingleOrVec::Single(marked_type)),
-                ..
-            }),
+            schema,
             Schema::Object(SchemaObject {
                 instance_type: None,
                 enum_values: Some(enum_values),
@@ -172,21 +179,27 @@ fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
                 enum_values: Some(enum_values),
                 ..
             }),
+            schema,
+        ) => enum_values
+            .iter()
+            .all(|value| schema_value_validate(schema, value, definitions).is_err()),
+
+        // If one schema has a constant value, it's incompatible if that value
+        // fails to validate against the other schema.
+        (
+            schema,
             Schema::Object(SchemaObject {
-                instance_type: Some(SingleOrVec::Single(marked_type)),
+                const_value: Some(value),
                 ..
             }),
-        ) => enum_values
-            .iter()
-            .map(|v| match v {
-                serde_json::Value::Null => InstanceType::Null,
-                serde_json::Value::Bool(_) => InstanceType::Boolean,
-                serde_json::Value::Number(_) => InstanceType::Number,
-                serde_json::Value::String(_) => InstanceType::String,
-                serde_json::Value::Array(_) => InstanceType::Array,
-                serde_json::Value::Object(_) => InstanceType::Object,
-            })
-            .all(|value_type| value_type != **marked_type),
+        )
+        | (
+            Schema::Object(SchemaObject {
+                const_value: Some(value),
+                ..
+            }),
+            schema,
+        ) => schema_value_validate(schema, value, definitions).is_err(),
 
         // Neither is a Schema::Bool; we need to look at the instance types.
         (Schema::Object(a), Schema::Object(b)) => {
@@ -285,7 +298,7 @@ fn schemas_mutually_exclusive(a: &Schema, b: &Schema) -> bool {
                         },
                     ) = (a, b)
                     {
-                        array_schemas_mutually_exclusive(a_validation, b_validation)
+                        array_schemas_mutually_exclusive(a_validation, b_validation, definitions)
                     } else {
                         // Could check further, but we'll be conservative.
                         false
@@ -383,6 +396,7 @@ fn object_schemas_mutually_exclusive(
 fn array_schemas_mutually_exclusive(
     a_validation: &ArrayValidation,
     b_validation: &ArrayValidation,
+    definitions: &BTreeMap<RefKey, Schema>,
 ) -> bool {
     match (a_validation, b_validation) {
         // If one is an array with a single item type and the other is a tuple
@@ -421,7 +435,7 @@ fn array_schemas_mutually_exclusive(
             },
         ) if max_items == min_items && *max_items as usize == vec.len() => vec
             .iter()
-            .any(|schema| schemas_mutually_exclusive(schema, single)),
+            .any(|schema| schemas_mutually_exclusive(schema, single, definitions)),
 
         (aa, bb) => {
             // If min > max then these schemas are incompatible.
@@ -438,7 +452,7 @@ fn array_schemas_mutually_exclusive(
                 // If thee's a single item schema and it's mutually exclusive
                 // then we're done.
                 (Some(SingleOrVec::Single(a_items)), _, Some(SingleOrVec::Single(b_items)), _)
-                    if schemas_mutually_exclusive(a_items, b_items) =>
+                    if schemas_mutually_exclusive(a_items, b_items, definitions) =>
                 {
                     return true;
                 }
@@ -861,6 +875,8 @@ impl StringValidator {
 
 #[cfg(test)]
 mod tests {
+    use std::collections::BTreeMap;
+
     use schemars::{
         gen::{SchemaGenerator, SchemaSettings},
         schema::StringValidation,
@@ -893,8 +909,8 @@ mod tests {
         let a = schema_for!(A).schema.into();
         let b = schema_for!(B).schema.into();
 
-        assert!(!schemas_mutually_exclusive(&a, &b));
-        assert!(!schemas_mutually_exclusive(&b, &a));
+        assert!(!schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
+        assert!(!schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
     }
 
     #[test]
@@ -913,7 +929,7 @@ mod tests {
 
         let a = gen.into_root_schema_for::<Vec<A>>().schema.into();
 
-        assert!(!schemas_mutually_exclusive(&a, &a));
+        assert!(!schemas_mutually_exclusive(&a, &a, &BTreeMap::new()));
     }
 
     #[test]
@@ -936,8 +952,8 @@ mod tests {
         let a = schema_for!(A).schema.into();
         let b = schema_for!(B).schema.into();
 
-        assert!(schemas_mutually_exclusive(&a, &b));
-        assert!(schemas_mutually_exclusive(&b, &a));
+        assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
+        assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
     }
 
     #[test]
@@ -961,8 +977,8 @@ mod tests {
         let a = schema_for!(A).schema.into();
         let b = schema_for!(B).schema.into();
 
-        assert!(schemas_mutually_exclusive(&a, &b));
-        assert!(schemas_mutually_exclusive(&b, &a));
+        assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
+        assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
     }
 
     #[test]
@@ -970,8 +986,8 @@ mod tests {
         let a = schema_for!(Vec<u32>).schema.into();
         let b = schema_for!(Vec<f32>).schema.into();
 
-        assert!(schemas_mutually_exclusive(&a, &b));
-        assert!(schemas_mutually_exclusive(&b, &a));
+        assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
+        assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
     }
 
     #[test]


Test patch:
diff --git a/typify/tests/schemas/various-enums.json b/typify/tests/schemas/various-enums.json
index 644f724..1ec21e2 100644
--- a/typify/tests/schemas/various-enums.json
+++ b/typify/tests/schemas/various-enums.json
@@ -285,6 +285,70 @@
         "25GBASE-T",
         "2,5,GBASE,T"
       ]
+    },
+    "option-oneof-enum": {
+      "oneOf": [
+        {
+          "type": "string"
+        },
+        {
+          "enum": [
+            null
+          ]
+        }
+      ]
+    },
+    "option-oneof-const": {
+      "oneOf": [
+        {
+          "type": "string"
+        },
+        {
+          "const": null
+        }
+      ]
+    },
+    "option-oneof-null": {
+      "oneOf": [
+        {
+          "type": "string"
+        },
+        {
+          "type": "null"
+        }
+      ]
+    },
+    "option-anyof-enum": {
+      "anyOf": [
+        {
+          "type": "string"
+        },
+        {
+          "enum": [
+            null
+          ]
+        }
+      ]
+    },
+    "option-anyof-const": {
+      "anyOf": [
+        {
+          "type": "string"
+        },
+        {
+          "const": null
+        }
+      ]
+    },
+    "option-anyof-null": {
+      "anyOf": [
+        {
+          "type": "string"
+        },
+        {
+          "type": "null"
+        }
+      ]
     }
   }
 }
diff --git a/typify/tests/schemas/various-enums.rs b/typify/tests/schemas/various-enums.rs
index a443cee..31234bc 100644
--- a/typify/tests/schemas/various-enums.rs
+++ b/typify/tests/schemas/various-enums.rs
@@ -1020,6 +1020,256 @@ impl ::std::convert::From<i64> for OneOfTypes {
         Self::Bar(value)
     }
 }
+#[doc = "`OptionAnyofConst`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"anyOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"const\": null"]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionAnyofConst(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionAnyofConst {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionAnyofConst> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionAnyofConst) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionAnyofConst> for OptionAnyofConst {
+    fn from(value: &OptionAnyofConst) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionAnyofConst {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
+#[doc = "`OptionAnyofEnum`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"anyOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"enum\": ["]
+#[doc = "        null"]
+#[doc = "      ]"]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionAnyofEnum(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionAnyofEnum {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionAnyofEnum> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionAnyofEnum) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionAnyofEnum> for OptionAnyofEnum {
+    fn from(value: &OptionAnyofEnum) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionAnyofEnum {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
+#[doc = "`OptionAnyofNull`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"anyOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"type\": \"null\""]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionAnyofNull(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionAnyofNull {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionAnyofNull> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionAnyofNull) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionAnyofNull> for OptionAnyofNull {
+    fn from(value: &OptionAnyofNull) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionAnyofNull {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
+#[doc = "`OptionOneofConst`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"oneOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"const\": null"]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionOneofConst(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionOneofConst {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionOneofConst> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionOneofConst) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionOneofConst> for OptionOneofConst {
+    fn from(value: &OptionOneofConst) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionOneofConst {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
+#[doc = "`OptionOneofEnum`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"oneOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"enum\": ["]
+#[doc = "        null"]
+#[doc = "      ]"]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionOneofEnum(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionOneofEnum {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionOneofEnum> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionOneofEnum) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionOneofEnum> for OptionOneofEnum {
+    fn from(value: &OptionOneofEnum) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionOneofEnum {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
+#[doc = "`OptionOneofNull`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"oneOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"type\": \"string\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"type\": \"null\""]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+#[serde(transparent)]
+pub struct OptionOneofNull(pub ::std::option::Option<::std::string::String>);
+impl ::std::ops::Deref for OptionOneofNull {
+    type Target = ::std::option::Option<::std::string::String>;
+    fn deref(&self) -> &::std::option::Option<::std::string::String> {
+        &self.0
+    }
+}
+impl ::std::convert::From<OptionOneofNull> for ::std::option::Option<::std::string::String> {
+    fn from(value: OptionOneofNull) -> Self {
+        value.0
+    }
+}
+impl ::std::convert::From<&OptionOneofNull> for OptionOneofNull {
+    fn from(value: &OptionOneofNull) -> Self {
+        value.clone()
+    }
+}
+impl ::std::convert::From<::std::option::Option<::std::string::String>> for OptionOneofNull {
+    fn from(value: ::std::option::Option<::std::string::String>) -> Self {
+        Self(value)
+    }
+}
 #[doc = "`ReferenceDef`"]
 #[doc = r""]
 #[doc = r" <details><summary>JSON schema</summary>"]