use indexmap::IndexMap;
use schemars::Schema;
use crate::generate::GenContext;
use crate::openapi::{
self, Operation, Parameter, ParameterData, QueryStyle, ReferenceOr, RequestBody, Response,
};
use crate::Error;
#[cfg(feature = "macros")]
pub use aide_macros::OperationIo;
#[allow(unused_variables)]
pub trait OperationInput {
fn operation_input(ctx: &mut GenContext, operation: &mut Operation) {}
fn inferred_early_responses(
ctx: &mut GenContext,
operation: &mut Operation,
) -> Vec<(Option<u16>, Response)> {
Vec::new()
}
}
impl OperationInput for () {}
macro_rules! impl_operation_input {
( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
impl<$($ty,)*> OperationInput for ($($ty,)*)
where
$( $ty: OperationInput, )*
{
fn operation_input(ctx: &mut GenContext, operation: &mut Operation) {
$(
$ty::operation_input(ctx, operation);
)*
}
fn inferred_early_responses(
ctx: &mut GenContext,
operation: &mut Operation,
) -> Vec<(Option<u16>, Response)> {
let mut responses = Vec::new();
$(
responses.extend($ty::inferred_early_responses(ctx, operation));
)*
responses
}
}
};
}
all_the_tuples!(impl_operation_input);
#[doc(hidden)]
pub trait OperationHandler<I: OperationInput, O: OperationOutput> {}
macro_rules! impl_operation_handler {
( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
impl<Ret, F, $($ty,)*> OperationHandler<($($ty,)*), Ret::Output> for F
where
F: FnOnce($($ty,)*) -> Ret,
Ret: std::future::Future,
Ret::Output: OperationOutput,
$( $ty: OperationInput, )*
{}
};
}
impl<Ret, F> OperationHandler<(), Ret::Output> for F
where
F: FnOnce() -> Ret,
Ret: std::future::Future,
Ret::Output: OperationOutput,
{
}
all_the_tuples!(impl_operation_handler);
#[allow(unused_variables)]
pub trait OperationOutput {
type Inner;
fn operation_response(ctx: &mut GenContext, operation: &mut Operation) -> Option<Response> {
None
}
fn inferred_responses(
ctx: &mut GenContext,
operation: &mut Operation,
) -> Vec<(Option<u16>, Response)> {
Vec::new()
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamLocation {
Query,
Path,
Header,
Cookie,
}
#[tracing::instrument(skip_all)]
pub fn parameters_from_schema(
ctx: &mut GenContext,
schema: Schema,
location: ParamLocation,
) -> Vec<Parameter> {
let schema = ctx.resolve_schema(&schema);
let mut params = Vec::new();
if let Some(obj) = schema.as_object() {
for (name, schema) in obj
.get("properties")
.and_then(|p| p.as_object())
.into_iter()
.flatten()
{
let json_schema: Schema = schema
.clone()
.try_into()
.unwrap_or_else(|err| panic!("Failed to convert schema {schema}: {err:?}"));
match location {
ParamLocation::Query => {
params.push(Parameter::Query {
parameter_data: ParameterData {
name: name.clone(),
description: json_schema
.get("description")
.and_then(|d| d.as_str())
.map(String::from),
required: obj
.get("required")
.and_then(|r| r.as_array())
.is_some_and(|r| r.contains(&name.as_str().into())),
format: crate::openapi::ParameterSchemaOrContent::Schema(
openapi::SchemaObject {
json_schema,
example: None,
external_docs: None,
},
),
extensions: Default::default(),
deprecated: None,
example: None,
examples: IndexMap::default(),
explode: None,
},
allow_reserved: false,
style: QueryStyle::Form,
allow_empty_value: None,
});
}
ParamLocation::Path => {
params.push(Parameter::Path {
parameter_data: ParameterData {
name: name.clone(),
description: json_schema
.get("description")
.and_then(|d| d.as_str())
.map(String::from),
required: obj
.get("required")
.and_then(|r| r.as_array())
.is_some_and(|r| r.contains(&name.as_str().into())),
format: crate::openapi::ParameterSchemaOrContent::Schema(
openapi::SchemaObject {
json_schema,
example: None,
external_docs: None,
},
),
extensions: Default::default(),
deprecated: None,
example: None,
examples: IndexMap::default(),
explode: None,
},
style: openapi::PathStyle::Simple,
});
}
ParamLocation::Header => {
params.push(Parameter::Header {
parameter_data: ParameterData {
name: name.clone(),
description: json_schema
.get("description")
.and_then(|d| d.as_str())
.map(String::from),
required: obj
.get("required")
.and_then(|r| r.as_array())
.is_some_and(|r| r.contains(&name.as_str().into())),
format: crate::openapi::ParameterSchemaOrContent::Schema(
openapi::SchemaObject {
json_schema,
example: None,
external_docs: None,
},
),
extensions: Default::default(),
deprecated: None,
example: None,
examples: IndexMap::default(),
explode: None,
},
style: openapi::HeaderStyle::Simple,
});
}
ParamLocation::Cookie => {
params.push(Parameter::Cookie {
parameter_data: ParameterData {
name: name.clone(),
description: json_schema
.get("description")
.and_then(|d| d.as_str())
.map(String::from),
required: obj
.get("required")
.and_then(|r| r.as_array())
.is_some_and(|r| r.contains(&name.as_str().into())),
format: crate::openapi::ParameterSchemaOrContent::Schema(
openapi::SchemaObject {
json_schema,
example: None,
external_docs: None,
},
),
extensions: Default::default(),
deprecated: None,
example: None,
examples: IndexMap::default(),
explode: None,
},
style: openapi::CookieStyle::Form,
});
}
}
}
}
params
}
pub fn set_body(ctx: &mut GenContext, operation: &mut Operation, body: RequestBody) {
if operation.request_body.is_some() {
ctx.error(Error::DuplicateRequestBody);
}
operation.request_body = Some(ReferenceOr::Item(body));
}
pub fn add_parameters(
ctx: &mut GenContext,
operation: &mut Operation,
params: impl IntoIterator<Item = Parameter>,
) {
for param in params {
if operation.parameters.iter().any(|p| match p {
ReferenceOr::Reference { .. } => false,
ReferenceOr::Item(p) => p.parameter_data_ref().name == param.parameter_data_ref().name,
}) {
ctx.error(Error::DuplicateParameter(
param.parameter_data_ref().name.clone(),
));
}
operation.parameters.push(ReferenceOr::Item(param));
}
}