@@ -1127,7 +1127,12 @@ impl SchemaAnalyzer {
11271127 ..
11281128 } => {
11291129 // Handle oneOf discriminated unions
1130- self . analyze_oneof_union ( one_of, discriminator. as_ref ( ) , None , & mut dependencies) ?
1130+ self . analyze_oneof_union (
1131+ one_of,
1132+ discriminator. as_ref ( ) ,
1133+ schema_name,
1134+ & mut dependencies,
1135+ ) ?
11311136 }
11321137 Schema :: AllOf { all_of, .. } => {
11331138 // Handle allOf composition (schema inheritance)
@@ -1258,7 +1263,7 @@ impl SchemaAnalyzer {
12581263 let union_schema_type = self . analyze_oneof_union (
12591264 one_of,
12601265 discriminator. as_ref ( ) ,
1261- Some ( & union_type_name) ,
1266+ & union_type_name,
12621267 dependencies,
12631268 ) ?;
12641269
@@ -1627,7 +1632,7 @@ impl SchemaAnalyzer {
16271632 let oneof_result = self . analyze_oneof_union (
16281633 one_of,
16291634 discriminator. as_ref ( ) ,
1630- Some ( & union_name) ,
1635+ & union_name,
16311636 dependencies,
16321637 ) ?;
16331638
@@ -1927,9 +1932,28 @@ impl SchemaAnalyzer {
19271932 & mut self ,
19281933 one_of_schemas : & [ Schema ] ,
19291934 discriminator : Option < & crate :: openapi:: Discriminator > ,
1930- parent_name : Option < & str > ,
1935+ parent_name : & str ,
19311936 dependencies : & mut HashSet < String > ,
19321937 ) -> Result < SchemaType > {
1938+ // Pattern: nullable [Type, null] — return the non-null type directly.
1939+ // The nullable bit is recorded at the property level via is_nullable_pattern().
1940+ if one_of_schemas. len ( ) == 2 {
1941+ let null_count = one_of_schemas
1942+ . iter ( )
1943+ . filter ( |s| matches ! ( s. schema_type( ) , Some ( OpenApiSchemaType :: Null ) ) )
1944+ . count ( ) ;
1945+ if null_count == 1 {
1946+ if let Some ( non_null) = one_of_schemas
1947+ . iter ( )
1948+ . find ( |s| !matches ! ( s. schema_type( ) , Some ( OpenApiSchemaType :: Null ) ) )
1949+ {
1950+ return self
1951+ . analyze_schema_value ( non_null, parent_name)
1952+ . map ( |a| a. schema_type ) ;
1953+ }
1954+ }
1955+ }
1956+
19331957 // If there's no discriminator, we should create an untagged union
19341958 if discriminator. is_none ( ) {
19351959 // Handle untagged unions (oneOf without discriminator)
@@ -2136,9 +2160,8 @@ impl SchemaAnalyzer {
21362160 } ) ;
21372161 } else {
21382162 // Handle inline schemas by creating type aliases or using primitive types directly
2139- let context = parent_name. unwrap_or ( "Union" ) ;
21402163 let inline_name = self . generate_context_aware_name (
2141- context ,
2164+ parent_name ,
21422165 "InlineVariant" ,
21432166 variant_index,
21442167 Some ( variant_schema) ,
@@ -2178,9 +2201,8 @@ impl SchemaAnalyzer {
21782201 }
21792202 _ => {
21802203 // For other array types, create an inline type
2181- let context = parent_name. unwrap_or ( "Inline" ) ;
21822204 let inline_type_name = self . generate_context_aware_name (
2183- context ,
2205+ parent_name ,
21842206 "Variant" ,
21852207 variant_index,
21862208 None ,
@@ -2206,11 +2228,8 @@ impl SchemaAnalyzer {
22062228 }
22072229 // For other complex types, create an inline type
22082230 _ => {
2209- let inline_type_name = format ! (
2210- "{}Variant{}" ,
2211- parent_name. unwrap_or( "Inline" ) ,
2212- variant_index + 1
2213- ) ;
2231+ let inline_type_name =
2232+ format ! ( "{}Variant{}" , parent_name, variant_index + 1 ) ;
22142233 self . add_inline_schema (
22152234 & inline_type_name,
22162235 variant_schema,
@@ -2246,12 +2265,27 @@ impl SchemaAnalyzer {
22462265 fn analyze_untagged_oneof_union (
22472266 & mut self ,
22482267 one_of_schemas : & [ Schema ] ,
2249- parent_name : Option < & str > ,
2268+ parent_name : & str ,
22502269 dependencies : & mut HashSet < String > ,
22512270 ) -> Result < SchemaType > {
2271+ // Drop {"type": "null"} variants. They mean "may be null" and are surfaced
2272+ // as Option<T> at the property level — including them here produces a junk
2273+ // `SerdeJsonValue(serde_json::Value)` variant.
2274+ let filtered: Vec < & Schema > = one_of_schemas
2275+ . iter ( )
2276+ . filter ( |s| !matches ! ( s. schema_type( ) , Some ( OpenApiSchemaType :: Null ) ) )
2277+ . collect ( ) ;
2278+
2279+ // If filtering leaves a single variant, return its analyzed type directly.
2280+ if filtered. len ( ) == 1 {
2281+ return self
2282+ . analyze_schema_value ( filtered[ 0 ] , parent_name)
2283+ . map ( |a| a. schema_type ) ;
2284+ }
2285+
22522286 let mut union_variants = Vec :: new ( ) ;
22532287
2254- for ( variant_index, variant_schema) in one_of_schemas . iter ( ) . enumerate ( ) {
2288+ for ( variant_index, variant_schema) in filtered . iter ( ) . copied ( ) . enumerate ( ) {
22552289 // First check if it's a reference or recursive reference
22562290 if let Some ( ref_str) = variant_schema. reference ( ) {
22572291 if let Some ( schema_name) = self . extract_schema_name ( ref_str) {
@@ -2279,9 +2313,8 @@ impl SchemaAnalyzer {
22792313 } ) ;
22802314 } else {
22812315 // Handle inline schemas by creating type aliases or using primitive types directly
2282- let context = parent_name. unwrap_or ( "Union" ) ;
22832316 let inline_name = self . generate_context_aware_name (
2284- context ,
2317+ parent_name ,
22852318 "InlineVariant" ,
22862319 variant_index,
22872320 Some ( variant_schema) ,
@@ -2340,9 +2373,8 @@ impl SchemaAnalyzer {
23402373 }
23412374 _ => {
23422375 // For deeper nesting, create an inline type
2343- let context = parent_name. unwrap_or ( "Inline" ) ;
23442376 let inline_type_name = self . generate_context_aware_name (
2345- context ,
2377+ parent_name ,
23462378 "Variant" ,
23472379 variant_index,
23482380 None ,
@@ -2361,9 +2393,8 @@ impl SchemaAnalyzer {
23612393 }
23622394 _ => {
23632395 // For other array types, create an inline type
2364- let context = parent_name. unwrap_or ( "Inline" ) ;
23652396 let inline_type_name = self . generate_context_aware_name (
2366- context ,
2397+ parent_name ,
23672398 "Variant" ,
23682399 variant_index,
23692400 None ,
@@ -2389,9 +2420,8 @@ impl SchemaAnalyzer {
23892420 }
23902421 // For other complex types, create an inline type
23912422 _ => {
2392- let context = parent_name. unwrap_or ( "Inline" ) ;
23932423 let inline_type_name = self . generate_context_aware_name (
2394- context ,
2424+ parent_name ,
23952425 "Variant" ,
23962426 variant_index,
23972427 None ,
@@ -3122,7 +3152,12 @@ impl SchemaAnalyzer {
31223152 // Check if this is a discriminated union
31233153 if let Some ( disc) = discriminator {
31243154 // This is a discriminated anyOf union, analyze it the same way as oneOf
3125- return self . analyze_oneof_union ( any_of_schemas, Some ( disc) , None , dependencies) ;
3155+ return self . analyze_oneof_union (
3156+ any_of_schemas,
3157+ Some ( disc) ,
3158+ context_name,
3159+ dependencies,
3160+ ) ;
31263161 }
31273162
31283163 // Auto-detect implicit discriminator from const fields across all variants
@@ -3134,7 +3169,7 @@ impl SchemaAnalyzer {
31343169 mapping : None ,
31353170 extra : BTreeMap :: new ( ) ,
31363171 } ) ,
3137- None ,
3172+ context_name ,
31383173 dependencies,
31393174 ) ;
31403175 }
0 commit comments