Skip to content

Commit 7b68dc1

Browse files
committed
Fix composed property enum mapping
1 parent b31c2ee commit 7b68dc1

2 files changed

Lines changed: 108 additions & 2 deletions

File tree

crates/clickhouse-openapi-analyzer/src/compare.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,30 @@ mod tests {
864864
}));
865865
}
866866

867+
#[test]
868+
fn maps_property_enum_beneath_top_level_composition() {
869+
let report = analyze_fixture(
870+
r#"
871+
pub struct Widget { pub status: Status }
872+
pub enum Status { #[serde(rename = "on")] On }
873+
"#,
874+
serde_json::json!({
875+
"allOf": [{
876+
"properties": {"status": {"enum": ["on", "off"]}}
877+
}]
878+
}),
879+
AnalyzerConfig::default(),
880+
);
881+
882+
assert!(report.findings.iter().any(|finding| {
883+
finding.kind == FindingKind::MissingEnumValue
884+
&& finding.details.get("enum").map(String::as_str) == Some("Status")
885+
&& finding.details.get("value").map(String::as_str) == Some("off")
886+
&& finding.rust_item.as_deref() == Some("models.rs::Widget::status")
887+
}));
888+
assert!(report.unsupported_enum_constraints.is_empty());
889+
}
890+
867891
#[test]
868892
fn nested_chain_through_non_struct_reports_unsupported() {
869893
let report = analyze_fixture(

crates/clickhouse-openapi-analyzer/src/openapi.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,11 +637,17 @@ fn enum_context(root: &Value, path: &[String]) -> EnumContext {
637637
schema: path[2].clone(),
638638
};
639639
}
640-
if path.len() >= 5 && path[0] == "components" && path[1] == "schemas" && path[3] == "properties"
640+
if path.len() >= 5
641+
&& path[0] == "components"
642+
&& path[1] == "schemas"
643+
&& let Some(properties_index) = path[3..]
644+
.iter()
645+
.position(|part| part == "properties")
646+
.map(|index| index + 3)
641647
{
642648
return EnumContext::Property {
643649
schema: path[2].clone(),
644-
steps: property_steps(&path[3..]),
650+
steps: property_steps(&path[properties_index..]),
645651
};
646652
}
647653
if path.len() > 3
@@ -895,6 +901,82 @@ mod tests {
895901
}
896902
}
897903

904+
#[test]
905+
fn attributes_property_enums_beneath_top_level_compositions() {
906+
let spec = serde_json::json!({
907+
"paths": {},
908+
"components": {"schemas": {
909+
"AllOfWidget": {"allOf": [{"properties": {
910+
"status": {"enum": ["on"]}
911+
}}]},
912+
"OneOfWidget": {"oneOf": [{"properties": {
913+
"states": {"type": "array", "items": {"enum": ["ready"]}}
914+
}}]},
915+
"AnyOfWidget": {"anyOf": [{"properties": {
916+
"settings": {"properties": {
917+
"mode": {"enum": ["fast"]}
918+
}}
919+
}}]},
920+
"NamedChoice": {"allOf": [{"enum": ["a"]}]}
921+
}}
922+
});
923+
let inventory = OpenApiInventory::build(&spec, &AnalyzerConfig::default()).unwrap();
924+
let by_pointer: BTreeMap<String, EnumContext> = inventory
925+
.enum_constraints
926+
.iter()
927+
.map(|constraint| (constraint.pointer.clone(), constraint.context.clone()))
928+
.collect();
929+
930+
for (pointer, schema, steps) in [
931+
(
932+
"/components/schemas/AllOfWidget/allOf/0/properties/status",
933+
"AllOfWidget",
934+
vec![PropertyStep {
935+
property: "status".to_string(),
936+
array_item: false,
937+
}],
938+
),
939+
(
940+
"/components/schemas/OneOfWidget/oneOf/0/properties/states/items",
941+
"OneOfWidget",
942+
vec![PropertyStep {
943+
property: "states".to_string(),
944+
array_item: true,
945+
}],
946+
),
947+
(
948+
"/components/schemas/AnyOfWidget/anyOf/0/properties/settings/properties/mode",
949+
"AnyOfWidget",
950+
vec![
951+
PropertyStep {
952+
property: "settings".to_string(),
953+
array_item: false,
954+
},
955+
PropertyStep {
956+
property: "mode".to_string(),
957+
array_item: false,
958+
},
959+
],
960+
),
961+
] {
962+
match &by_pointer[pointer] {
963+
EnumContext::Property {
964+
schema: actual_schema,
965+
steps: actual_steps,
966+
} => {
967+
assert_eq!(actual_schema, schema);
968+
assert_eq!(actual_steps, &steps);
969+
}
970+
other => panic!("expected property context for {pointer}, got {other:?}"),
971+
}
972+
}
973+
974+
assert!(matches!(
975+
&by_pointer["/components/schemas/NamedChoice/allOf/0"],
976+
EnumContext::NamedSchema { schema } if schema == "NamedChoice"
977+
));
978+
}
979+
898980
#[test]
899981
fn keeps_single_property_chains_for_the_common_case() {
900982
let spec = serde_json::json!({

0 commit comments

Comments
 (0)