-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add macro to simplify nesting route specs (#138)
* Add `get_nested_endpoints_and_docs` macro * Add nested example
- Loading branch information
1 parent
fa77b69
commit 8421dbd
Showing
9 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/target | ||
**/*.rs.bk | ||
Cargo.lock | ||
/.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "nested" | ||
version = "0.1.0" | ||
authors = ["Maxime Borges <[email protected]>", "Ralph Bisschops <[email protected]>"] | ||
edition = "2021" | ||
|
||
[dependencies] | ||
rocket = { version = "=0.5.0", default-features = false, features = ["json"] } | ||
rocket_okapi = { path = "../../rocket-okapi", features = ["rapidoc"] } | ||
serde = "1.0" | ||
serde_json = "1.0" | ||
indexmap = "1.8.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use rocket::form::FromForm; | ||
use rocket::{get, post, serde::json::Json}; | ||
use rocket_okapi::okapi::openapi3::OpenApi; | ||
use rocket_okapi::okapi::schemars::{self, JsonSchema}; | ||
use rocket_okapi::openapi; | ||
use rocket_okapi::openapi_get_routes_spec; | ||
use rocket_okapi::settings::OpenApiSettings; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec<rocket::Route>, OpenApi) { | ||
openapi_get_routes_spec![settings: create_message, get_message] | ||
} | ||
|
||
#[derive(Serialize, Deserialize, JsonSchema, FromForm)] | ||
struct Message { | ||
/// The unique identifier for the message. | ||
message_id: u64, | ||
/// Content of the message. | ||
content: String, | ||
} | ||
|
||
/// # Create a message | ||
/// | ||
/// Returns the created message. | ||
#[openapi(tag = "Message")] | ||
#[post("/", data = "<message>")] | ||
fn create_message(message: crate::DataResult<'_, Message>) -> crate::Result<Message> { | ||
let message = message?.into_inner(); | ||
Ok(Json(message)) | ||
} | ||
|
||
/// # Get a message by id | ||
/// | ||
/// Returns the message with the requested id. | ||
#[openapi(tag = "Message")] | ||
#[get("/<id>")] | ||
fn get_message(id: u64) -> crate::Result<Message> { | ||
Ok(Json(Message { | ||
message_id: id, | ||
content: "Hey, how are you?".to_owned(), | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
mod message; | ||
mod post; | ||
|
||
use rocket_okapi::{ | ||
get_nested_endpoints_and_docs, okapi::openapi3::OpenApi, settings::OpenApiSettings, | ||
}; | ||
|
||
pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec<rocket::Route>, OpenApi) { | ||
get_nested_endpoints_and_docs! { | ||
"/posts" => post::get_routes_and_docs(settings), | ||
"/message" => message::get_routes_and_docs(settings), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
use rocket::form::FromForm; | ||
use rocket::{get, post, serde::json::Json}; | ||
use rocket_okapi::okapi::openapi3::OpenApi; | ||
use rocket_okapi::okapi::schemars::{self, JsonSchema}; | ||
use rocket_okapi::openapi; | ||
use rocket_okapi::openapi_get_routes_spec; | ||
use rocket_okapi::settings::OpenApiSettings; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec<rocket::Route>, OpenApi) { | ||
openapi_get_routes_spec![settings: create_post, get_post] | ||
} | ||
|
||
#[derive(Serialize, Deserialize, JsonSchema, FromForm)] | ||
struct Post { | ||
/// The unique identifier for the post. | ||
post_id: u64, | ||
/// The title of the post. | ||
title: String, | ||
/// A short summary of the post. | ||
summary: Option<String>, | ||
} | ||
|
||
/// # Create post | ||
/// | ||
/// Returns the created post. | ||
#[openapi(tag = "Posts")] | ||
#[post("/", data = "<post>")] | ||
fn create_post(post: crate::DataResult<'_, Post>) -> crate::Result<Post> { | ||
let post = post?.into_inner(); | ||
Ok(Json(post)) | ||
} | ||
|
||
/// # Get a post by id | ||
/// | ||
/// Returns the post with the requested id. | ||
#[openapi(tag = "Posts")] | ||
#[get("/<id>")] | ||
fn get_post(id: u64) -> crate::Result<Post> { | ||
Ok(Json(Post { | ||
post_id: id, | ||
title: "Your post".to_owned(), | ||
summary: Some("Best summary ever.".to_owned()), | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use rocket::{ | ||
http::{ContentType, Status}, | ||
request::Request, | ||
response::{self, Responder, Response}, | ||
}; | ||
use rocket_okapi::okapi::openapi3::Responses; | ||
use rocket_okapi::okapi::schemars::{self, Map}; | ||
use rocket_okapi::{gen::OpenApiGenerator, response::OpenApiResponderInner, OpenApiError}; | ||
|
||
/// Error messages returned to user | ||
#[derive(Debug, serde::Serialize, schemars::JsonSchema)] | ||
pub struct Error { | ||
/// The title of the error message | ||
pub err: String, | ||
/// The description of the error | ||
pub msg: Option<String>, | ||
// HTTP Status Code returned | ||
#[serde(skip)] | ||
pub http_status_code: u16, | ||
} | ||
|
||
impl OpenApiResponderInner for Error { | ||
fn responses(_generator: &mut OpenApiGenerator) -> Result<Responses, OpenApiError> { | ||
use rocket_okapi::okapi::openapi3::{RefOr, Response as OpenApiReponse}; | ||
|
||
let mut responses = Map::new(); | ||
responses.insert( | ||
"400".to_string(), | ||
RefOr::Object(OpenApiReponse { | ||
description: "\ | ||
# [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)\n\ | ||
The request given is wrongly formatted or data asked could not be fulfilled. \ | ||
" | ||
.to_string(), | ||
..Default::default() | ||
}), | ||
); | ||
responses.insert( | ||
"404".to_string(), | ||
RefOr::Object(OpenApiReponse { | ||
description: "\ | ||
# [404 Not Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404)\n\ | ||
This response is given when you request a page that does not exists.\ | ||
" | ||
.to_string(), | ||
..Default::default() | ||
}), | ||
); | ||
responses.insert( | ||
"422".to_string(), | ||
RefOr::Object(OpenApiReponse { | ||
description: "\ | ||
# [422 Unprocessable Entity](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422)\n\ | ||
This response is given when you request body is not correctly formatted. \ | ||
".to_string(), | ||
..Default::default() | ||
}), | ||
); | ||
responses.insert( | ||
"500".to_string(), | ||
RefOr::Object(OpenApiReponse { | ||
description: "\ | ||
# [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)\n\ | ||
This response is given when something wend wrong on the server. \ | ||
".to_string(), | ||
..Default::default() | ||
}), | ||
); | ||
Ok(Responses { | ||
responses, | ||
..Default::default() | ||
}) | ||
} | ||
} | ||
|
||
impl std::fmt::Display for Error { | ||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!( | ||
formatter, | ||
"Error `{}`: {}", | ||
self.err, | ||
self.msg.as_deref().unwrap_or("<no message>") | ||
) | ||
} | ||
} | ||
|
||
impl std::error::Error for Error {} | ||
|
||
impl<'r> Responder<'r, 'static> for Error { | ||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { | ||
// Convert object to json | ||
let body = serde_json::to_string(&self).unwrap(); | ||
Response::build() | ||
.sized_body(body.len(), std::io::Cursor::new(body)) | ||
.header(ContentType::JSON) | ||
.status(Status::new(self.http_status_code)) | ||
.ok() | ||
} | ||
} | ||
|
||
impl From<rocket::serde::json::Error<'_>> for Error { | ||
fn from(err: rocket::serde::json::Error) -> Self { | ||
use rocket::serde::json::Error::*; | ||
match err { | ||
Io(io_error) => Error { | ||
err: "IO Error".to_owned(), | ||
msg: Some(io_error.to_string()), | ||
http_status_code: 422, | ||
}, | ||
Parse(_raw_data, parse_error) => Error { | ||
err: "Parse Error".to_owned(), | ||
msg: Some(parse_error.to_string()), | ||
http_status_code: 422, | ||
}, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
use rocket::{Build, Rocket}; | ||
use rocket_okapi::okapi::openapi3::OpenApi; | ||
use rocket_okapi::settings::UrlObject; | ||
use rocket_okapi::{mount_endpoints_and_merged_docs, rapidoc::*}; | ||
|
||
mod api; | ||
mod error; | ||
|
||
pub type Result<T> = std::result::Result<rocket::serde::json::Json<T>, error::Error>; | ||
pub type DataResult<'a, T> = | ||
std::result::Result<rocket::serde::json::Json<T>, rocket::serde::json::Error<'a>>; | ||
|
||
#[rocket::main] | ||
async fn main() { | ||
let launch_result = create_server().launch().await; | ||
match launch_result { | ||
Ok(_) => println!("Rocket shut down gracefully."), | ||
Err(err) => println!("Rocket had an error: {}", err), | ||
}; | ||
} | ||
|
||
pub fn create_server() -> Rocket<Build> { | ||
let mut building_rocket = rocket::build().mount( | ||
"/rapidoc/", | ||
make_rapidoc(&RapiDocConfig { | ||
title: Some("My special documentation | RapiDoc".to_owned()), | ||
general: GeneralConfig { | ||
spec_urls: vec![UrlObject::new("General", "../v1/openapi.json")], | ||
..Default::default() | ||
}, | ||
hide_show: HideShowConfig { | ||
allow_spec_url_load: false, | ||
allow_spec_file_load: false, | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}), | ||
); | ||
|
||
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); | ||
let custom_route_spec = (vec![], custom_openapi_spec()); | ||
mount_endpoints_and_merged_docs! { | ||
building_rocket, "/v1".to_owned(), openapi_settings, | ||
"/external" => custom_route_spec, | ||
"/api" => api::get_routes_and_docs(&openapi_settings), | ||
}; | ||
|
||
building_rocket | ||
} | ||
|
||
fn custom_openapi_spec() -> OpenApi { | ||
use indexmap::indexmap; | ||
use rocket_okapi::okapi::openapi3::*; | ||
use rocket_okapi::okapi::schemars::schema::*; | ||
OpenApi { | ||
openapi: OpenApi::default_version(), | ||
info: Info { | ||
title: "The best API ever".to_owned(), | ||
description: Some("This is the best API ever, please use me!".to_owned()), | ||
terms_of_service: Some( | ||
"https://github.com/GREsau/okapi/blob/master/LICENSE".to_owned(), | ||
), | ||
contact: Some(Contact { | ||
name: Some("okapi example".to_owned()), | ||
url: Some("https://github.com/GREsau/okapi".to_owned()), | ||
email: None, | ||
..Default::default() | ||
}), | ||
license: Some(License { | ||
name: "MIT".to_owned(), | ||
url: Some("https://github.com/GREsau/okapi/blob/master/LICENSE".to_owned()), | ||
..Default::default() | ||
}), | ||
version: env!("CARGO_PKG_VERSION").to_owned(), | ||
..Default::default() | ||
}, | ||
servers: vec![ | ||
Server { | ||
url: "http://127.0.0.1:8000/".to_owned(), | ||
description: Some("Localhost".to_owned()), | ||
..Default::default() | ||
}, | ||
Server { | ||
url: "https://example.com/".to_owned(), | ||
description: Some("Possible Remote".to_owned()), | ||
..Default::default() | ||
}, | ||
], | ||
// Add paths that do not exist in Rocket (or add extra info to existing paths) | ||
paths: { | ||
indexmap! { | ||
"/home".to_owned() => PathItem{ | ||
get: Some( | ||
Operation { | ||
tags: vec!["HomePage".to_owned()], | ||
summary: Some("This is my homepage".to_owned()), | ||
responses: Responses{ | ||
responses: indexmap!{ | ||
"200".to_owned() => RefOr::Object( | ||
Response{ | ||
description: "Return the page, no error.".to_owned(), | ||
content: indexmap!{ | ||
"text/html".to_owned() => MediaType{ | ||
schema: Some(SchemaObject{ | ||
instance_type: Some(SingleOrVec::Single(Box::new( | ||
InstanceType::String | ||
))), | ||
..Default::default() | ||
}), | ||
..Default::default() | ||
} | ||
}, | ||
..Default::default() | ||
} | ||
) | ||
}, | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
} | ||
), | ||
..Default::default() | ||
} | ||
} | ||
}, | ||
..Default::default() | ||
} | ||
} |
Oops, something went wrong.