@@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
1010use serde_json:: Value ;
1111use std:: {
1212 collections:: { btree_map:: Entry , BTreeMap , BTreeSet } ,
13-
13+ ops :: Deref as _ ,
1414} ;
1515
1616/// schemars [`Visitor`] that rewrites a [`Schema`] to conform to Kubernetes' "structural schema" rules
@@ -252,7 +252,7 @@ enum SingleOrVec<T> {
252252#[ cfg( test) ]
253253mod test {
254254 use assert_json_diff:: assert_json_eq;
255- use schemars:: { json_schema, schema_for, JsonSchema } ;
255+ use schemars:: { json_schema, schema_for, JsonSchema } ;
256256 use serde:: { Deserialize , Serialize } ;
257257
258258 use super :: * ;
@@ -276,8 +276,6 @@ mod test {
276276 #[ test]
277277 fn hoisting_a_schema ( ) {
278278 let schemars_schema = schema_for ! ( NormalEnum ) ;
279- let mut kube_schema: crate :: schema:: Schema =
280- schemars_schema_to_kube_schema ( schemars_schema. clone ( ) ) . unwrap ( ) ;
281279
282280 assert_json_eq ! (
283281 schemars_schema,
@@ -308,9 +306,14 @@ mod test {
308306 }
309307 )
310308 ) ;
311- hoist_one_of_enum ( & mut kube_schema) ;
309+
310+
311+ let kube_schema: crate :: schema:: Schema =
312+ schemars_schema_to_kube_schema ( schemars_schema. clone ( ) ) . unwrap ( ) ;
313+
314+ let hoisted_kube_schema = hoist_one_of_enum ( kube_schema) ;
312315 assert_json_eq ! (
313- kube_schema ,
316+ hoisted_kube_schema ,
314317 json_schema!(
315318 {
316319 "$schema" : "https://json-schema.org/draft/2020-12/schema" ,
@@ -334,44 +337,94 @@ fn schemars_schema_to_kube_schema(incoming: schemars::Schema) -> Result<Schema,
334337 serde_json:: from_value ( incoming. to_value ( ) )
335338}
336339
337- #[ cfg( test) ]
338- fn hoist_one_of_enum ( schema : & mut Schema ) {
339- if let Schema :: Object ( SchemaObject {
340+ /// Hoist `oneOf` into top level `enum`.
341+ ///
342+ /// This will move all `enum` variants and `const` values under `oneOf` into a single top level `enum` along with `type`.
343+ /// It will panic if there are anomalies, like differences in `type` values, or lack of `enum` or `const` fields in the `oneOf` entries.
344+ ///
345+ /// Note: variant descriptions will be lost in the process, and the original `oneOf` will be erased.
346+ ///
347+ // Note: This function is heavily documented to express intent. It is intended to help developers
348+ // make adjustments for future Schemars changes.
349+ fn hoist_one_of_enum ( incoming : Schema ) -> Schema {
350+ // Run some initial checks in case there is nothing to do
351+ let Schema :: Object ( SchemaObject {
340352 subschemas : Some ( subschemas) ,
341- instance_type : object_type,
342- enum_values : object_ebum,
343353 ..
344- } ) = schema && let SubschemaValidation {
354+ } ) = & incoming
355+ else {
356+ return incoming;
357+ } ;
358+
359+ let SubschemaValidation {
345360 one_of : Some ( one_of) , ..
346- } = & * * subschemas
347- && !one_of. is_empty ( )
361+ } = subschemas. deref ( )
362+ else {
363+ return incoming;
364+ } ;
365+
366+ if one_of. is_empty ( ) {
367+ return incoming;
368+ }
369+
370+ // At this point, we need to create a new Schema and hoist the `oneOf`
371+ // variants' `enum`/`const` values up into a parent `enum`.
372+ let mut new_schema = incoming. clone ( ) ;
373+ if let Schema :: Object ( SchemaObject {
374+ subschemas : Some ( new_subschemas) ,
375+ instance_type : new_instance_type,
376+ enum_values : new_enum_values,
377+ ..
378+ } ) = & mut new_schema
348379 {
380+ // For each `oneOf`, get the `type`.
381+ // Panic if it has no `type`, or if the entry is a boolean.
349382 let mut types = one_of. iter ( ) . map ( |obj| match obj {
350- Schema :: Object ( SchemaObject { instance_type : Some ( type_) , ..} ) => type_,
383+ Schema :: Object ( SchemaObject {
384+ instance_type : Some ( r#type) ,
385+ ..
386+ } ) => r#type,
387+ // TODO (@NickLarsenNZ): Is it correct that JSON Schema oneOf must have a type?
351388 Schema :: Object ( _) => panic ! ( "oneOf variants need to define a type!: {obj:?}" ) ,
352389 Schema :: Bool ( _) => panic ! ( "oneOf variants can not be of type boolean" ) ,
353390 } ) ;
354- let enums = one_of. iter ( ) . flat_map ( |obj| match obj {
355- Schema :: Object ( SchemaObject { enum_values : Some ( enum_) , ..} ) => enum_. clone ( ) ,
356- Schema :: Object ( SchemaObject { other, ..} ) => match other. get ( "const" ) {
357- Some ( const_) => vec ! [ const_. clone( ) ] ,
358- None => panic ! ( "oneOf variant did not provide \" enum\" or \" const\" : {obj:?}" ) ,
359- } ,
360- Schema :: Bool ( _) => panic ! ( "oneOf variants can not be of type boolean" ) ,
361- } ) ;
362391
363- let first_type = types
392+ // Get the first `type` value, then panic if any subsequent `type` values differ.
393+ let hoisted_instance_type = types
364394 . next ( )
365395 . expect ( "oneOf must have at least one variant - we already checked that" ) ;
366- if types. any ( |t| t != first_type) {
396+ // TODO (@NickLarsenNZ): Didn't sbernauer say that the types
397+ if types. any ( |t| t != hoisted_instance_type) {
367398 panic ! ( "All oneOf variants must have the same type" ) ;
368399 }
369400
370- * object_type = Some ( first_type. clone ( ) ) ;
371- * object_ebum = Some ( enums. collect ( ) ) ;
372- subschemas. one_of = None ;
401+ * new_instance_type = Some ( hoisted_instance_type. clone ( ) ) ;
402+
403+ // For each `oneOf` entry, iterate over the `enum` and `const` values.
404+ // Panic on an entry that doesn't contain an `enum` or `const`.
405+ let new_enums = one_of. iter ( ) . flat_map ( |obj| match obj {
406+ Schema :: Object ( SchemaObject {
407+ enum_values : Some ( r#enum) ,
408+ ..
409+ } ) => r#enum. clone ( ) ,
410+ // Warning: The `const` check below must come after the enum check above.
411+ // Otherwise it will panic on a valid entry with an `enum`.
412+ Schema :: Object ( SchemaObject { other, .. } ) => match other. get ( "const" ) {
413+ Some ( r#const) => vec ! [ r#const. clone( ) ] ,
414+ None => panic ! ( "oneOf variant did not provide \" enum\" or \" const\" : {obj:?}" ) ,
415+ } ,
416+ Schema :: Bool ( _) => panic ! ( "oneOf variants can not be of type boolean" ) ,
417+ } ) ;
418+
419+ // Just in case there were existing enum values, add to them.
420+ // TODO (@NickLarsenNZ): Check if `oneOf` and `enum` are mutually exclusive for a valid spec.
421+ new_enum_values. get_or_insert_default ( ) . extend ( new_enums) ;
373422
423+ // We can clear out the existing oneOf's, since they will be hoisted below.
424+ new_subschemas. one_of = None ;
374425 }
426+
427+ new_schema
375428}
376429
377430impl Transform for StructuralSchemaRewriter {
0 commit comments