gotham_restful/openapi/
router.rs1use super::{
2 builder::OpenapiBuilder,
3 handler::{OpenapiDocHandler, OpenapiSpecHandler},
4 operation::OperationDescription
5};
6use crate::{routing::*, EndpointWithSchema, ResourceWithSchema, ResponseSchema};
7use gotham::{
8 hyper::{Method, StatusCode},
9 pipeline::PipelineHandleChain,
10 prelude::*,
11 router::builder::{RouterBuilder, ScopeBuilder}
12};
13use lazy_regex::regex_replace_all;
14use openapi_type::OpenapiType;
15use std::{collections::HashMap, panic::RefUnwindSafe};
16
17pub trait GetOpenapi {
19 fn openapi_spec(&mut self, path: &str);
21
22 fn openapi_doc(&mut self, path: &str);
24}
25
26#[derive(Debug)]
27pub struct OpenapiRouter<'a, D> {
28 pub(crate) router: &'a mut D,
29 pub(crate) scope: Option<&'a str>,
30 pub(crate) openapi_builder: &'a mut OpenapiBuilder
31}
32
33macro_rules! implOpenapiRouter {
34 ($implType:ident) => {
35 impl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>
36 where
37 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
38 P: RefUnwindSafe + Send + Sync + 'static
39 {
40 pub fn scope<F>(&mut self, path: &str, callback: F)
41 where
42 F: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)
43 {
44 let mut openapi_builder = self.openapi_builder.clone();
45 let new_scope = self
46 .scope
47 .map(|scope| format!("{scope}/{path}").replace("//", "/"));
48 self.router.scope(path, |router| {
49 let mut router = OpenapiRouter {
50 router,
51 scope: Some(new_scope.as_ref().map(String::as_ref).unwrap_or(path)),
52 openapi_builder: &mut openapi_builder
53 };
54 callback(&mut router);
55 });
56 }
57 }
58
59 impl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>
60 where
61 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
62 P: RefUnwindSafe + Send + Sync + 'static
63 {
64 fn openapi_spec(&mut self, path: &str) {
65 self.router
66 .get(path)
67 .to_new_handler(OpenapiSpecHandler::new(
68 self.openapi_builder.openapi.clone()
69 ));
70 }
71
72 fn openapi_doc(&mut self, path: &str) {
73 self.router
74 .get(path)
75 .to_new_handler(OpenapiDocHandler::new(self.openapi_builder.openapi.clone()));
76 }
77 }
78
79 impl<'a, 'b, C, P> DrawResourcesWithSchema for OpenapiRouter<'a, $implType<'b, C, P>>
80 where
81 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
82 P: RefUnwindSafe + Send + Sync + 'static
83 {
84 fn resource<R: ResourceWithSchema>(&mut self, mut path: &str) {
85 if path.starts_with('/') {
86 path = &path[1..];
87 }
88 R::setup((self, path));
89 }
90 }
91
92 impl<'a, 'b, C, P> DrawResourceRoutesWithSchema
93 for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
94 where
95 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
96 P: RefUnwindSafe + Send + Sync + 'static
97 {
98 fn endpoint<E: EndpointWithSchema + 'static>(&mut self) {
99 let mut responses: HashMap<StatusCode, _> = HashMap::new();
100 for code in E::Output::status_codes() {
101 responses.insert(
102 code,
103 (self.0).openapi_builder.add_schema(E::Output::schema(code))
104 );
105 }
106 let mut path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
107 let mut descr = OperationDescription::new::<E>(responses, &path);
108 if E::has_placeholders() {
109 descr.set_path_params(E::Placeholders::schema());
110 }
111 if E::needs_params() {
112 descr.set_query_params(E::Params::schema());
113 }
114 if E::needs_body() {
115 let body_schema = (self.0).openapi_builder.add_schema(E::Body::schema());
116 descr.set_body::<E::Body>(body_schema);
117 }
118
119 let uri: &str = &E::uri();
120 let uri =
121 regex_replace_all!(r#"(^|/):([^/]+)(/|$)"#, uri, |_, prefix, name, suffix| {
122 format!("{prefix}{{{name}}}{suffix}")
123 });
124 if !uri.is_empty() {
125 path = format!("{path}/{uri}");
126 }
127
128 let op = descr.into_operation();
129 let mut item = (self.0).openapi_builder.remove_path(&path);
130 match E::http_method() {
131 Method::GET => item.get = Some(op),
132 Method::PUT => item.put = Some(op),
133 Method::POST => item.post = Some(op),
134 Method::DELETE => item.delete = Some(op),
135 Method::OPTIONS => item.options = Some(op),
136 Method::HEAD => item.head = Some(op),
137 Method::PATCH => item.patch = Some(op),
138 Method::TRACE => item.trace = Some(op),
139 method => {
140 warn!("Ignoring unsupported method '{method}' in OpenAPI Specification")
141 }
142 };
143 (self.0).openapi_builder.add_path(path, item);
144
145 (&mut *(self.0).router, self.1).endpoint::<E>()
146 }
147 }
148 };
149}
150
151implOpenapiRouter!(RouterBuilder);
152implOpenapiRouter!(ScopeBuilder);