@@ -8,14 +8,15 @@ use std::collections::btree_map::Entry;
8
8
#[ allow( unused_imports) ] use schemars:: gen:: SchemaSettings ;
9
9
10
10
use schemars:: {
11
- schema:: { Metadata , ObjectValidation , Schema , SchemaObject } ,
11
+ schema:: { InstanceType , Metadata , ObjectValidation , Schema , SchemaObject , SingleOrVec } ,
12
12
visit:: Visitor ,
13
13
} ;
14
14
15
15
/// schemars [`Visitor`] that rewrites a [`Schema`] to conform to Kubernetes' "structural schema" rules
16
16
///
17
17
/// The following two transformations are applied
18
18
/// * Rewrite enums from `oneOf` to `object`s with multiple variants ([schemars#84](https://github.com/GREsau/schemars/issues/84))
19
+ /// * Rewrite untagged enums from `anyOf` to `object`s with multiple variants ([kube#1028](https://github.com/kube-rs/kube/pull/1028))
19
20
/// * Rewrite `additionalProperties` from `#[serde(flatten)]` to `x-kubernetes-preserve-unknown-fields` ([kube#844](https://github.com/kube-rs/kube/issues/844))
20
21
///
21
22
/// This is used automatically by `kube::derive`'s `#[derive(CustomResource)]`,
@@ -31,72 +32,26 @@ pub struct StructuralSchemaRewriter;
31
32
impl Visitor for StructuralSchemaRewriter {
32
33
fn visit_schema_object ( & mut self , schema : & mut schemars:: schema:: SchemaObject ) {
33
34
schemars:: visit:: visit_schema_object ( self , schema) ;
35
+
34
36
if let Some ( one_of) = schema
35
37
. subschemas
36
38
. as_mut ( )
37
39
. and_then ( |subschemas| subschemas. one_of . as_mut ( ) )
38
40
{
39
- let common_obj = schema
40
- . object
41
- . get_or_insert_with ( || Box :: new ( ObjectValidation :: default ( ) ) ) ;
42
- for variant in one_of {
43
- if let Schema :: Object ( SchemaObject {
44
- instance_type : variant_type,
45
- object : Some ( variant_obj) ,
46
- metadata : variant_metadata,
47
- ..
48
- } ) = variant
49
- {
50
- if let Some ( variant_metadata) = variant_metadata {
51
- // Move enum variant description from oneOf clause to its corresponding property
52
- if let Some ( description) = std:: mem:: take ( & mut variant_metadata. description ) {
53
- if let Some ( Schema :: Object ( variant_object) ) =
54
- only_item ( variant_obj. properties . values_mut ( ) )
55
- {
56
- let metadata = variant_object
57
- . metadata
58
- . get_or_insert_with ( || Box :: new ( Metadata :: default ( ) ) ) ;
59
- metadata. description = Some ( description) ;
60
- }
61
- }
62
- }
63
-
64
- // Move all properties
65
- let variant_properties = std:: mem:: take ( & mut variant_obj. properties ) ;
66
- for ( property_name, property) in variant_properties {
67
- match common_obj. properties . entry ( property_name) {
68
- Entry :: Occupied ( entry) => panic ! (
69
- "property {:?} is already defined in another enum variant" ,
70
- entry. key( )
71
- ) ,
72
- Entry :: Vacant ( entry) => {
73
- entry. insert ( property) ;
74
- }
75
- }
76
- }
77
-
78
- // Kubernetes doesn't allow variants to set additionalProperties
79
- variant_obj. additional_properties = None ;
41
+ // Tagged enums are serialized using `one_of`
42
+ hoist_subschema_properties ( one_of, & mut schema. object , & mut schema. instance_type ) ;
43
+ }
80
44
81
- // Try to merge metadata
82
- match ( & mut schema. instance_type , variant_type. take ( ) ) {
83
- ( _, None ) => { }
84
- ( common_type @ None , variant_type) => {
85
- * common_type = variant_type;
86
- }
87
- ( Some ( common_type) , Some ( variant_type) ) => {
88
- if * common_type != variant_type {
89
- panic ! (
90
- "variant defined type {:?}, conflicting with existing type {:?}" ,
91
- variant_type, common_type
92
- ) ;
93
- }
94
- }
95
- }
96
- }
97
- }
45
+ if let Some ( any_of) = schema
46
+ . subschemas
47
+ . as_mut ( )
48
+ . and_then ( |subschemas| subschemas. any_of . as_mut ( ) )
49
+ {
50
+ // Untagged enums are serialized using `any_of`
51
+ hoist_subschema_properties ( any_of, & mut schema. object , & mut schema. instance_type ) ;
98
52
}
99
- // check for maps without with properties (i.e. flattnened maps)
53
+
54
+ // check for maps without with properties (i.e. flattened maps)
100
55
// and allow these to persist dynamically
101
56
if let Some ( object) = & mut schema. object {
102
57
if !object. properties . is_empty ( )
@@ -111,10 +66,87 @@ impl Visitor for StructuralSchemaRewriter {
111
66
}
112
67
}
113
68
69
+ /// Bring all property definitions from subschemas up to the root schema,
70
+ /// since Kubernetes doesn't allow subschemas to define properties.
71
+ fn hoist_subschema_properties (
72
+ subschemas : & mut Vec < Schema > ,
73
+ common_obj : & mut Option < Box < ObjectValidation > > ,
74
+ instance_type : & mut Option < SingleOrVec < InstanceType > > ,
75
+ ) {
76
+ let common_obj = common_obj. get_or_insert_with ( || Box :: new ( ObjectValidation :: default ( ) ) ) ;
77
+
78
+ for variant in subschemas {
79
+ if let Schema :: Object ( SchemaObject {
80
+ instance_type : variant_type,
81
+ object : Some ( variant_obj) ,
82
+ metadata : variant_metadata,
83
+ ..
84
+ } ) = variant
85
+ {
86
+ if let Some ( variant_metadata) = variant_metadata {
87
+ // Move enum variant description from oneOf clause to its corresponding property
88
+ if let Some ( description) = std:: mem:: take ( & mut variant_metadata. description ) {
89
+ if let Some ( Schema :: Object ( variant_object) ) =
90
+ only_item ( variant_obj. properties . values_mut ( ) )
91
+ {
92
+ let metadata = variant_object
93
+ . metadata
94
+ . get_or_insert_with ( || Box :: new ( Metadata :: default ( ) ) ) ;
95
+ metadata. description = Some ( description) ;
96
+ }
97
+ }
98
+ }
99
+
100
+ // Move all properties
101
+ let variant_properties = std:: mem:: take ( & mut variant_obj. properties ) ;
102
+ for ( property_name, property) in variant_properties {
103
+ match common_obj. properties . entry ( property_name) {
104
+ Entry :: Vacant ( entry) => {
105
+ entry. insert ( property) ;
106
+ }
107
+ Entry :: Occupied ( entry) => {
108
+ if & property != entry. get ( ) {
109
+ panic ! ( "Property {:?} has the schema {:?} but was already defined as {:?} in another subschema. The schemas for a property used in multiple subschemas must be identical" ,
110
+ entry. key( ) ,
111
+ & property,
112
+ entry. get( ) ) ;
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ // Kubernetes doesn't allow variants to set additionalProperties
119
+ variant_obj. additional_properties = None ;
120
+
121
+ merge_metadata ( instance_type, variant_type. take ( ) ) ;
122
+ }
123
+ }
124
+ }
125
+
114
126
fn only_item < I : Iterator > ( mut i : I ) -> Option < I :: Item > {
115
127
let item = i. next ( ) ?;
116
128
if i. next ( ) . is_some ( ) {
117
129
return None ;
118
130
}
119
131
Some ( item)
120
132
}
133
+
134
+ fn merge_metadata (
135
+ instance_type : & mut Option < SingleOrVec < InstanceType > > ,
136
+ variant_type : Option < SingleOrVec < InstanceType > > ,
137
+ ) {
138
+ match ( instance_type, variant_type) {
139
+ ( _, None ) => { }
140
+ ( common_type @ None , variant_type) => {
141
+ * common_type = variant_type;
142
+ }
143
+ ( Some ( common_type) , Some ( variant_type) ) => {
144
+ if * common_type != variant_type {
145
+ panic ! (
146
+ "variant defined type {:?}, conflicting with existing type {:?}" ,
147
+ variant_type, common_type
148
+ ) ;
149
+ }
150
+ }
151
+ }
152
+ }
0 commit comments