-
-
Notifications
You must be signed in to change notification settings - Fork 520
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support to Rocket-Okapi (#1071)
* feat: support to okapi * fix: fmt checks * chore: rocket-okapi-example: add required schemas * chore: rocket-okapi-example: add dto * chore: rocket-okapi-example: add custom error * chore: rocket-okapi-example: add api controllers * chore: rocket-okapi-example: add notes in Readme * chore: make rocket_okapi optional * refactor: delete rocket example from rocket_example * chore: rocket-okapi-example: add base files for okapi example * chore: rocket-okapi-example: add controllers and dto * chore: rocket-okapi-example: add docs
- Loading branch information
Showing
30 changed files
with
1,014 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "sea-orm-rocket-okapi-example" | ||
version = "0.1.0" | ||
authors = ["Sam Samai <sam@studio2pi.com.au>", "Erick Pacheco <eum602@gmail.com"] | ||
edition = "2021" | ||
publish = false | ||
|
||
[workspace] | ||
members = [".", "api", "core", "entity", "migration", "dto"] | ||
|
||
[dependencies] | ||
rocket-example-api = { path = "api" } |
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 @@ | ||
# Rocket and Rocket-API with SeaORM example app | ||
|
||
1. Modify the `url` var in `api/Rocket.toml` to point to your chosen database | ||
|
||
1. Turn on the appropriate database feature for your chosen db in `core/Cargo.toml` (the `"sqlx-postgres",` line) | ||
|
||
1. Execute `cargo run` to start the server | ||
|
||
1. You can go to ```http://localhost:8000/swagger-ui/index.html``` to see the api documentation about this demo project. | ||
![swagger](swagger.png) | ||
1. Additionally, you can navigate to ```http://localhost:8000/rapidoc/index.html``` to see the rapidoc format for api documentation | ||
![rapidoc](rapidoc.png) |
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,11 @@ | ||
[default] | ||
template_dir = "api/templates/" | ||
|
||
[default.databases.sea_orm] | ||
# Mysql | ||
# make sure to enable "sqlx-mysql" feature in Cargo.toml, i.e default = ["sqlx-mysql"] | ||
# url = "mysql://root:@localhost/rocket_example" | ||
|
||
# Postgres | ||
# make sure to enable "sqlx-postgres" feature in Cargo.toml, i.e default = ["sqlx-postgres"] | ||
url = "postgres://user:pass@localhost:5432/rocket" |
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,39 @@ | ||
[package] | ||
name = "rocket-example-api" | ||
version = "0.1.0" | ||
authors = ["Sam Samai <sam@studio2pi.com.au>"] | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
async-stream = { version = "^0.3" } | ||
async-trait = { version = "0.1" } | ||
rocket-example-core = { path = "../core" } | ||
futures = { version = "^0.3" } | ||
futures-util = { version = "^0.3" } | ||
rocket = { version = "0.5.0-rc.1", features = [ | ||
"json", | ||
] } | ||
rocket_dyn_templates = { version = "0.1.0-rc.1", features = [ | ||
"tera", | ||
] } | ||
serde_json = { version = "^1" } | ||
entity = { path = "../entity" } | ||
migration = { path = "../migration" } | ||
tokio = "1.20.0" | ||
serde = "1.0" | ||
dto = { path = "../dto" } | ||
|
||
[dependencies.sea-orm-rocket] | ||
path = "../../../sea-orm-rocket/lib" # remove this line in your own project and use the git line | ||
features = ["rocket_okapi"] #enables rocket_okapi so to have open api features enabled | ||
# git = "https://github.com/SeaQL/sea-orm" | ||
|
||
[dependencies.rocket_okapi] | ||
version = "0.8.0-rc.2" | ||
features = ["swagger", "rapidoc","rocket_db_pools"] | ||
|
||
[dependencies.rocket_cors] | ||
git = "https://github.com/lawliet89/rocket_cors.git" | ||
rev = "54fae070" | ||
default-features = false |
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,139 @@ | ||
#[macro_use] | ||
extern crate rocket; | ||
|
||
use rocket::fairing::{self, AdHoc}; | ||
use rocket::{Build, Rocket}; | ||
|
||
use migration::MigratorTrait; | ||
use sea_orm_rocket::Database; | ||
|
||
use rocket_okapi::mount_endpoints_and_merged_docs; | ||
use rocket_okapi::okapi::openapi3::OpenApi; | ||
use rocket_okapi::rapidoc::{make_rapidoc, GeneralConfig, HideShowConfig, RapiDocConfig}; | ||
use rocket_okapi::settings::UrlObject; | ||
use rocket_okapi::swagger_ui::{make_swagger_ui, SwaggerUIConfig}; | ||
|
||
use rocket::http::Method; | ||
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; | ||
|
||
mod pool; | ||
use pool::Db; | ||
mod error; | ||
mod okapi_example; | ||
|
||
pub use entity::post; | ||
pub use entity::post::Entity as Post; | ||
|
||
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result { | ||
let conn = &Db::fetch(&rocket).unwrap().conn; | ||
let _ = migration::Migrator::up(conn, None).await; | ||
Ok(rocket) | ||
} | ||
|
||
#[tokio::main] | ||
async fn start() -> Result<(), rocket::Error> { | ||
let mut building_rocket = rocket::build() | ||
.attach(Db::init()) | ||
.attach(AdHoc::try_on_ignite("Migrations", run_migrations)) | ||
.mount( | ||
"/swagger-ui/", | ||
make_swagger_ui(&SwaggerUIConfig { | ||
url: "../v1/openapi.json".to_owned(), | ||
..Default::default() | ||
}), | ||
) | ||
.mount( | ||
"/rapidoc/", | ||
make_rapidoc(&RapiDocConfig { | ||
title: Some("Rocket/SeaOrm - RapiDoc 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() | ||
}), | ||
) | ||
.attach(cors()); | ||
|
||
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, | ||
"/additional" => custom_route_spec, | ||
"/okapi-example" => okapi_example::get_routes_and_docs(&openapi_settings), | ||
}; | ||
|
||
building_rocket.launch().await.map(|_| ()) | ||
} | ||
|
||
fn cors() -> Cors { | ||
let allowed_origins = | ||
AllowedOrigins::some_exact(&["http://localhost:8000", "http://127.0.0.1:8000"]); | ||
|
||
let cors = rocket_cors::CorsOptions { | ||
allowed_origins, | ||
allowed_methods: vec![Method::Get, Method::Post, Method::Delete] | ||
.into_iter() | ||
.map(From::from) | ||
.collect(), | ||
allowed_headers: AllowedHeaders::all(), | ||
allow_credentials: true, | ||
..Default::default() | ||
} | ||
.to_cors() | ||
.unwrap(); | ||
cors | ||
} | ||
|
||
fn custom_openapi_spec() -> OpenApi { | ||
use rocket_okapi::okapi::openapi3::*; | ||
OpenApi { | ||
openapi: OpenApi::default_version(), | ||
info: Info { | ||
title: "SeaOrm-Rocket-Okapi Example".to_owned(), | ||
description: Some("API Docs for Rocket/SeaOrm example".to_owned()), | ||
terms_of_service: Some("https://github.com/SeaQL/sea-orm#license".to_owned()), | ||
contact: Some(Contact { | ||
name: Some("SeaOrm".to_owned()), | ||
url: Some("https://github.com/SeaQL/sea-orm".to_owned()), | ||
email: None, | ||
..Default::default() | ||
}), | ||
license: Some(License { | ||
name: "MIT".to_owned(), | ||
url: Some("https://github.com/SeaQL/sea-orm/blob/master/LICENSE-MIT".to_owned()), | ||
..Default::default() | ||
}), | ||
version: env!("CARGO_PKG_VERSION").to_owned(), | ||
..Default::default() | ||
}, | ||
servers: vec![ | ||
Server { | ||
url: "http://127.0.0.1:8000/v1".to_owned(), | ||
description: Some("Localhost".to_owned()), | ||
..Default::default() | ||
}, | ||
Server { | ||
url: "https://production-server.com/".to_owned(), | ||
description: Some("Remote development server".to_owned()), | ||
..Default::default() | ||
}, | ||
], | ||
..Default::default() | ||
} | ||
} | ||
|
||
pub fn main() { | ||
let result = start(); | ||
|
||
println!("Rocket: deorbit."); | ||
|
||
if let Some(err) = result.err() { | ||
println!("Error: {}", err); | ||
} | ||
} |
Oops, something went wrong.