Repository: netwo-io/apistos
Pull request #63 (2024-03-26 10:33:52)

Fix simple response type operation
Fix an issue with response generation for simple types


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index ef2a94f..24d80f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,7 +46,7 @@ chrono = "0.4"
 garde = { version = "0.18", features = ["derive", "serde"] }
 rust_decimal = "1"
 serde_qs = "0.12"
-uuid = "1"
+uuid = { version = "1", features = ["serde", "v4"] }
 url = "2"
 
 [workspace.lints.rust]
diff --git a/apistos-core/src/api_component.rs b/apistos-core/src/api_component.rs
index 962b608..093c4de 100644
--- a/apistos-core/src/api_component.rs
+++ b/apistos-core/src/api_component.rs
@@ -265,6 +265,20 @@ where
           ..Default::default()
         }),
       ));
+    } else if let Some(schema) = Self::raw_schema() {
+      responses.push((
+        "200".to_owned(),
+        ReferenceOr::Object(Response {
+          content: BTreeMap::from_iter(vec![(
+            content_type.unwrap_or_else(Self::content_type),
+            MediaType {
+              schema: Some(schema),
+              ..Default::default()
+            },
+          )]),
+          ..Default::default()
+        }),
+      ));
     } else if let Some(content_type) = content_type {
       responses.push((
         "200".to_owned(),
diff --git a/apistos/src/actix.rs b/apistos/src/actix.rs
index 5830122..107afc0 100644
--- a/apistos/src/actix.rs
+++ b/apistos/src/actix.rs
@@ -88,7 +88,7 @@ where
 
   fn responses(_content_type: Option<String>) -> Option<Responses> {
     let status = StatusCode::ACCEPTED;
-    response_from_schema(status, Self::schema())
+    response_from_schema(status, Self::schema()).or_else(|| response_from_raw_schema(status, Self::raw_schema()))
   }
 }
 
@@ -130,7 +130,7 @@ where
 
   fn responses(_content_type: Option<String>) -> Option<Responses> {
     let status = StatusCode::CREATED;
-    response_from_schema(status, Self::schema())
+    response_from_schema(status, Self::schema()).or_else(|| response_from_raw_schema(status, Self::raw_schema()))
   }
 }
 
@@ -161,6 +161,31 @@ fn response_from_schema(status: StatusCode, schema: Option<(String, ReferenceOr<
   })
 }
 
+fn response_from_raw_schema(status: StatusCode, raw_schema: Option<ReferenceOr<Schema>>) -> Option<Responses> {
+  raw_schema.map(|schema| match schema {
+    ReferenceOr::Reference { _ref } => Responses {
+      responses: BTreeMap::from_iter(vec![(status.as_str().to_string(), ReferenceOr::Reference { _ref })]),
+      ..Default::default()
+    },
+    schema @ ReferenceOr::Object(_) => {
+      let response = Response {
+        content: BTreeMap::from_iter(vec![(
+          "application/json".to_string(),
+          MediaType {
+            schema: Some(schema),
+            ..Default::default()
+          },
+        )]),
+        ..Default::default()
+      };
+      Responses {
+        responses: BTreeMap::from_iter(vec![(status.as_str().to_string(), ReferenceOr::Object(response))]),
+        ..Default::default()
+      }
+    }
+  })
+}
+
 #[cfg(test)]
 mod test {
   #![allow(clippy::expect_used)]


Test patch:
diff --git a/apistos-gen-test/Cargo.toml b/apistos-gen-test/Cargo.toml
index b10cf70..04bd83d 100644
--- a/apistos-gen-test/Cargo.toml
+++ b/apistos-gen-test/Cargo.toml
@@ -19,12 +19,13 @@ actix-web = { workspace = true }
 assert-json-diff = { workspace = true }
 chrono = { workspace = true, features = ["serde"] }
 futures-core = { workspace = true }
-apistos = { path = "../apistos", features = ["multipart"] }
+apistos = { path = "../apistos", features = ["multipart", "uuid"] }
 apistos-core = { path = "../apistos-core", version = "0.1.6" }
 apistos-gen = { path = "../apistos-gen", version = "0.1.6" }
 schemars = { workspace = true }
 serde = { version = "1.0.188", features = ["derive"] }
 serde_json = { workspace = true }
+uuid = { workspace = true, features = ["v4"] }
 
 [lints]
 workspace = true
diff --git a/apistos-gen-test/src/tests/api_operation.rs b/apistos-gen-test/src/tests/api_operation.rs
index ae64d91..1b80457 100644
--- a/apistos-gen-test/src/tests/api_operation.rs
+++ b/apistos-gen-test/src/tests/api_operation.rs
@@ -7,6 +7,7 @@ use apistos_core::PathItemDefinition;
 use apistos_gen::api_operation;
 use assert_json_diff::assert_json_eq;
 use schemars::_serde_json::json;
+use uuid::Uuid;
 
 #[allow(clippy::todo)]
 mod test_models {
@@ -199,6 +200,86 @@ fn api_operation() {
   );
 }
 
+#[test]
+#[allow(dead_code)]
+fn api_operation_simple_response() {
+  /// Add a new pet to the store
+  /// Add a new pet to the store
+  /// Plop
+  #[api_operation(tag = "pet")]
+  pub(crate) async fn test(_body: Json<test_models::Test>) -> Result<Json<Uuid>, test_models::ErrorResponse> {
+    Ok(Json(Uuid::new_v4()))
+  }
+
+  let components = __openapi_test::components();
+  // only one component here because: error does not have schema and Test is used both for query and response
+  assert_eq!(components.len(), 1);
+  let components = serde_json::to_value(components).expect("Unable to serialize as Json");
+
+  let operation = __openapi_test::operation();
+  let operation = serde_json::to_value(operation).expect("Unable to serialize as Json");
+
+  assert_json_eq!(
+    components,
+    json!([
+      {
+        "schemas": {
+          "Test": {
+            "properties": {
+              "test": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "test"
+            ],
+            "title": "Test",
+            "type": "object"
+          }
+        }
+      }
+    ])
+  );
+  assert_json_eq!(
+    operation,
+    json!({
+      "deprecated": false,
+      "description": "Add a new pet to the store\\\nPlop",
+      "requestBody": {
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Test"
+            }
+          }
+        },
+        "required": true
+      },
+      "responses": {
+        "200": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "format": "uuid",
+                "title": "Uuid",
+                "type": "string"
+              }
+            }
+          },
+          "description": ""
+        },
+        "405": {
+          "description": "Invalid input"
+        }
+      },
+      "summary": "Add a new pet to the store",
+      "tags": [
+        "pet"
+      ]
+    })
+  );
+}
+
 #[test]
 #[allow(dead_code)]
 fn api_operation_without_parameters() {
@@ -434,6 +515,86 @@ fn api_operation_created_json() {
   );
 }
 
+#[test]
+#[allow(dead_code)]
+fn api_operation_created_json_simple_response() {
+  /// Add a new pet to the store
+  /// Add a new pet to the store
+  /// Plop
+  #[api_operation(tag = "pet")]
+  pub(crate) async fn test(_body: Json<test_models::Test>) -> Result<CreatedJson<Uuid>, test_models::ErrorResponse> {
+    Ok(CreatedJson(Uuid::new_v4()))
+  }
+
+  let components = __openapi_test::components();
+  // only one component here because: error does not have schema and Test is used both for query and response
+  assert_eq!(components.len(), 1);
+  let components = serde_json::to_value(components).expect("Unable to serialize as Json");
+
+  let operation = __openapi_test::operation();
+  let operation = serde_json::to_value(operation).expect("Unable to serialize as Json");
+
+  assert_json_eq!(
+    components,
+    json!([
+      {
+        "schemas": {
+          "Test": {
+            "properties": {
+              "test": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "test"
+            ],
+            "title": "Test",
+            "type": "object"
+          }
+        }
+      }
+    ])
+  );
+  assert_json_eq!(
+    operation,
+    json!({
+      "deprecated": false,
+      "description": "Add a new pet to the store\\\nPlop",
+      "requestBody": {
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "#/components/schemas/Test"
+            }
+          }
+        },
+        "required": true
+      },
+      "responses": {
+        "201": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "format": "uuid",
+                "title": "Uuid",
+                "type": "string"
+              }
+            }
+          },
+          "description": ""
+        },
+        "405": {
+          "description": "Invalid input"
+        }
+      },
+      "summary": "Add a new pet to the store",
+      "tags": [
+        "pet"
+      ]
+    })
+  );
+}
+
 #[test]
 #[allow(dead_code)]
 fn api_operation_accepted_json() {