use crate::OpenApiHandlerImpl;
use alux_http::{
BytesOutAlg, EmptyOutAlg, FileOutAlg, HeaderNameAlg, HeaderOutAlg, HtmlOutAlg, HttpErrorAlg, HttpStatus,
JsonOutAlg, OutputAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg,
};
use alux_shape::ShapeOf;
use alux_shape_jsonschema::{JsonSchema, JsonSchemaShape};
use core::marker::PhantomData;
use serde_json::Value;
const BYTES: &str = "application/octet-stream";
const LOCATION: &str = "location";
#[derive(Debug, Clone)]
pub struct OpenApiAnswer {
pub status: HttpStatus,
pub headers: Vec<&'static str>,
pub content_type: Option<&'static str>,
pub schema: Option<Value>,
}
impl OpenApiAnswer {
pub fn content(status: HttpStatus, content_type: &'static str, schema: Value) -> Self {
Self { status, headers: Vec::new(), content_type: Some(content_type), schema: Some(schema) }
}
pub fn bodiless(status: HttpStatus) -> Self {
Self { status, headers: Vec::new(), content_type: None, schema: None }
}
}
pub trait OpenApiOutputAlg<From> {
fn answers(schema: &JsonSchemaShape) -> Vec<OpenApiAnswer>;
}
macro_rules! openapi_outputs {
($($output:ident => $content_type:literal, $meaning:literal),+ $(,)?) => {
$(
#[doc = concat!("Describes ", $meaning, " answers in a document.")]
pub struct $output;
impl<From> OutputAlg<From> for $output {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl<From> OpenApiOutputAlg<From> for $output
where
From: ShapeOf<JsonSchemaShape, Shape = JsonSchema>,
{
fn answers(schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
vec![OpenApiAnswer::content(HttpStatus::OK, $content_type, From::shape_of(schema).into_value())]
}
}
)+
};
}
openapi_outputs! {
OpenApiJsonOutput => "application/json", "JSON",
OpenApiTextOutput => "text/plain", "plain-text",
OpenApiHtmlOutput => "text/html", "HTML",
}
macro_rules! openapi_bytes {
($($output:ident => $meaning:literal),+ $(,)?) => {
$(
#[doc = concat!("Describes ", $meaning, " answers in a document.")]
pub struct $output;
impl<From> OutputAlg<From> for $output {
type Output = From;
fn output(from: From) -> From {
from
}
}
)+
};
}
openapi_bytes! {
OpenApiBytesOutput => "raw-byte",
OpenApiFileOutput => "streamed-file",
OpenApiStreamOutput => "streamed",
}
impl<From> OpenApiOutputAlg<From> for OpenApiBytesOutput {
fn answers(_schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
vec![OpenApiAnswer::content(HttpStatus::OK, BYTES, binary())]
}
}
impl<From> OpenApiOutputAlg<From> for OpenApiStreamOutput {
fn answers(_schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
vec![OpenApiAnswer::content(HttpStatus::OK, BYTES, binary())]
}
}
impl<File, Error> OpenApiOutputAlg<(Result<File, Error>, String)> for OpenApiFileOutput
where
Error: HttpErrorAlg,
{
fn answers(_schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
let failures = Error::HTTP_STATUSES.iter().map(|status| OpenApiAnswer::content(*status, "text/plain", text()));
core::iter::once(OpenApiAnswer::content(HttpStatus::OK, BYTES, binary())).chain(failures).collect()
}
}
pub struct OpenApiEmptyOutput;
impl<From> OutputAlg<From> for OpenApiEmptyOutput {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl OpenApiOutputAlg<()> for OpenApiEmptyOutput {
fn answers(_schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
vec![OpenApiAnswer::bodiless(HttpStatus::NO_CONTENT)]
}
}
pub struct OpenApiRedirectOutput;
impl<From> OutputAlg<From> for OpenApiRedirectOutput {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl<From> OpenApiOutputAlg<From> for OpenApiRedirectOutput {
fn answers(_schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
vec![OpenApiAnswer { headers: vec![LOCATION], ..OpenApiAnswer::bodiless(HttpStatus::SEE_OTHER) }]
}
}
pub struct OpenApiHeaderOutput<Inner, Name>(PhantomData<fn(Inner, Name)>);
impl<Inner, Name, From> OutputAlg<From> for OpenApiHeaderOutput<Inner, Name> {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl<Inner, Name, Value, Rest> OpenApiOutputAlg<(Value, Rest)> for OpenApiHeaderOutput<Inner, Name>
where
Inner: OpenApiOutputAlg<Rest>,
Name: HeaderNameAlg,
{
fn answers(schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
Inner::answers(schema)
.into_iter()
.map(|answer| {
let mut carried = answer;
if carried.status.is_success() {
carried.headers.push(Name::HEADER_NAME);
}
carried
})
.collect()
}
}
impl<Context> HeaderOutAlg for OpenApiHandlerImpl<Context> {
type Header<Inner, Name> = OpenApiHeaderOutput<Inner, Name>;
}
pub struct OpenApiStatusOutput<Inner, const CODE: u16>(PhantomData<Inner>);
impl<Inner, From, const CODE: u16> OutputAlg<From> for OpenApiStatusOutput<Inner, CODE> {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl<Inner, From, const CODE: u16> OpenApiOutputAlg<From> for OpenApiStatusOutput<Inner, CODE>
where
Inner: OpenApiOutputAlg<From>,
{
fn answers(schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
Inner::answers(schema)
.into_iter()
.map(|answer| OpenApiAnswer { status: HttpStatus::new(CODE), ..answer })
.collect()
}
}
pub struct OpenApiResultOutput<Inner, Error>(PhantomData<fn(Inner, Error)>);
impl<Inner, Error, From> OutputAlg<From> for OpenApiResultOutput<Inner, Error> {
type Output = From;
fn output(from: From) -> From {
from
}
}
impl<Inner, Error, Value> OpenApiOutputAlg<Result<Value, Error>> for OpenApiResultOutput<Inner, Error>
where
Inner: OpenApiOutputAlg<Value>,
Error: HttpErrorAlg,
{
fn answers(schema: &JsonSchemaShape) -> Vec<OpenApiAnswer> {
let failures = Error::HTTP_STATUSES.iter().map(|status| OpenApiAnswer::content(*status, "text/plain", text()));
Inner::answers(schema).into_iter().chain(failures).collect()
}
}
fn text() -> Value {
serde_json::json!({ "type": "string" })
}
fn binary() -> Value {
serde_json::json!({ "type": "string", "format": "binary" })
}
macro_rules! openapi_kinds {
($($alg:ident => $selected:ident, $output:ty),+ $(,)?) => {
$(
impl<Context> $alg for OpenApiHandlerImpl<Context> {
type $selected<From> = $output;
}
)+
};
}
openapi_kinds! {
JsonOutAlg => Json, OpenApiJsonOutput,
FileOutAlg => File, OpenApiFileOutput,
TextOutAlg => Text, OpenApiTextOutput,
HtmlOutAlg => Html, OpenApiHtmlOutput,
BytesOutAlg => Bytes, OpenApiBytesOutput,
EmptyOutAlg => Empty, OpenApiEmptyOutput,
RedirectOutAlg => Redirect, OpenApiRedirectOutput,
StreamOutAlg => Stream, OpenApiStreamOutput,
}
impl<Context> StatusOutAlg for OpenApiHandlerImpl<Context> {
type Status<Inner, const CODE: u16> = OpenApiStatusOutput<Inner, CODE>;
}
impl<Context> ResultOutAlg for OpenApiHandlerImpl<Context> {
type Result<Inner, Error> = OpenApiResultOutput<Inner, Error>;
}