Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ pub enum Schema {
#[serde(flatten)]
details: SchemaDetails,
},
/// AllOf composition (must come before Typed to handle type + allOf
/// patterns)
AllOf {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
schema_type: Option<SchemaType>,
#[serde(rename = "allOf")]
all_of: Vec<Schema>,
#[serde(flatten)]
details: SchemaDetails,
},
/// Schema with `type` as an array (OpenAPI 3.1 / JSON Schema 2020-12).
/// The canonical 3.1 way to express a nullable type is
/// `type: ["string", "null"]`. Listed before `Typed` so the array form
Expand All @@ -171,13 +181,6 @@ pub enum Schema {
#[serde(flatten)]
details: SchemaDetails,
},
/// AllOf composition
AllOf {
#[serde(rename = "allOf")]
all_of: Vec<Schema>,
#[serde(flatten)]
details: SchemaDetails,
},
/// Schema without explicit type (inferred from other fields)
Untyped {
#[serde(flatten)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
source: src/test_helpers.rs
assertion_line: 229
expression: "&generated_code"
---
//! Generated types from OpenAPI specification
//!
//! This file contains all the generated types for the API.
//! Do not edit manually - regenerate using the appropriate script.
#![allow(clippy::large_enum_variant)]
#![allow(clippy::format_in_format_args)]
#![allow(clippy::let_unit_value)]
#![allow(unreachable_patterns)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct Widget {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct WidgetExtra {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct WidgetBase {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
42 changes: 42 additions & 0 deletions tests/serde_json_value_reduction_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,48 @@ fn test_nested_allof_composition() {
assert!(result.contains("pub content: String"));
}

#[test]
fn test_allof_with_redundant_type_object_sibling() {
// Test AllOf composition that should flatten properties instead of using serde_json::Value
// even with type: object.
let spec = json!({
"openapi": "3.1.0",
"info": {"title": "Test", "version": "1.0"},
"components": {
"schemas": {
"Widget": {
"type": "object",
"allOf": [
{"$ref": "#/components/schemas/WidgetBase"},
{"$ref": "#/components/schemas/WidgetExtra"}
]
},
"WidgetBase": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
},
"WidgetExtra": {
"type": "object",
"properties": {
"name": {"type": "string"}
}
}
}
}
});

let result =
test_generation("allof_type_object_sibling_test", spec).expect("Generation failed");

assert!(result.contains("pub struct Widget"));
// Check different possible formats of string.
assert!(result.contains("pub id: Option<String>") || result.contains("pub id: String"));
assert!(result.contains("pub name: Option<String>") || result.contains("pub name: String"));
assert!(!result.contains("pub type Widget = serde_json::Value"));
}

#[test]
fn test_object_with_additional_properties() {
// Test that objects with additionalProperties correctly use BTreeMap<String, serde_json::Value>
Expand Down
22 changes: 20 additions & 2 deletions tests/server_raw_body_roundtrip_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,21 @@ mod tests {
}
}

fn sample_plugin() -> Plugin {
Plugin {
added_at: "2024-01-01T00:00:00Z".parse().unwrap(),
connection: PluginModeUnion::PluginSupervisedProps(PluginSupervisedProps {}),
description: None,
id: "plugin-one".to_string(),
manifest: PluginManifest::default(),
name: "Test Plugin".to_string(),
status: PluginStatus::PluginStatusActive(PluginStatusActive {
activated_at: "2024-01-01T00:00:00Z".parse().unwrap(),
}),
version: None,
}
}

#[async_trait::async_trait]
impl PluginsApi for Api {
async fn plugin_update_package(
Expand All @@ -412,7 +427,7 @@ mod tests {
self.captured
.send(("plugin".into(), body.map(|value| value.to_vec())))
.unwrap();
PluginUpdatePackageResponse::Ok(serde_json::json!({"updated": true}))
PluginUpdatePackageResponse::Ok(sample_plugin())
}
}

Expand All @@ -437,7 +452,10 @@ mod tests {
.plugin_update_package("plugin-one", Some(archive.clone()))
.await
.unwrap();
assert_eq!(response, serde_json::json!({"updated": true}));
assert_eq!(
serde_json::to_value(&response).unwrap(),
serde_json::to_value(sample_plugin()).unwrap()
);
assert_eq!(
captured_rx.recv().await.unwrap(),
("plugin".into(), Some(archive))
Expand Down
Loading