Skip to content

Commit 3cd977d

Browse files
Merge pull request #57 from mwja/object-allOf-collapse-fix
fix - move allOf before the typed shape
2 parents 869d0f9 + dd1cf49 commit 3cd977d

4 files changed

Lines changed: 103 additions & 9 deletions

File tree

src/openapi.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ pub enum Schema {
154154
#[serde(flatten)]
155155
details: SchemaDetails,
156156
},
157+
/// AllOf composition (must come before Typed to handle type + allOf
158+
/// patterns)
159+
AllOf {
160+
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
161+
schema_type: Option<SchemaType>,
162+
#[serde(rename = "allOf")]
163+
all_of: Vec<Schema>,
164+
#[serde(flatten)]
165+
details: SchemaDetails,
166+
},
157167
/// Schema with `type` as an array (OpenAPI 3.1 / JSON Schema 2020-12).
158168
/// The canonical 3.1 way to express a nullable type is
159169
/// `type: ["string", "null"]`. Listed before `Typed` so the array form
@@ -171,13 +181,6 @@ pub enum Schema {
171181
#[serde(flatten)]
172182
details: SchemaDetails,
173183
},
174-
/// AllOf composition
175-
AllOf {
176-
#[serde(rename = "allOf")]
177-
all_of: Vec<Schema>,
178-
#[serde(flatten)]
179-
details: SchemaDetails,
180-
},
181184
/// Schema without explicit type (inferred from other fields)
182185
Untyped {
183186
#[serde(flatten)]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: src/test_helpers.rs
3+
assertion_line: 229
4+
expression: "&generated_code"
5+
---
6+
//! Generated types from OpenAPI specification
7+
//!
8+
//! This file contains all the generated types for the API.
9+
//! Do not edit manually - regenerate using the appropriate script.
10+
#![allow(clippy::large_enum_variant)]
11+
#![allow(clippy::format_in_format_args)]
12+
#![allow(clippy::let_unit_value)]
13+
#![allow(unreachable_patterns)]
14+
use serde::{Deserialize, Serialize};
15+
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
16+
pub struct Widget {
17+
#[serde(skip_serializing_if = "Option::is_none")]
18+
pub id: Option<String>,
19+
#[serde(skip_serializing_if = "Option::is_none")]
20+
pub name: Option<String>,
21+
}
22+
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
23+
pub struct WidgetExtra {
24+
#[serde(skip_serializing_if = "Option::is_none")]
25+
pub name: Option<String>,
26+
}
27+
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
28+
pub struct WidgetBase {
29+
#[serde(skip_serializing_if = "Option::is_none")]
30+
pub id: Option<String>,
31+
}

tests/serde_json_value_reduction_tests.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,48 @@ fn test_nested_allof_composition() {
307307
assert!(result.contains("pub content: String"));
308308
}
309309

310+
#[test]
311+
fn test_allof_with_redundant_type_object_sibling() {
312+
// Test AllOf composition that should flatten properties instead of using serde_json::Value
313+
// even with type: object.
314+
let spec = json!({
315+
"openapi": "3.1.0",
316+
"info": {"title": "Test", "version": "1.0"},
317+
"components": {
318+
"schemas": {
319+
"Widget": {
320+
"type": "object",
321+
"allOf": [
322+
{"$ref": "#/components/schemas/WidgetBase"},
323+
{"$ref": "#/components/schemas/WidgetExtra"}
324+
]
325+
},
326+
"WidgetBase": {
327+
"type": "object",
328+
"properties": {
329+
"id": {"type": "string"}
330+
}
331+
},
332+
"WidgetExtra": {
333+
"type": "object",
334+
"properties": {
335+
"name": {"type": "string"}
336+
}
337+
}
338+
}
339+
}
340+
});
341+
342+
let result =
343+
test_generation("allof_type_object_sibling_test", spec).expect("Generation failed");
344+
345+
assert!(result.contains("pub struct Widget"));
346+
// Check different possible formats of string.
347+
assert!(result.contains("pub id: Option<String>") || result.contains("pub id: String"));
348+
assert!(result.contains("pub name: Option<String>") || result.contains("pub name: String"));
349+
assert!(!result.contains("pub type Widget = serde_json::Value"));
350+
}
351+
310352
#[test]
311353
fn test_object_with_additional_properties() {
312354
// Test that objects with additionalProperties correctly use BTreeMap<String, serde_json::Value>

tests/server_raw_body_roundtrip_test.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,21 @@ mod tests {
401401
}
402402
}
403403
404+
fn sample_plugin() -> Plugin {
405+
Plugin {
406+
added_at: "2024-01-01T00:00:00Z".parse().unwrap(),
407+
connection: PluginModeUnion::PluginSupervisedProps(PluginSupervisedProps {}),
408+
description: None,
409+
id: "plugin-one".to_string(),
410+
manifest: PluginManifest::default(),
411+
name: "Test Plugin".to_string(),
412+
status: PluginStatus::PluginStatusActive(PluginStatusActive {
413+
activated_at: "2024-01-01T00:00:00Z".parse().unwrap(),
414+
}),
415+
version: None,
416+
}
417+
}
418+
404419
#[async_trait::async_trait]
405420
impl PluginsApi for Api {
406421
async fn plugin_update_package(
@@ -412,7 +427,7 @@ mod tests {
412427
self.captured
413428
.send(("plugin".into(), body.map(|value| value.to_vec())))
414429
.unwrap();
415-
PluginUpdatePackageResponse::Ok(serde_json::json!({"updated": true}))
430+
PluginUpdatePackageResponse::Ok(sample_plugin())
416431
}
417432
}
418433
@@ -437,7 +452,10 @@ mod tests {
437452
.plugin_update_package("plugin-one", Some(archive.clone()))
438453
.await
439454
.unwrap();
440-
assert_eq!(response, serde_json::json!({"updated": true}));
455+
assert_eq!(
456+
serde_json::to_value(&response).unwrap(),
457+
serde_json::to_value(sample_plugin()).unwrap()
458+
);
441459
assert_eq!(
442460
captured_rx.recv().await.unwrap(),
443461
("plugin".into(), Some(archive))

0 commit comments

Comments
 (0)