Skip to content

Commit 85eaf36

Browse files
committed
Finish enum oneOf hoisting
1 parent 0cb62f1 commit 85eaf36

File tree

1 file changed

+90
-68
lines changed

1 file changed

+90
-68
lines changed

kube-core/src/schema.rs

Lines changed: 90 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
1010
use serde_json::Value;
1111
use std::{
1212
collections::{btree_map::Entry, BTreeMap, BTreeSet},
13-
ops::{Deref, Not},
13+
1414
};
1515

1616
/// schemars [`Visitor`] that rewrites a [`Schema`] to conform to Kubernetes' "structural schema" rules
@@ -252,9 +252,10 @@ enum SingleOrVec<T> {
252252
#[cfg(test)]
253253
mod test {
254254
use assert_json_diff::assert_json_eq;
255-
use schemars::{json_schema, schema_for, schema_for_value, JsonSchema};
256-
use serde::{de::Expected, Deserialize, Serialize};
257-
use serde_json::json;
255+
use schemars::{json_schema, schema_for, JsonSchema};
256+
use serde::{Deserialize, Serialize};
257+
258+
use super::*;
258259

259260
/// A very simple enum with empty variants
260261
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
@@ -274,82 +275,103 @@ mod test {
274275

275276
#[test]
276277
fn hoisting_a_schema() {
277-
let incoming = json_schema!(
278-
{
279-
"$schema": "https://json-schema.org/draft/2020-12/schema",
280-
"description": "A very simple enum with empty variants",
281-
"oneOf": [
282-
{
283-
"enum": [
284-
"C",
285-
"D"
278+
let schemars_schema = schema_for!(NormalEnum);
279+
let mut kube_schema: crate::schema::Schema =
280+
schemars_schema_to_kube_schema(schemars_schema.clone()).unwrap();
281+
282+
assert_json_eq!(
283+
schemars_schema,
284+
json_schema!(
285+
{
286+
"$schema": "https://json-schema.org/draft/2020-12/schema",
287+
"description": "A very simple enum with empty variants",
288+
"oneOf": [
289+
{
290+
"enum": [
291+
"C",
292+
"D"
293+
],
294+
"type": "string"
295+
},
296+
{
297+
"const": "A",
298+
"description": "First variant",
299+
"type": "string"
300+
},
301+
{
302+
"const": "B",
303+
"description": "Second variant",
304+
"type": "string"
305+
}
286306
],
287-
"type": "string"
288-
},
289-
{
290-
"const": "A",
291-
"description": "First variant",
292-
"type": "string"
293-
},
294-
{
295-
"const": "B",
296-
"description": "Second variant",
297-
"type": "string"
307+
"title": "NormalEnum"
298308
}
299-
],
300-
"title": "NormalEnum"
301-
}
309+
)
302310
);
303-
304-
// Initial check that the text schema above is correct for NormalEnum
305-
assert_json_eq!(schema_for!(NormalEnum), incoming);
306-
307-
let expected = json_schema!(
308-
{
309-
"$schema": "https://json-schema.org/draft/2020-12/schema",
310-
"description": "A very simple enum with empty variants",
311-
"type": "string",
312-
"enum": [
313-
"C",
314-
"D",
315-
"A",
316-
"B"
317-
],
318-
"title": "NormalEnum"
319-
}
311+
hoist_one_of_enum(&mut kube_schema);
312+
assert_json_eq!(
313+
kube_schema,
314+
json_schema!(
315+
{
316+
"$schema": "https://json-schema.org/draft/2020-12/schema",
317+
"description": "A very simple enum with empty variants",
318+
"type": "string",
319+
"enum": [
320+
"C",
321+
"D",
322+
"A",
323+
"B"
324+
],
325+
"title": "NormalEnum"
326+
}
327+
)
320328
);
321-
322-
// let actual = hoist
323-
324-
// assert_json_eq!(expected, actual);
325329
}
326330
}
327331

328-
fn hoist_one_of_enum(incoming: Schema) -> Schema {
329-
let Schema::Object(SchemaObject {
332+
#[cfg(test)]
333+
fn schemars_schema_to_kube_schema(incoming: schemars::Schema) -> Result<Schema, serde_json::Error> {
334+
serde_json::from_value(incoming.to_value())
335+
}
336+
337+
#[cfg(test)]
338+
fn hoist_one_of_enum(schema: &mut Schema) {
339+
if let Schema::Object(SchemaObject {
330340
subschemas: Some(subschemas),
341+
instance_type: object_type,
342+
enum_values: object_ebum,
331343
..
332-
}) = &incoming
333-
else {
334-
return incoming;
335-
};
336-
337-
let SubschemaValidation {
344+
}) = schema && let SubschemaValidation {
338345
one_of: Some(one_of), ..
339-
} = subschemas.deref()
340-
else {
341-
return incoming;
342-
};
343-
344-
if one_of.is_empty() {
345-
return incoming;
346-
}
347-
348-
// now the meat. Need to get the oneOf variants up into `enum`
349-
// panic if the types differ
346+
} = &**subschemas
347+
&& !one_of.is_empty()
348+
{
349+
let mut types = one_of.iter().map(|obj| match obj {
350+
Schema::Object(SchemaObject { instance_type: Some(type_), ..}) => type_,
351+
Schema::Object(_) => panic!("oneOf variants need to define a type!: {obj:?}"),
352+
Schema::Bool(_) => panic!("oneOf variants can not be of type boolean"),
353+
});
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+
});
362+
363+
let first_type = types
364+
.next()
365+
.expect("oneOf must have at least one variant - we already checked that");
366+
if types.any(|t| t != first_type) {
367+
panic!("All oneOf variants must have the same type");
368+
}
350369

370+
*object_type = Some(first_type.clone());
371+
*object_ebum = Some(enums.collect());
372+
subschemas.one_of = None;
351373

352-
todo!("finish it")
374+
}
353375
}
354376

355377
impl Transform for StructuralSchemaRewriter {

0 commit comments

Comments
 (0)