Skip to content

Commit

Permalink
Support actix web either
Browse files Browse the repository at this point in the history
  • Loading branch information
rlebran committed Jul 10, 2024
1 parent 8f3f8d1 commit 0ab6ce2
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 2 deletions.
112 changes: 111 additions & 1 deletion apistos-core/src/api_component.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::ApiErrorComponent;
#[cfg(feature = "actix")]
use crate::{PathItemDefinition, ResponseWrapper};
use actix_web::Either;
use apistos_models::paths::{MediaType, Parameter, RequestBody, Response, Responses};
use apistos_models::reference_or::ReferenceOr;
use apistos_models::security::SecurityScheme;
use apistos_models::Schema;
use schemars::schema::{ArrayValidation, InstanceType, SchemaObject, SingleOrVec};
use schemars::schema::{ArrayValidation, InstanceType, SchemaObject, SingleOrVec, SubschemaValidation};
use std::collections::BTreeMap;
#[cfg(feature = "actix")]
use std::future::Future;
Expand Down Expand Up @@ -175,6 +176,115 @@ where
}
}

impl<T, E> ApiComponent for Either<T, E>
where
T: ApiComponent,
E: ApiComponent,
{
fn required() -> bool {
T::required() && E::required()
}

fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
let mut child_schemas = T::child_schemas();
child_schemas.append(&mut E::child_schemas());
child_schemas
}

fn raw_schema() -> Option<ReferenceOr<Schema>> {
match (T::raw_schema(), E::raw_schema()) {
(Some(raw_schema1), Some(raw_schema2)) => {
let raw_schema1 = match raw_schema1 {
ReferenceOr::Object(schema_obj) => schema_obj,
ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
reference: Some(_ref),
..Default::default()
}),
};
let raw_schema2 = match raw_schema2 {
ReferenceOr::Object(schema_obj) => schema_obj,
ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
reference: Some(_ref),
..Default::default()
}),
};
Some(ReferenceOr::Object(Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![raw_schema1, raw_schema2]),
..Default::default()
})),
..Default::default()
})))
}
(Some(raw_schema1), None) => Some(raw_schema1),
(None, Some(raw_schema2)) => Some(raw_schema2),
(None, None) => None,
}
}

fn schema() -> Option<(String, ReferenceOr<Schema>)> {
match (T::schema(), E::schema()) {
(Some(schema1), Some(schema2)) => {
let (schema_name1, schema1) = schema1;
let schema1 = match schema1 {
ReferenceOr::Object(schema_obj) => schema_obj,
ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
reference: Some(_ref),
..Default::default()
}),
};
let (schema_name2, schema2) = schema2;
let schema2 = match schema2 {
ReferenceOr::Object(schema_obj) => schema_obj,
ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
reference: Some(_ref),
..Default::default()
}),
};
let schema = ReferenceOr::Object(Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![schema1, schema2]),
..Default::default()
})),
..Default::default()
}));
let schema_name = format!("Either{}Or{}", schema_name1, schema_name2);
Some((schema_name, schema))
}
(Some(schema1), None) => Some(schema1),
(None, Some(schema2)) => Some(schema2),
(None, None) => None,
}
}

// We expect error to be present only for response part
fn error_responses() -> Vec<(String, Response)> {
let mut error_responses = T::error_responses();
error_responses.append(&mut E::error_responses());
error_responses
}

// We expect error to be present only for response part
fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
let mut error_schemas = E::error_schemas();
error_schemas.append(&mut T::error_schemas());
error_schemas
}

fn responses(content_type: Option<String>) -> Option<Responses> {
let responses = T::responses(content_type.clone());
match responses {
None => E::responses(content_type),
Some(mut responses) => {
responses
.responses
.append(&mut E::responses(content_type).map(|r| r.responses).unwrap_or_default());
Some(responses)
}
}
}
}

#[cfg(feature = "actix")]
impl<T> ApiComponent for actix_web::web::Data<T> {
fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
Expand Down
1 change: 1 addition & 0 deletions apistos-gen-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ license.workspace = true
[dependencies]
actix-multipart = { workspace = true }
actix-web = { workspace = true }
actix-web-grants = { workspace = true }
assert-json-diff = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
futures-core = { workspace = true }
Expand Down
105 changes: 104 additions & 1 deletion apistos-gen-test/src/tests/api_operation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use actix_multipart::form::MultipartForm;
use actix_web::dev::ServiceRequest;
use actix_web::http::header::ContentType;
use actix_web::web::Json;
use actix_web::{HttpResponse, Responder};
use actix_web::{Error, HttpResponse, Responder};
use assert_json_diff::assert_json_eq;
use schemars::_serde_json::json;
use std::collections::HashSet;
use uuid::Uuid;

use apistos::actix::{AcceptedJson, CreatedJson, NoContent};
Expand Down Expand Up @@ -1442,3 +1444,104 @@ fn api_operation_root_vec() {
})
);
}

#[test]
fn api_operation_actix_web_grant() {
async fn extract(_req: &ServiceRequest) -> Result<HashSet<String>, Error> {
Ok(Default::default())
}

/// Add a new pet to the store
/// Add a new pet to the store
/// Plop
#[actix_web_grants::protect("ADMIN")]
#[api_operation(tag = "pet")]
pub(crate) async fn test(
_body: Json<test_models::Test>,
) -> Result<Json<Vec<test_models::TestResult>>, test_models::ErrorResponse> {
Ok(Json(vec![test_models::TestResult { id: 0 }]))
}

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"
},
"TestResult": {
"properties": {
"id": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"id"
],
"title": "TestResult",
"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": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TestResult"
}
}
}
},
"description": ""
},
"405": {
"description": "Invalid input"
}
},
"summary": "Add a new pet to the store",
"tags": [
"pet"
]
})
);
}

0 comments on commit 0ab6ce2

Please sign in to comment.