1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use http::Method;
5use once_cell::sync::Lazy;
6
7use silent::prelude::{HandlerGetter, Route};
8use silent::{
9 Handler, HandlerWrapper, Request as SilentRequest, Response as SilentResponse,
10 Result as SilentResult,
11};
12use utoipa::openapi::{Components, ComponentsBuilder, OpenApi};
13
14#[derive(Clone, Debug)]
16pub struct DocMeta {
17 pub summary: Option<String>,
18 pub description: Option<String>,
19}
20
21static DOC_REGISTRY: Lazy<Mutex<HashMap<usize, DocMeta>>> =
22 Lazy::new(|| Mutex::new(HashMap::new()));
23
24pub fn register_doc_by_ptr(ptr: usize, summary: Option<&str>, description: Option<&str>) {
25 let mut map = DOC_REGISTRY.lock().expect("doc registry poisoned");
26 map.insert(
27 ptr,
28 DocMeta {
29 summary: summary.map(|s| s.to_string()),
30 description: description.map(|s| s.to_string()),
31 },
32 );
33}
34
35pub(crate) fn lookup_doc_by_handler_ptr(ptr: usize) -> Option<DocMeta> {
36 DOC_REGISTRY.lock().ok().and_then(|m| m.get(&ptr).cloned())
37}
38
39#[derive(Clone, Debug)]
41pub enum ResponseMeta {
42 TextPlain,
43 Json { type_name: &'static str },
44}
45
46static RESPONSE_REGISTRY: Lazy<Mutex<HashMap<usize, ResponseMeta>>> =
47 Lazy::new(|| Mutex::new(HashMap::new()));
48
49pub fn register_response_by_ptr(ptr: usize, meta: ResponseMeta) {
50 let mut map = RESPONSE_REGISTRY
51 .lock()
52 .expect("response registry poisoned");
53 map.insert(ptr, meta);
54}
55
56pub(crate) fn lookup_response_by_handler_ptr(ptr: usize) -> Option<ResponseMeta> {
57 RESPONSE_REGISTRY
58 .lock()
59 .ok()
60 .and_then(|m| m.get(&ptr).cloned())
61}
62
63pub fn list_registered_json_types() -> Vec<&'static str> {
64 let map = RESPONSE_REGISTRY.lock().ok();
65 let mut out = Vec::new();
66 if let Some(map) = map {
67 for meta in map.values() {
68 if let ResponseMeta::Json { type_name } = meta
69 && !out.contains(type_name)
70 {
71 out.push(*type_name);
72 }
73 }
74 }
75 out
76}
77
78type SchemaRegFn = fn(&mut Components);
80static SCHEMA_REGISTRY: Lazy<Mutex<Vec<SchemaRegFn>>> = Lazy::new(|| Mutex::new(Vec::new()));
81
82pub fn register_schema_for<T>()
83where
84 T: crate::ToSchema + ::utoipa::PartialSchema + 'static,
85{
86 fn add_impl<U: crate::ToSchema + ::utoipa::PartialSchema>(components: &mut Components) {
87 let mut refs: Vec<(
88 String,
89 ::utoipa::openapi::RefOr<::utoipa::openapi::schema::Schema>,
90 )> = Vec::new();
91 <U as crate::ToSchema>::schemas(&mut refs);
92 for (name, schema) in refs {
93 components.schemas.entry(name).or_insert(schema);
94 }
95 let name = <U as crate::ToSchema>::name().into_owned();
96 let schema = <U as ::utoipa::PartialSchema>::schema();
97 components.schemas.entry(name).or_insert(schema);
98 }
99 let mut reg = SCHEMA_REGISTRY.lock().expect("schema registry poisoned");
100 reg.push(add_impl::<T> as SchemaRegFn);
101}
102
103pub fn apply_registered_schemas(openapi: &mut OpenApi) {
104 let mut components = openapi
105 .components
106 .clone()
107 .unwrap_or_else(|| ComponentsBuilder::new().build());
108 if let Ok(reg) = SCHEMA_REGISTRY.lock() {
109 for f in reg.iter() {
110 f(&mut components);
111 }
112 }
113 openapi.components = Some(components);
114}
115
116pub trait RouteDocMarkExt {
118 fn doc(self, method: Method, summary: &str, description: &str) -> Self;
119}
120
121pub fn handler_with_doc<F, Fut, T>(f: F, summary: &str, description: &str) -> Arc<dyn Handler>
123where
124 F: Fn(SilentRequest) -> Fut + Send + Sync + 'static,
125 Fut: core::future::Future<Output = SilentResult<T>> + Send + 'static,
126 T: Into<SilentResponse> + Send + 'static,
127{
128 let handler = Arc::new(HandlerWrapper::new(f));
129 let ptr = Arc::as_ptr(&handler) as *const () as usize;
130 register_doc_by_ptr(ptr, Some(summary), Some(description));
131 handler
132}
133
134impl RouteDocMarkExt for Route {
135 fn doc(self, method: Method, summary: &str, description: &str) -> Self {
136 if let Some(handler) = self.handler.get(&method).cloned() {
137 let ptr = Arc::as_ptr(&handler) as *const () as usize;
138 register_doc_by_ptr(ptr, Some(summary), Some(description));
139 }
140 self
141 }
142}
143
144pub trait RouteDocAppendExt {
146 fn get_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
147 fn post_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
148 fn put_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
149 fn delete_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
150 fn patch_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
151 fn options_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self;
152}
153
154impl RouteDocAppendExt for Route {
155 fn get_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
156 let ptr = Arc::as_ptr(&handler) as *const () as usize;
157 register_doc_by_ptr(ptr, Some(summary), Some(description));
158 <Route as HandlerGetter>::handler(self, Method::GET, handler)
159 }
160
161 fn post_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
162 let ptr = Arc::as_ptr(&handler) as *const () as usize;
163 register_doc_by_ptr(ptr, Some(summary), Some(description));
164 <Route as HandlerGetter>::handler(self, Method::POST, handler)
165 }
166
167 fn put_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
168 let ptr = Arc::as_ptr(&handler) as *const () as usize;
169 register_doc_by_ptr(ptr, Some(summary), Some(description));
170 <Route as HandlerGetter>::handler(self, Method::PUT, handler)
171 }
172
173 fn delete_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
174 let ptr = Arc::as_ptr(&handler) as *const () as usize;
175 register_doc_by_ptr(ptr, Some(summary), Some(description));
176 <Route as HandlerGetter>::handler(self, Method::DELETE, handler)
177 }
178
179 fn patch_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
180 let ptr = Arc::as_ptr(&handler) as *const () as usize;
181 register_doc_by_ptr(ptr, Some(summary), Some(description));
182 <Route as HandlerGetter>::handler(self, Method::PATCH, handler)
183 }
184
185 fn options_with_doc(self, handler: Arc<dyn Handler>, summary: &str, description: &str) -> Self {
186 let ptr = Arc::as_ptr(&handler) as *const () as usize;
187 register_doc_by_ptr(ptr, Some(summary), Some(description));
188 <Route as HandlerGetter>::handler(self, Method::OPTIONS, handler)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use serde::Serialize;
196 use utoipa::ToSchema;
197
198 async fn ok_handler(_req: SilentRequest) -> SilentResult<SilentResponse> {
199 Ok(SilentResponse::text("ok"))
200 }
201
202 #[test]
203 fn test_register_and_lookup_doc() {
204 let handler = Arc::new(HandlerWrapper::new(|_req: SilentRequest| async move {
205 Ok::<_, silent::SilentError>(SilentResponse::text("doc"))
206 }));
207 let ptr = Arc::as_ptr(&handler) as *const () as usize;
208 register_doc_by_ptr(ptr, Some("summary"), Some("desc"));
209 let got = lookup_doc_by_handler_ptr(ptr).expect("doc meta");
210 assert_eq!(got.summary.as_deref(), Some("summary"));
211 assert_eq!(got.description.as_deref(), Some("desc"));
212 }
213
214 #[test]
215 fn test_register_and_lookup_response() {
216 let handler = Arc::new(HandlerWrapper::new(ok_handler));
217 let ptr = Arc::as_ptr(&handler) as *const () as usize;
218 register_response_by_ptr(ptr, ResponseMeta::TextPlain);
219 let got = lookup_response_by_handler_ptr(ptr).expect("resp meta");
220 matches!(got, ResponseMeta::TextPlain);
221 }
222
223 #[test]
224 fn test_list_registered_json_types() {
225 let h1 = Arc::new(HandlerWrapper::new(ok_handler));
226 let h2 = Arc::new(HandlerWrapper::new(ok_handler));
227 let p1 = Arc::as_ptr(&h1) as *const () as usize;
228 let p2 = Arc::as_ptr(&h2) as *const () as usize;
229 register_response_by_ptr(p1, ResponseMeta::Json { type_name: "User" });
230 register_response_by_ptr(p2, ResponseMeta::Json { type_name: "User" });
231 let list = list_registered_json_types();
232 assert!(list.contains(&"User"));
233 assert_eq!(list.len(), 1);
234 }
235
236 #[derive(Serialize, ToSchema)]
237 struct FooSchema {
238 id: i32,
239 name: String,
240 }
241
242 #[test]
243 fn test_register_schema_and_apply() {
244 register_schema_for::<FooSchema>();
245 let mut openapi = crate::OpenApiDoc::new("T", "1").into_openapi();
246 apply_registered_schemas(&mut openapi);
247 let components = openapi.components.expect("components");
248 assert!(components.schemas.contains_key("FooSchema"));
249 }
250}