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