1use std::borrow::Cow;
8
9use salvo_core::writing::Text;
10use salvo_core::{async_trait, Depot, FlowCtrl, Handler, Request, Response, Router};
11
12use crate::html::{description_meta, escape_html, keywords_meta};
13
14const INDEX_TMPL: &str = r#"
15<!doctype html>
16<html>
17 <head>
18 <title>{{title}}</title>
19 {{keywords}}
20 {{description}}
21 <meta charset="utf-8">
22 <script type="module" src="{{lib_url}}"></script>
23 </head>
24 <body>
25 <rapi-doc spec-url="{{spec_url}}"></rapi-doc>
26 </body>
27</html>
28"#;
29
30#[non_exhaustive]
32#[derive(Clone, Debug)]
33pub struct RapiDoc {
34 pub title: Cow<'static, str>,
36 pub keywords: Option<Cow<'static, str>>,
38 pub description: Option<Cow<'static, str>>,
40 pub lib_url: Cow<'static, str>,
42 pub spec_url: Cow<'static, str>,
44}
45impl RapiDoc {
46 #[must_use]
58 pub fn new(spec_url: impl Into<Cow<'static, str>>) -> Self {
59 Self {
60 title: "RapiDoc".into(),
61 keywords: None,
62 description: None,
63 lib_url: "https://unpkg.com/rapidoc/dist/rapidoc-min.js".into(),
64 spec_url: spec_url.into(),
65 }
66 }
67
68 #[must_use]
70 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
71 self.title = title.into();
72 self
73 }
74
75 #[must_use]
77 pub fn keywords(mut self, keywords: impl Into<Cow<'static, str>>) -> Self {
78 self.keywords = Some(keywords.into());
79 self
80 }
81
82 #[must_use]
84 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
85 self.description = Some(description.into());
86 self
87 }
88
89 #[must_use]
91 pub fn lib_url(mut self, lib_url: impl Into<Cow<'static, str>>) -> Self {
92 self.lib_url = lib_url.into();
93 self
94 }
95
96 pub fn into_router(self, path: impl Into<String>) -> Router {
98 Router::with_path(path.into()).goal(self)
99 }
100}
101
102#[async_trait]
103impl Handler for RapiDoc {
104 async fn handle(&self, _req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
105 let keywords = self
106 .keywords
107 .as_ref()
108 .map(|s| keywords_meta(s))
109 .unwrap_or_default();
110 let description = self
111 .description
112 .as_ref()
113 .map(|s| description_meta(s))
114 .unwrap_or_default();
115 let html = INDEX_TMPL
116 .replacen("{{spec_url}}", &escape_html(&self.spec_url), 1)
117 .replacen("{{lib_url}}", &escape_html(&self.lib_url), 1)
118 .replacen("{{description}}", &description, 1)
119 .replacen("{{keywords}}", &keywords, 1)
120 .replacen("{{title}}", &escape_html(&self.title), 1);
121 res.render(Text::Html(html));
122 }
123}