use salvo_core::writing::Text;
use salvo_core::{async_trait, Depot, FlowCtrl, Handler, Request, Response, Router};
use std::borrow::Cow;
const INDEX_TMPL: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
{{keywords}}
{{description}}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="redoc-container"></div>
<script src="{{lib_url}}"></script>
<script>
Redoc.init(
"{{spec_url}}",
{},
document.getElementById("redoc-container")
);
</script>
</body>
</html>
"#;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ReDoc {
pub title: Cow<'static, str>,
pub keywords: Option<Cow<'static, str>>,
pub description: Option<Cow<'static, str>>,
pub lib_url: Cow<'static, str>,
pub spec_url: Cow<'static, str>,
}
impl ReDoc {
pub fn new(spec_url: impl Into<Cow<'static, str>>) -> Self {
Self {
title: "ReDoc".into(),
keywords: None,
description: None,
lib_url: "https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js".into(),
spec_url: spec_url.into(),
}
}
pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
self.title = title.into();
self
}
pub fn keywords(mut self, keywords: impl Into<Cow<'static, str>>) -> Self {
self.keywords = Some(keywords.into());
self
}
pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
self.description = Some(description.into());
self
}
pub fn lib_url(mut self, lib_url: impl Into<Cow<'static, str>>) -> Self {
self.lib_url = lib_url.into();
self
}
pub fn into_router(self, path: impl Into<String>) -> Router {
Router::with_path(path.into()).goal(self)
}
}
#[async_trait]
impl Handler for ReDoc {
async fn handle(&self, _req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
let keywords = self
.keywords
.as_ref()
.map(|s| {
format!(
"<meta name=\"keywords\" content=\"{}\">",
s.split(',').map(|s| s.trim()).collect::<Vec<_>>().join(",")
)
})
.unwrap_or_default();
let description = self
.description
.as_ref()
.map(|s| format!("<meta name=\"description\" content=\"{}\">", s))
.unwrap_or_default();
let html = INDEX_TMPL
.replacen("{{spec_url}}", &self.spec_url, 1)
.replacen("{{lib_url}}", &self.lib_url, 1)
.replacen("{{description}}", &description, 1)
.replacen("{{keywords}}", &keywords, 1)
.replacen("{{title}}", &self.title, 1);
res.render(Text::Html(html));
}
}