Skip to content

Commit

Permalink
feat: Add ReDoc and RapiDoc (#355)
Browse files Browse the repository at this point in the history
* Version 0.50.0

* feat: Add ReDoc and RapiDoc

* wip
  • Loading branch information
chrislearn authored Aug 8, 2023
1 parent 73090e8 commit 5ed9b80
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 124 deletions.
34 changes: 17 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.49.1"
version = "0.50.0"
authors = ["Chrislearn Young <[email protected]>"]
edition = "2021"
rust-version = "1.67"
Expand Down Expand Up @@ -80,23 +80,23 @@ rustls = "0.21.1"
rustls-pemfile = "1.0"
rust-embed = "6"
salvo-utils = { version = "0.0.5", default-features = true }
salvo_macros = { version = "0.49.1", path = "crates/macros", default-features = false }
salvo_core = { version = "0.49.1", path = "crates/core", default-features = false }
salvo_extra = { version = "0.49.1", path = "crates/extra", default-features = false }
salvo-compression = { version = "0.49.1", path = "crates/compression", default-features = false }
salvo-cache = { version = "0.49.1", path = "crates/cache", default-features = false }
salvo-cors = { version = "0.49.1", path = "crates/cors", default-features = false }
salvo-csrf = { version = "0.49.1", path = "crates/csrf", default-features = false }
salvo-flash = { version = "0.49.1", path = "crates/flash", default-features = false }
salvo_macros = { version = "0.50.0", path = "crates/macros", default-features = false }
salvo_core = { version = "0.50.0", path = "crates/core", default-features = false }
salvo_extra = { version = "0.50.0", path = "crates/extra", default-features = false }
salvo-compression = { version = "0.50.0", path = "crates/compression", default-features = false }
salvo-cache = { version = "0.50.0", path = "crates/cache", default-features = false }
salvo-cors = { version = "0.50.0", path = "crates/cors", default-features = false }
salvo-csrf = { version = "0.50.0", path = "crates/csrf", default-features = false }
salvo-flash = { version = "0.50.0", path = "crates/flash", default-features = false }
salvo-http3 = { version = "0.0.4", default-features = false }
salvo-jwt-auth = { version = "0.49.1", path = "crates/jwt-auth", default-features = false }
salvo-oapi = { version = "0.49.1", path = "./crates/oapi", default-features = false }
salvo-oapi-macros = { version = "0.49.1", path = "crates/oapi-macros", default-features = false }
salvo-otel = { version = "0.49.1", path = "crates/otel", default-features = false }
salvo-proxy = { version = "0.49.1", path = "crates/proxy", default-features = false }
salvo-rate-limiter = { version = "0.49.1", path = "crates/rate-limiter", default-features = false }
salvo-serve-static = { version = "0.49.1", path = "crates/serve-static", default-features = false }
salvo-session = { version = "0.49.1", path = "crates/session", default-features = false }
salvo-jwt-auth = { version = "0.50.0", path = "crates/jwt-auth", default-features = false }
salvo-oapi = { version = "0.50.0", path = "./crates/oapi", default-features = false }
salvo-oapi-macros = { version = "0.50.0", path = "crates/oapi-macros", default-features = false }
salvo-otel = { version = "0.50.0", path = "crates/otel", default-features = false }
salvo-proxy = { version = "0.50.0", path = "crates/proxy", default-features = false }
salvo-rate-limiter = { version = "0.50.0", path = "crates/rate-limiter", default-features = false }
salvo-serve-static = { version = "0.50.0", path = "crates/serve-static", default-features = false }
salvo-session = { version = "0.50.0", path = "crates/session", default-features = false }
serde = "1"
serde_json = "1"
serde-xml-rs = "0.6"
Expand Down
2 changes: 0 additions & 2 deletions crates/core/src/conn/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,9 @@ where
if !prefix.is_empty() {
self.pre = Some(prefix);
}
println!("======poll ready {} remaing: {}", copy_len, buf.remaining());
return Poll::Ready(Ok(()));
}
}
println!("======inner poll poll_read");
Pin::new(&mut self.inner).poll_read(cx, buf)
}
}
Expand Down
4 changes: 3 additions & 1 deletion crates/oapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ authors = ["Juha Kukkonen <[email protected]>", "Chrislearn Young <chrisle

[features]
default = []
full = ["swagger-ui", "chrono", "decimal", "yaml", "uuid", "time", "smallvec", "indexmap"]
full = ["swagger-ui", "rapidoc", "redoc", "chrono", "decimal", "yaml", "uuid", "time", "smallvec", "indexmap"]
swagger-ui = ["dep:rust-embed"]
rapidoc = []
redoc = []
chrono = ["salvo-oapi-macros/chrono"]
decimal = ["salvo-oapi-macros/decimal"]
yaml = ["dep:serde_yaml"]
Expand Down
8 changes: 8 additions & 0 deletions crates/oapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ cfg_feature! {
#![feature ="swagger-ui"]
pub mod swagger_ui;
}
cfg_feature! {
#![feature ="rapidoc"]
pub mod rapidoc;
}
cfg_feature! {
#![feature ="redoc"]
pub mod redoc;
}

#[doc = include_str!("../docs/endpoint.md")]
pub use salvo_oapi_macros::endpoint;
Expand Down
65 changes: 65 additions & 0 deletions crates/oapi/src/rapidoc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! This crate implements necessary boiler plate code to serve RapiDoc via web server. It
//! works as a bridge for serving the OpenAPI documentation created with [`salvo`][salvo] library in the
//! RapiDoc.
//!
//! [salvo]: <https://docs.rs/salvo/>
//!
use salvo_core::writing::Text;
use salvo_core::{async_trait, Depot, FlowCtrl, Handler, Request, Response, Router};

const INDEX_TMPL: &str = r#"
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc spec-url="{{spec_url}}"></rapi-doc>
</body>
</html>
"#;

/// Implements [`Handler`] for serving RapiDoc.
#[derive(Clone, Debug)]
pub struct RapiDoc {
spec_url: String,
html: String,
}
impl RapiDoc {
/// Create a new [`RapiDoc`] for given path.
///
/// Path argument will expose the RapiDoc to the user and should be something that
/// the underlying application framework / library supports.
///
/// # Examples
///
/// ```rust
/// # use salvo_oapi::rapidoc::RapiDoc;
/// let doc = RapiDoc::new("/rapidoc/openapi.json");
/// ```
pub fn new(spec_url: impl Into<String>) -> Self {
let spec_url = spec_url.into();
Self {
html: INDEX_TMPL.replace("{{spec_url}}", &spec_url),
spec_url,
}
}

/// Returns the spec url.
pub fn sepec_url(&self) -> &str {
&self.spec_url
}

/// Consusmes the [`RapiDoc`] and returns [`Router`] with the [`RapiDoc`] as handler.
pub fn into_router(self, path: impl Into<String>) -> Router {
Router::with_path(path.into()).handle(self)
}
}

#[async_trait]
impl Handler for RapiDoc {
async fn handle(&self, _req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
res.render(Text::Html(&self.html));
}
}
86 changes: 86 additions & 0 deletions crates/oapi/src/redoc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! This crate implements necessary boiler plate code to serve ReDoc via web server. It
//! works as a bridge for serving the OpenAPI documentation created with [`salvo`][salvo] library in the
//! ReDoc.
//!
//! [salvo]: <https://docs.rs/salvo/>
//!
use salvo_core::writing::Text;
use salvo_core::{async_trait, Depot, FlowCtrl, Handler, Request, Response, Router};

const INDEX_TMPL: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>Redoc</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="redoc-container"></div>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
<script>
Redoc.init(
"{{spec_url}}",
{},
document.getElementById("redoc-container")
);
</script>
</body>
</html>
"#;

/// Implements [`Handler`] for serving ReDoc.
#[derive(Clone, Debug)]
pub struct ReDoc {
spec_url: String,
html: String,
}
impl ReDoc {
/// Create a new [`ReDoc`] for given path.
///
/// Path argument will expose the ReDoc to the user and should be something that
/// the underlying application framework / library supports.
///
/// # Examples
///
/// ```rust
/// # use salvo_oapi::rapidoc::ReDoc;
/// let doc = ReDoc::new("/rapidoc/openapi.json");
/// ```
pub fn new(spec_url: impl Into<String>) -> Self {
let spec_url = spec_url.into();
Self {
html: INDEX_TMPL.replace("{{spec_url}}", &spec_url),
spec_url,
}
}

/// Returns the spec url.
pub fn sepec_url(&self) -> &str {
&self.spec_url
}

/// Consusmes the [`ReDoc`] and returns [`Router`] with the [`ReDoc`] as handler.
pub fn into_router(self, path: impl Into<String>) -> Router {
Router::with_path(path.into()).handle(self)
}
}

#[async_trait]
impl Handler for ReDoc {
async fn handle(&self, _req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) {
res.render(Text::Html(&self.html));
}
}
93 changes: 9 additions & 84 deletions crates/oapi/src/swagger_ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use std::borrow::Cow;

mod config;
pub mod oauth;
use crate::OpenApi;
pub use config::Config;
use rust_embed::RustEmbed;
use salvo_core::http::uri::{Parts as UriParts, Uri};
Expand Down Expand Up @@ -77,9 +76,7 @@ const INDEX_TMPL: &str = r#"
/// Implements [`Handler`] for serving Swagger UI.
#[derive(Clone, Debug)]
pub struct SwaggerUi {
urls: Vec<(Url<'static>, OpenApi)>,
config: Config<'static>,
external_urls: Vec<(Url<'static>, serde_json::Value)>,
}
impl SwaggerUi {
/// Create a new [`SwaggerUi`] for given path.
Expand All @@ -94,18 +91,11 @@ impl SwaggerUi {
/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}");
/// ```
pub fn new(config: impl Into<Config<'static>>) -> Self {
Self {
urls: Vec::new(),
config: config.into(),
external_urls: Vec::new(),
}
Self { config: config.into() }
}

/// Add api doc [`Url`] into [`SwaggerUi`].
///
/// Method takes two arguments where first one is path which exposes the [`OpenApi`] to the user.
/// Second argument is the actual Rust implementation of the OpenAPI doc which is being exposed.
///
/// Calling this again will add another url to the Swagger UI.
///
/// # Examples
Expand All @@ -115,11 +105,10 @@ impl SwaggerUi {
/// # use salvo_oapi::OpenApi;
///
/// let swagger = SwaggerUi::new("/api-doc/openapi.json")
/// .url("/api-docs/openapi2.json", OpenApi::new("example api", "0.0.1"));
/// .url("/api-docs/openapi2.json");
/// ```
pub fn url<U: Into<Url<'static>>>(mut self, url: U, openapi: OpenApi) -> Self {
self.urls.push((url.into(), openapi));

pub fn url<U: Into<Url<'static>>>(mut self, url: U) -> Self {
self.config.urls.push(url.into());
self
}

Expand All @@ -140,77 +129,13 @@ impl SwaggerUi {
/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
/// .urls(
/// vec![
/// (Url::with_primary("api doc 1", "/api-docs/openapi.json", true), OpenApi::new("example api", "0.0.1")),
/// (Url::new("api doc 2", "/api-docs/openapi2.json"), OpenApi::new("example api2", "0.0.1"))
/// (Url::with_primary("api doc 1", "/api-docs/openapi.json", true)),
/// (Url::new("api doc 2", "/api-docs/openapi2.json"))
/// ]
/// );
/// ```
pub fn urls(mut self, urls: Vec<(Url<'static>, OpenApi)>) -> Self {
self.urls = urls;

self
}

/// Add external API doc to the [`SwaggerUi`].
///
/// This operation is unchecked and so it does not check any validity of provided content.
/// Users are required to do their own check if any regarding validity of the external
/// OpenAPI document.
///
/// Method accepts two arguments, one is [`Url`] the API doc is served at and the second one is
/// the [`serde_json::Value`] of the OpenAPI doc to be served.
///
/// # Examples
///
/// Add external API doc to the [`SwaggerUi`].
///```rust
/// # use salvo_oapi::swagger_ui::{SwaggerUi, Url};
/// # use salvo_oapi::OpenApi;
/// # use serde_json::json;
/// let external_openapi = json!({"openapi": "3.0.0"});
///
/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
/// .external_url_unchecked("/api-docs/openapi.json", external_openapi);
///```
pub fn external_url_unchecked<U: Into<Url<'static>>>(mut self, url: U, openapi: serde_json::Value) -> Self {
self.external_urls.push((url.into(), openapi));

self
}

/// Add external API docs to the [`SwaggerUi`] from iterator.
///
/// This operation is unchecked and so it does not check any validity of provided content.
/// Users are required to do their own check if any regarding validity of the external
/// OpenAPI documents.
///
/// Method accepts one argument, an `iter` of [`Url`] and [`serde_json::Value`] tuples. The
/// [`Url`] will point to location the OpenAPI document is served and the [`serde_json::Value`]
/// is the OpenAPI document to be served.
///
/// # Examples
///
/// Add external API docs to the [`SwaggerUi`].
///```rust
/// # use salvo_oapi::swagger_ui::{SwaggerUi, Url};
/// # use salvo_oapi::OpenApi;
/// # use serde_json::json;
/// let external_openapi = json!({"openapi": "3.0.0"});
/// let external_openapi2 = json!({"openapi": "3.0.0"});
///
/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
/// .external_urls_from_iter_unchecked([
/// ("/api-docs/openapi.json", external_openapi),
/// ("/api-docs/openapi2.json", external_openapi2)
/// ]);
///```
pub fn external_urls_from_iter_unchecked<I: IntoIterator<Item = (U, serde_json::Value)>, U: Into<Url<'static>>>(
mut self,
external_urls: I,
) -> Self {
self.external_urls
.extend(external_urls.into_iter().map(|(url, doc)| (url.into(), doc)));

pub fn urls(mut self, urls: Vec<Url<'static>>) -> Self {
self.config.urls = urls;
self
}

Expand Down Expand Up @@ -424,7 +349,7 @@ pub fn serve<'a>(path: &str, config: &Config<'a>) -> Result<Option<SwaggerFile<'
}
Some(Cow::Owned(index.as_bytes().to_vec()))
} else {
SwaggerUiDist::get(path).map(|f|f.data)
SwaggerUiDist::get(path).map(|f| f.data)
};
let file = bytes.map(|bytes| SwaggerFile {
bytes,
Expand Down
Loading

0 comments on commit 5ed9b80

Please sign in to comment.