Skip to content

Commit 1ce7e47

Browse files
committed
Move null schema into NULL_SCHEMA static
1 parent 9c19f10 commit 1ce7e47

File tree

5 files changed

+59
-49
lines changed

5 files changed

+59
-49
lines changed

kube-core/src/schema/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ mod transform_properties;
1313
use schemars::{transform::Transform, JsonSchema};
1414
use serde::{Deserialize, Serialize};
1515
use serde_json::Value;
16-
use std::collections::{BTreeMap, BTreeSet};
16+
use std::{
17+
collections::{BTreeMap, BTreeSet},
18+
sync::LazyLock,
19+
};
1720

1821
use crate::schema::{
1922
transform_any_of::hoist_any_of_subschema_with_a_nullable_variant,
@@ -22,6 +25,14 @@ use crate::schema::{
2225
transform_properties::hoist_properties_for_any_of_subschemas,
2326
};
2427

28+
/// This is the signature for the `null` variant produced by schemars.
29+
static NULL_SCHEMA: LazyLock<Value> = LazyLock::new(|| {
30+
serde_json::json!({
31+
"enum": [null],
32+
"nullable": true
33+
})
34+
});
35+
2536
/// schemars [`Visitor`] that rewrites a [`Schema`] to conform to Kubernetes' "structural schema" rules
2637
///
2738
/// The following two transformations are applied

kube-core/src/schema/transform_any_of.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops::DerefMut;
22

3-
use crate::schema::{Schema, SchemaObject, SubschemaValidation};
3+
use crate::schema::{Schema, SchemaObject, SubschemaValidation, NULL_SCHEMA};
44

55
/// Replace the schema with the anyOf subschema and set to nullable when the
66
/// only other subschema is the nullable entry.
@@ -36,19 +36,10 @@ pub(crate) fn hoist_any_of_subschema_with_a_nullable_variant(kube_schema: &mut S
3636
return;
3737
}
3838

39-
// This is the signature for the null variant, indicating the "other"
40-
// variant is the subschema that needs hoisting
41-
let null = serde_json::json!({
42-
"enum": [null],
43-
"nullable": true
44-
});
45-
46-
// iter through any_of entries, converting them to Value,
47-
// and build a truth table for null matches
4839
let entry_is_null: [bool; 2] = any_of
4940
.iter()
5041
.map(|x| serde_json::to_value(x).expect("schema should be able to convert to JSON"))
51-
.map(|x| x == null)
42+
.map(|x| x == *NULL_SCHEMA)
5243
.collect::<Vec<_>>()
5344
.try_into()
5445
.expect("there should be exactly 2 elements. We checked earlier");
@@ -63,8 +54,8 @@ pub(crate) fn hoist_any_of_subschema_with_a_nullable_variant(kube_schema: &mut S
6354
// At this point, we can be reasonably sure we need to hoist the non-null
6455
// anyOf subschema up to the schema level, and unset the anyOf field.
6556
// From here, anything that looks wrong will panic instead of return.
66-
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the infallible schemars::Transform
67-
57+
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the
58+
// infallible schemars::Transform
6859
let Schema::Object(to_hoist) = subschema_to_hoist else {
6960
panic!("the non-null anyOf subschema is a bool. That is not expected here");
7061
};
@@ -90,6 +81,8 @@ pub(crate) fn hoist_any_of_subschema_with_a_nullable_variant(kube_schema: &mut S
9081

9182
#[cfg(test)]
9283
mod tests {
84+
use assert_json_diff::assert_json_eq;
85+
9386
use super::*;
9487

9588
#[test]
@@ -168,7 +161,7 @@ mod tests {
168161
let mut actual_converted_schema_object = original_schema_object.clone();
169162
hoist_any_of_subschema_with_a_nullable_variant(&mut actual_converted_schema_object);
170163

171-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
164+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
172165
}
173166

174167
#[test]
@@ -216,6 +209,6 @@ mod tests {
216209
let mut actual_converted_schema_object = original_schema_object.clone();
217210
hoist_any_of_subschema_with_a_nullable_variant(&mut actual_converted_schema_object);
218211

219-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
212+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
220213
}
221214
}

kube-core/src/schema/transform_one_of.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ pub(crate) fn hoist_one_of_enum_with_unit_variants(kube_schema: &mut SchemaObjec
4141
// At this point, we can be reasonably sure we need to hoist the oneOf
4242
// subschema enums and types up to the schema level, and unset the oneOf field.
4343
// From here, anything that looks wrong will panic instead of return.
44-
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the infallible schemars::Transform
44+
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the
45+
// infallible schemars::Transform
4546

4647
// Prepare to ensure each variant schema has a type
4748
let mut types = one_of.iter().map(|schema| match schema {
@@ -92,6 +93,8 @@ pub(crate) fn hoist_one_of_enum_with_unit_variants(kube_schema: &mut SchemaObjec
9293

9394
#[cfg(test)]
9495
mod tests {
96+
use assert_json_diff::assert_json_eq;
97+
9598
use super::*;
9699

97100
#[test]
@@ -143,6 +146,6 @@ mod tests {
143146
let mut actual_converted_schema_object = original_schema_object.clone();
144147
hoist_one_of_enum_with_unit_variants(&mut actual_converted_schema_object);
145148

146-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
149+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
147150
}
148151
}

kube-core/src/schema/transform_optional_enum_with_null.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ pub(crate) fn remove_optional_enum_null_variant(kube_schema: &mut SchemaObject)
1515
return;
1616
};
1717

18-
// It only makes sense to remove `null` enum values in case the enum is
19-
// nullable (thus optional).
18+
// It only makes sense to remove `null` enum values in case the enum is nullable (thus optional)
19+
// This is a safety mechanism, "nullable" should always be set to true, if one enum variant is
20+
// null.
2021
if let Some(Value::Bool(true)) = extensions.get("nullable") {
21-
// Don't remove the single last enum variant. This often happens for
22-
// `Option<XXX>`, which is represented as
23-
// `"anyOf": [XXX, {"enum": [null], "optional": true}]`
22+
// Don't remove the single last enum variant. This often happens for `Option<XXX>`, which is
23+
// represented as `"anyOf": [XXX, {"enum": [null], "optional": true}]`.
24+
// We need to keep `"enum": [null]` (as opposed to `"enum": []`), because other hoisting
25+
// code uses `kube::core::NULL_SCHEMA` to detect null variants.
2426
if enum_values.len() > 1 {
2527
enum_values.retain(|enum_value| enum_value != &Value::Null);
2628
}
@@ -29,6 +31,8 @@ pub(crate) fn remove_optional_enum_null_variant(kube_schema: &mut SchemaObject)
2931

3032
#[cfg(test)]
3133
mod tests {
34+
use assert_json_diff::assert_json_eq;
35+
3236
use super::*;
3337

3438
#[test]
@@ -64,6 +68,6 @@ mod tests {
6468
let mut actual_converted_schema_object = original_schema_object.clone();
6569
remove_optional_enum_null_variant(&mut actual_converted_schema_object);
6670

67-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
71+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
6872
}
6973
}

kube-core/src/schema/transform_properties.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec};
1+
use crate::schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec, NULL_SCHEMA};
22

33
/// Take oneOf or anyOf subschema properties and move them them into the schema
44
/// properties.
@@ -35,28 +35,21 @@ pub(crate) fn hoist_properties_for_any_of_subschemas(kube_schema: &mut SchemaObj
3535
return;
3636
}
3737

38-
// Ensure we aren't looking at the one with a null
38+
// Ensure we aren't looking at the one with a null, as that is hoisted by another transformer
3939
if subschemas.len() == 2 {
40-
// This is the signature for the null variant, indicating the "other"
41-
// variant is the subschema that needs hoisting
42-
let null = serde_json::json!({
43-
"enum": [null],
44-
"nullable": true
45-
});
46-
47-
// Return if one of the two entries are nulls
48-
for value in subschemas
40+
// Return if there is a null entry
41+
if subschemas
4942
.iter()
5043
.map(|x| serde_json::to_value(x).expect("schema should be able to convert to JSON"))
44+
.any(|x| x == *NULL_SCHEMA)
5145
{
52-
if value == null {
53-
return;
54-
}
46+
return;
5547
}
5648
}
5749

5850
// At this point, we can be reasonably sure we need operate on the schema.
59-
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the infallible schemars::Transform
51+
// TODO (@NickLarsenNZ): Return errors instead of panicking, leave panicking up to the
52+
// infallible schemars::Transform
6053

6154
let subschemas = subschemas
6255
.iter_mut()
@@ -67,7 +60,8 @@ pub(crate) fn hoist_properties_for_any_of_subschemas(kube_schema: &mut SchemaObj
6760
.collect::<Vec<_>>();
6861

6962
for subschema in subschemas {
70-
// This will clear out any objects that don't have required/properties fields (so that it appears as: {}).
63+
// This will clear out any objects that don't have required/properties fields (so that it
64+
// appears as: {}).
7165
let metadata = subschema.metadata.take();
7266
subschema.instance_type.take();
7367

@@ -91,10 +85,12 @@ pub(crate) fn hoist_properties_for_any_of_subschemas(kube_schema: &mut SchemaObj
9185
}
9286

9387
// If properties are set, hoist them to the schema properties.
94-
// This will panic if duplicate properties are encountered that do not have the same shape.
95-
// That can happen when the untagged enum variants each refer to structs which contain the same field name but with different types or doc-comments.
88+
// This will panic if duplicate properties are encountered that do not have the same
89+
// shape. That can happen when the untagged enum variants each refer to structs which
90+
// contain the same field name but with different types or doc-comments.
9691
// The developer needs to make them the same.
97-
// TODO (@NickLarsenNZ): Add a case for a structural variant, and a tuple variant containing a structure where the same field name is used.
92+
// TODO (@NickLarsenNZ): Add a case for a structural variant, and a tuple variant
93+
// containing a structure where the same field name is used.
9894
while let Some((property_name, Schema::Object(property_schema_object))) =
9995
object.properties.pop_first()
10096
{
@@ -105,8 +101,9 @@ pub(crate) fn hoist_properties_for_any_of_subschemas(kube_schema: &mut SchemaObj
105101
.properties
106102
.get(&property_name)
107103
{
108-
// TODO (@NickLarsenNZ): Here we could do another check to see if it only differs by description.
109-
// If the schema property description is not set, then we could overwrite it and not panic.
104+
// TODO (@NickLarsenNZ): Here we could do another check to see if it only
105+
// differs by description. If the schema property description is not set, then
106+
// we could overwrite it and not panic.
110107
assert_eq!(
111108
existing_property,
112109
&Schema::Object(property_schema_object.clone()),
@@ -126,6 +123,8 @@ pub(crate) fn hoist_properties_for_any_of_subschemas(kube_schema: &mut SchemaObj
126123

127124
#[cfg(test)]
128125
mod tests {
126+
use assert_json_diff::assert_json_eq;
127+
129128
use super::*;
130129

131130
#[test]
@@ -314,7 +313,7 @@ mod tests {
314313
let mut actual_converted_schema_object = original_schema_object.clone();
315314
hoist_properties_for_any_of_subschemas(&mut actual_converted_schema_object);
316315

317-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
316+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
318317
}
319318

320319
#[test]
@@ -439,7 +438,7 @@ mod tests {
439438
let mut actual_converted_schema_object = original_schema_object.clone();
440439
hoist_properties_for_any_of_subschemas(&mut actual_converted_schema_object);
441440

442-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
441+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
443442
}
444443

445444
#[test]
@@ -558,7 +557,7 @@ mod tests {
558557
let mut actual_converted_schema_object = original_schema_object.clone();
559558
hoist_properties_for_any_of_subschemas(&mut actual_converted_schema_object);
560559

561-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
560+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
562561
}
563562

564563
#[test]
@@ -647,7 +646,7 @@ mod tests {
647646
let mut actual_converted_schema_object = original_schema_object.clone();
648647
hoist_properties_for_any_of_subschemas(&mut actual_converted_schema_object);
649648

650-
assert_json_diff::assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
649+
assert_json_eq!(actual_converted_schema_object, expected_converted_schema_object);
651650
}
652651

653652
#[test]

0 commit comments

Comments
 (0)