Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support to Rocket-Okapi #1071

Merged
merged 12 commits into from
Oct 16, 2022
12 changes: 12 additions & 0 deletions examples/rocket_okapi_example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "sea-orm-rocket-okapi-example"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyt2y3 I added a new folder to exemplify the usage of rocket_okapi. Hope this is enough otherwise I could make some changes you guys consider to make it better.

version = "0.1.0"
authors = ["Sam Samai <[email protected]>", "Erick Pacheco <[email protected]"]
edition = "2021"
publish = false

[workspace]
members = [".", "api", "core", "entity", "migration", "dto"]

[dependencies]
rocket-example-api = { path = "api" }
12 changes: 12 additions & 0 deletions examples/rocket_okapi_example/README.md
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)
11 changes: 11 additions & 0 deletions examples/rocket_okapi_example/Rocket.toml
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"
39 changes: 39 additions & 0 deletions examples/rocket_okapi_example/api/Cargo.toml
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 <[email protected]>"]
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
117 changes: 117 additions & 0 deletions examples/rocket_okapi_example/api/src/error.rs
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,
},
}
}
}
139 changes: 139 additions & 0 deletions examples/rocket_okapi_example/api/src/lib.rs
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);
}
}
Loading