use alux_shape::ShapeOf;
use alux_shape_jsonschema::{JsonSchema, JsonSchemaShape};
use core::marker::PhantomData;
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenApiSource {
Path,
Query,
Header,
Cookie,
Body(&'static str),
Unstated,
}
#[derive(Debug, Clone)]
pub struct OpenApiArgument {
pub source: OpenApiSource,
pub stated: OpenApiStated,
}
#[derive(Debug, Clone)]
pub enum OpenApiStated {
Whole(Value),
Named(Vec<OpenApiNamed>),
}
#[derive(Debug, Clone)]
pub struct OpenApiNamed {
pub name: String,
pub schema: Value,
pub required: bool,
}
fn named_values(schema: &JsonSchemaShape, stated: Value) -> OpenApiStated {
let resolved = resolved(schema, &stated);
let Some(object) = resolved.as_object() else {
return OpenApiStated::Whole(stated);
};
if object.get("type").and_then(Value::as_str) != Some("object") {
return OpenApiStated::Whole(stated);
}
let Some(properties) = object.get("properties").and_then(Value::as_object) else {
return OpenApiStated::Whole(stated);
};
let required = object.get("required").and_then(Value::as_array).cloned().unwrap_or_default();
let named = properties
.iter()
.map(|(name, schema)| OpenApiNamed {
name: name.clone(),
schema: schema.clone(),
required: required.iter().any(|stated| stated.as_str() == Some(name)),
})
.collect();
OpenApiStated::Named(named)
}
fn resolved(schema: &JsonSchemaShape, stated: &Value) -> Value {
let Some(reference) = stated.get("$ref").and_then(Value::as_str) else {
return stated.clone();
};
let named = reference.rsplit('/').next().unwrap_or_default();
schema.definitions().get(named).cloned().unwrap_or_else(|| stated.clone())
}
macro_rules! openapi_inputs {
($($marker:ident => $source:expr, $stated:ident, $meaning:literal),+ $(,)?) => {
$(
#[doc = concat!("Describes ", $meaning, " in a document.")]
pub struct $marker<Input>(PhantomData<Input>);
impl<Input> OpenApiInputAlg for $marker<Input>
where
Input: ShapeOf<JsonSchemaShape, Shape = JsonSchema>,
{
fn describe(schema: &JsonSchemaShape) -> OpenApiArgument {
let stated = Input::shape_of(schema).into_value();
OpenApiArgument { source: $source, stated: $stated(schema, stated) }
}
}
)+
};
}
fn whole(_schema: &JsonSchemaShape, stated: Value) -> OpenApiStated {
OpenApiStated::Whole(stated)
}
pub trait OpenApiInputAlg {
fn describe(schema: &JsonSchemaShape) -> OpenApiArgument;
}
openapi_inputs! {
OpenApiPathInput => OpenApiSource::Path, whole, "a segment the path binds",
OpenApiQueryInput => OpenApiSource::Query, named_values, "a value in the query string",
OpenApiHeaderInput => OpenApiSource::Header, named_values, "a header the caller sent",
OpenApiCookieInput => OpenApiSource::Cookie, named_values, "a cookie the caller sent",
OpenApiBodyInput => OpenApiSource::Body("application/json"), whole, "a request body read as a document",
OpenApiFormInput => OpenApiSource::Body("application/x-www-form-urlencoded"), whole, "a form-encoded request body",
OpenApiMultipartInput => OpenApiSource::Body("multipart/form-data"), whole, "a request body arriving as parts",
OpenApiRawBodyInput => OpenApiSource::Body("application/octet-stream"), whole, "a request body taken as it arrived",
}
pub struct OpenApiUnstatedInput<Input>(PhantomData<Input>);
impl<Input> OpenApiInputAlg for OpenApiUnstatedInput<Input> {
fn describe(_schema: &JsonSchemaShape) -> OpenApiArgument {
OpenApiArgument { source: OpenApiSource::Unstated, stated: OpenApiStated::Whole(Value::Null) }
}
}
pub trait OpenApiInputsAlg {
fn describe(schema: &JsonSchemaShape) -> Vec<OpenApiArgument>;
}
impl OpenApiInputsAlg for () {
fn describe(_schema: &JsonSchemaShape) -> Vec<OpenApiArgument> {
Vec::new()
}
}
macro_rules! openapi_products {
($($input:ident),+ $(,)?) => {
impl<$($input),+> OpenApiInputsAlg for ($($input,)+)
where
$($input: OpenApiInputAlg,)+
{
fn describe(schema: &JsonSchemaShape) -> Vec<OpenApiArgument> {
vec![$($input::describe(schema),)+]
}
}
};
}
openapi_products!(I1);
openapi_products!(I1, I2);
openapi_products!(I1, I2, I3);
openapi_products!(I1, I2, I3, I4);
openapi_products!(I1, I2, I3, I4, I5);
openapi_products!(I1, I2, I3, I4, I5, I6);
openapi_products!(I1, I2, I3, I4, I5, I6, I7);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15);
openapi_products!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, I16);