Skip to main content

alux_http_openapi/
handler.rs

1//! Reads one endpoint as what a document says about it.
2
3use crate::input::{
4    OpenApiBodyInput, OpenApiCookieInput, OpenApiFormInput, OpenApiHeaderInput, OpenApiInputsAlg,
5    OpenApiMultipartInput, OpenApiPathInput, OpenApiQueryInput, OpenApiRawBodyInput, OpenApiUnstatedInput,
6};
7use crate::output::OpenApiOutputAlg;
8use crate::route::{OpenApiEndpoint, OpenApiRoute, OpenApiRouteImpl, OpenApiSelector};
9use alux_ext::{ApplyAlg, HandlerContextAlg, OperationAlg};
10use alux_http::{
11    HandlerAlg, HandlerEndpointAlg, HttpInputAlg, HttpMethod, HttpSelectorAlg, OutputKindAlg, RouteAlg, RoutePath,
12    SelectorAlg,
13};
14use alux_shape::Spelling;
15use alux_shape_jsonschema::JsonSchemaShape;
16use serde_json::{Value, json};
17use std::sync::Arc;
18
19/// Interprets typed HTTP programs as the `OpenAPI` document that describes them.
20///
21/// Nothing is applied. Every endpoint is read for what a caller would have to state and what it
22/// would be answered with, which is why this interpretation asks for shapes where the others ask
23/// for extractors.
24pub struct OpenApiHandlerImpl<Context> {
25    schema: JsonSchemaShape,
26    context: core::marker::PhantomData<fn() -> Context>,
27}
28
29impl<Context> Default for OpenApiHandlerImpl<Context> {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl<Context> OpenApiHandlerImpl<Context> {
36    /// Describes a surface, naming shapes where an `OpenAPI` document keeps them.
37    pub fn new() -> Self {
38        Self {
39            schema: JsonSchemaShape::new(Spelling::Snake, "#/components/schemas/"),
40            context: core::marker::PhantomData,
41        }
42    }
43
44    /// Returns the whole document for a surface, under a title and a version.
45    pub fn document(&self, title: &str, version: &str, route: &OpenApiRoute) -> Value {
46        let paths = route.paths_object();
47        let schemas = self.schema.definitions().into_iter().collect::<serde_json::Map<_, _>>();
48        let mut document = json!({
49            "openapi": "3.1.0",
50            "info": { "title": title, "version": version },
51            "paths": paths,
52        });
53        if !schemas.is_empty()
54            && let Some(document) = document.as_object_mut()
55        {
56            document.insert("components".into(), json!({ "schemas": Value::Object(schemas) }));
57        }
58
59        document
60    }
61}
62
63impl<Context> HandlerContextAlg<Context> for OpenApiHandlerImpl<Context>
64where
65    Context: Send + Sync + 'static,
66{
67    type Handle = Arc<Context>;
68}
69
70impl<Context> HandlerAlg for OpenApiHandlerImpl<Context> {
71    type Endpoint = OpenApiEndpoint;
72}
73
74impl<Context> HttpInputAlg for OpenApiHandlerImpl<Context> {
75    type Path<I> = OpenApiPathInput<I>;
76    type Query<I> = OpenApiQueryInput<I>;
77    type Body<I> = OpenApiBodyInput<I>;
78    type Form<I> = OpenApiFormInput<I>;
79    type Multipart<I> = OpenApiMultipartInput<I>;
80    type RawBody<I> = OpenApiRawBodyInput<I>;
81    type Header<I> = OpenApiHeaderInput<I>;
82    type Cookie<I> = OpenApiCookieInput<I>;
83    // A caller states an authentication value in a header; an endpoint context it never states.
84    type Auth<I> = OpenApiHeaderInput<I>;
85    type Context<I> = OpenApiUnstatedInput<I>;
86}
87
88impl<Context, Inputs, Args, Transform, Answering, Output>
89    HandlerEndpointAlg<Arc<Context>, Inputs, Args, Transform, Output> for OpenApiHandlerImpl<Context>
90where
91    Context: Send + Sync + 'static,
92    Inputs: OpenApiInputsAlg,
93    Transform: OutputKindAlg<Self, Output, Transform = Answering>,
94    Answering: OpenApiOutputAlg<Output>,
95{
96    fn finish_handler<Handler>(&self, _handler: Handler) -> <Self as HandlerAlg>::Endpoint
97    where
98        Handler: OperationAlg + ApplyAlg<Arc<Context>, Args, Output = Output> + Send + Sync + 'static,
99    {
100        // Input roles accumulate in the same order as the handler's arguments, so a role and the
101        // name it was declared under are the same position of two products.
102        let arguments = Handler::ARG_NAMES.iter().copied().zip(Inputs::describe(&self.schema)).collect();
103
104        OpenApiEndpoint {
105            operation: Handler::NAME,
106            doc: Handler::DOC,
107            arguments,
108            answers: Answering::answers(&self.schema),
109        }
110    }
111}
112
113impl<Context> SelectorAlg for OpenApiHandlerImpl<Context> {
114    type Selector = OpenApiSelector;
115
116    fn identity(&self) -> Self::Selector {
117        SelectorAlg::identity(&OpenApiRouteImpl)
118    }
119
120    fn compose(&self, first: Self::Selector, second: Self::Selector) -> Self::Selector {
121        SelectorAlg::compose(&OpenApiRouteImpl, first, second)
122    }
123}
124
125impl<Context> RouteAlg for OpenApiHandlerImpl<Context> {
126    type Route = OpenApiRoute;
127    type Selector = OpenApiSelector;
128    type Endpoint = OpenApiEndpoint;
129
130    fn initial(&self) -> Self::Route {
131        RouteAlg::initial(&OpenApiRouteImpl)
132    }
133
134    fn coproduct(&self, left: Self::Route, right: Self::Route) -> Self::Route {
135        RouteAlg::coproduct(&OpenApiRouteImpl, left, right)
136    }
137
138    fn precompose(&self, selector: Self::Selector, route: Self::Route) -> Self::Route {
139        RouteAlg::precompose(&OpenApiRouteImpl, selector, route)
140    }
141
142    fn lift(&self, endpoint: Self::Endpoint) -> Self::Route {
143        RouteAlg::lift(&OpenApiRouteImpl, endpoint)
144    }
145}
146
147impl<Context> HttpSelectorAlg for OpenApiHandlerImpl<Context> {
148    type Selector = OpenApiSelector;
149
150    fn http_method(&self, method: HttpMethod) -> Self::Selector {
151        HttpSelectorAlg::http_method(&OpenApiRouteImpl, method)
152    }
153
154    fn http_path(&self, path: &RoutePath) -> Self::Selector {
155        HttpSelectorAlg::http_path(&OpenApiRouteImpl, path)
156    }
157
158    fn http_prefix(&self, prefix: &RoutePath) -> Self::Selector {
159        HttpSelectorAlg::http_prefix(&OpenApiRouteImpl, prefix)
160    }
161}