use utoipa::openapi::path::{Operation, ParameterBuilder, ParameterIn};
use utoipa::openapi::schema::{KnownFormat, SchemaFormat};
use utoipa::openapi::{ObjectBuilder, RefOr, Required, Schema, Type};
use utoipa::{IntoParams, PartialSchema};
use crate::headers::DocumentedHeader;
pub trait DocQueryParams {
fn describe(op: &mut Operation);
}
pub trait DocPathParams {
fn describe(op: &mut Operation, path_param_names: &[&'static str]);
}
pub trait PathScalar: sealed::Sealed {
fn path_scalar_schema() -> RefOr<Schema>;
}
mod sealed {
pub trait Sealed {}
}
pub trait DocHeaderParams {
fn describe(op: &mut Operation);
}
pub trait DocRequestBody {
fn describe(op: &mut Operation);
}
pub trait DocOperationSecurity {
fn describe(op: &mut Operation);
}
impl<T: IntoParams> DocQueryParams for axum::extract::Query<T> {
fn describe(op: &mut Operation) {
let params = T::into_params(|| Some(ParameterIn::Query));
if params.is_empty() {
return;
}
op.parameters.get_or_insert_with(Vec::new).extend(params);
}
}
impl<T: IntoParams> DocPathParams for axum::extract::Path<T> {
fn describe(op: &mut Operation, _path_param_names: &[&'static str]) {
let params = T::into_params(|| Some(ParameterIn::Path));
if params.is_empty() {
return;
}
op.parameters.get_or_insert_with(Vec::new).extend(params);
}
}
fn push_scalar_path<T: PathScalar>(op: &mut Operation, name: &str) {
let param = ParameterBuilder::new()
.name(name)
.parameter_in(ParameterIn::Path)
.required(Required::True)
.schema(Some(T::path_scalar_schema()))
.build();
op.parameters.get_or_insert_with(Vec::new).push(param);
}
fn scalar_schema(ty: Type, fmt: Option<KnownFormat>) -> RefOr<Schema> {
let mut b = ObjectBuilder::new().schema_type(ty);
if let Some(f) = fmt {
b = b.format(Some(SchemaFormat::KnownFormat(f)));
}
RefOr::T(Schema::Object(b.build()))
}
fn name_at(names: &[&'static str], index: usize) -> String {
names
.get(index)
.map(|s| (*s).to_string())
.unwrap_or_else(|| format!("param{index}"))
}
macro_rules! impl_path_scalar {
($($t:ty => ($ty_enum:expr, $fmt:expr)),* $(,)?) => {
$(
impl sealed::Sealed for $t {}
impl PathScalar for $t {
fn path_scalar_schema() -> RefOr<Schema> {
scalar_schema($ty_enum, $fmt)
}
}
)*
};
}
impl_path_scalar!(
bool => (Type::Boolean, None),
i8 => (Type::Integer, Some(KnownFormat::Int32)),
i16 => (Type::Integer, Some(KnownFormat::Int32)),
i32 => (Type::Integer, Some(KnownFormat::Int32)),
i64 => (Type::Integer, Some(KnownFormat::Int64)),
i128 => (Type::Integer, None),
isize => (Type::Integer, None),
u8 => (Type::Integer, Some(KnownFormat::Int32)),
u16 => (Type::Integer, Some(KnownFormat::Int32)),
u32 => (Type::Integer, Some(KnownFormat::Int32)),
u64 => (Type::Integer, Some(KnownFormat::Int64)),
u128 => (Type::Integer, None),
usize => (Type::Integer, None),
f32 => (Type::Number, Some(KnownFormat::Float)),
f64 => (Type::Number, Some(KnownFormat::Double)),
String => (Type::String, None),
);
impl sealed::Sealed for uuid::Uuid {}
impl PathScalar for uuid::Uuid {
fn path_scalar_schema() -> RefOr<Schema> {
scalar_schema(Type::String, Some(KnownFormat::Uuid))
}
}
pub trait DocPathScalar {
fn describe_scalar(op: &mut Operation, path_param_names: &[&'static str]);
}
impl<T: PathScalar> DocPathScalar for axum::extract::Path<T> {
fn describe_scalar(op: &mut Operation, path_param_names: &[&'static str]) {
let name = name_at(path_param_names, 0);
push_scalar_path::<T>(op, &name);
}
}
macro_rules! impl_tuple_path {
($($idx:tt => $T:ident),+ $(,)?) => {
impl<$($T: PathScalar),+> DocPathScalar for axum::extract::Path<($($T,)+)> {
fn describe_scalar(op: &mut Operation, path_param_names: &[&'static str]) {
$(
let name = name_at(path_param_names, $idx);
push_scalar_path::<$T>(op, &name);
)+
}
}
};
}
impl_tuple_path!(0 => T1, 1 => T2);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3, 3 => T4);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7);
impl_tuple_path!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8);
impl<H: DocumentedHeader> DocHeaderParams for crate::extractor::Header<H> {
fn describe(op: &mut Operation) {
use utoipa::openapi::path::{ParameterBuilder, ParameterIn as InLoc};
use utoipa::openapi::{ObjectBuilder, RefOr, Required, Schema, Type};
let mut b = ParameterBuilder::new()
.name(H::name())
.parameter_in(InLoc::Header)
.required(Required::True)
.schema(Some(RefOr::T(Schema::Object(
ObjectBuilder::new().schema_type(Type::String).build(),
))));
let desc = H::description();
if !desc.is_empty() {
b = b.description(Some(desc.to_string()));
}
if let Some(ex) = H::example() {
b = b.example(Some(serde_json::Value::String(ex.to_string())));
}
op.parameters.get_or_insert_with(Vec::new).push(b.build());
}
}
impl<T: utoipa::ToSchema + PartialSchema + 'static> DocRequestBody for axum::Json<T> {
fn describe(op: &mut Operation) {
use utoipa::openapi::request_body::RequestBodyBuilder;
use utoipa::openapi::{ContentBuilder, Required};
let content = ContentBuilder::new().schema(Some(T::schema())).build();
let body = RequestBodyBuilder::new()
.content("application/json", content)
.required(Some(Required::True))
.build();
op.request_body = Some(body);
}
}