diff --git a/Cargo.toml b/Cargo.toml index 6e528be..c531f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,37 @@ [package] name = "limiting-factor" version = "0.5.1" authors = [ "Sébastien Santoro ", ] description = "Library to create a REST API with Diesel and Rocket" readme = "README.md" keywords = [ "Diesel", "API", "Rocket", "REST", ] categories = [ "web-programming", ] license = "BSD-2-Clause" repository = "https://devcentral.nasqueron.org/source/limiting-factor/" [dependencies] diesel = { version = "^1.0.0", features = ["postgres", "r2d2", "chrono"], optional = true } dotenv = "0.9.0" log = "^0.4.4" r2d2 = { version = "^0.8.2", optional = true } -rocket = "^0.3.16" -rocket_contrib = { version = "^0.3.16", features = [ "json" ] } +rocket = "^0.4.0" +rocket_contrib = { version = "^0.4.0", features = [ "json" ] } serde = { version = "1.0", optional = true } [features] default = ["minimal"] minimal = ["serialization"] full = ["pgsql", "serialization"] pgsql = ["diesel", "r2d2"] serialization = ["serde"] diff --git a/src/api/replies.rs b/src/api/replies.rs index 4e70667..fed0eee 100644 --- a/src/api/replies.rs +++ b/src/api/replies.rs @@ -1,193 +1,194 @@ //! # API standard and JSON responses. //! //! This module provides useful traits and methods to craft API replies from an existing type. #[cfg(feature = "pgsql")] use diesel::result::{DatabaseErrorInformation, DatabaseErrorKind, QueryResult}; #[cfg(feature = "pgsql")] use diesel::result::Error as ResultError; use rocket::http::Status; -use rocket::response::Failure; -use rocket_contrib::Json; +use rocket_contrib::json::Json; #[cfg(feature = "serialization")] use serde::Serialize; #[cfg(feature = "pgsql")] use std::error::Error; /* ------------------------------------------------------------- Custom types - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -pub type ApiJsonResponse = Result, Failure>; +pub type ApiJsonResponse = Result, Status>; /* ------------------------------------------------------------- API Response :: Implementation for QueryResult (Diesel ORM) :: Implementation for Json (Rocket contrib) :: Implementation for Serialize (Serde) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /// This trait allows to consume an object into an HTTP response. pub trait ApiResponse { - /// Consumes the value and creates a JSON or a Failure result response. + /// Consumes the value and creates a JSON or a Status result response. fn into_json_response(self) -> ApiJsonResponse; } #[cfg(feature = "pgsql")] impl ApiResponse for QueryResult { /// Prepares an API response from a query result. /// /// The result is the data structure prepared by the Diesel ORM after a SELECT query /// with one result, for example using `first` method. You can also you use it to /// parse the returning result (... RETURNING *), which is a default for Diesel after /// an INSERT query. /// /// So result can be: /// - Ok(T) - /// - Err(E) where E is a Failure containing an HTTP error code according the situation + /// - Err(E) where E is a Status containing an HTTP error code according the situation /// /// # Examples /// /// To offer a /player/foo route to serve player information from the players table: /// /// ``` /// use limiting_factor::api::ApiResponse; /// use limiting_factor::api::ApiJsonResponse; /// /// #[get("/player/")] /// pub fn get_player(connection: DatabaseConnection, name: String) -> ApiJsonResponse { /// players /// .filter(username.eq(&name)) /// .first::(&*connection) /// .into_json_response() /// } /// ``` /// /// This will produce a JSON representation when the result is found, /// a 404 error when no result is found, a 500 error if there is a database issue. /// /// To insert a new player in the same table: /// /// ``` /// use limiting_factor::api::ApiResponse; /// use limiting_factor::api::ApiJsonResponse; /// /// #[post("/register", format="application/json", data="")] /// pub fn register(connection: DatabaseConnection, user: Json) -> ApiJsonResponse { /// let user: UserToRegister = user.into_inner(); /// let player_to_create = user.to_new_player(); /// /// diesel::insert_into(players) /// .values(&player_to_create) /// .get_result::(&*connection) /// .into_json_response() /// } /// ``` /// /// This will produce a JSON representation of the newly inserted player if successful. /// If the insert fails because of an unique constraint violation (e.g. an username already /// taken), it returns a 409 Conflict. /// If the failure is from a foreign key integrity constraint, it returns a 400. /// If there is any other database issue, it returns a 500. fn into_json_response(self) -> ApiJsonResponse { self // CASE I - The query returns one value, we return a JSON representation fo the item .map(|item| Json(item)) .map_err(|error| match error { // Case II - The query returns no result, we return a 404 Not found response - ResultError::NotFound => Failure::from(Status::NotFound), + ResultError::NotFound => Status::NotFound, // Case III - We need to handle a database error, which could be a 400/409/500 ResultError::DatabaseError(kind, details) => { build_database_error_response(kind, details) } // Case IV - The error is probably server responsibility, log it and throw a 500 _ => error.into_failure_response(), }) } } /// Prepares an API response from a JSON. impl ApiResponse for Json { fn into_json_response(self) -> ApiJsonResponse { Ok(self) } } /// Prepares an API response from a Serde-serializable result. /// /// This is probably the easiest way to convert most struct /// into API responders. /// /// # Examples /// #[cfg(feature = "serialization")] impl ApiResponse for T where T: Serialize { fn into_json_response(self) -> ApiJsonResponse { Ok(Json(self)) } } /* ------------------------------------------------------------- Failure response :: Implementation for diesel::result::Error - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /// This trait allows to consume an object into an HTTP failure response. pub trait FailureResponse { /// Consumes the variable and creates a Failure response . - fn into_failure_response(self) -> Failure; + fn into_failure_response(self) -> Status; } #[cfg(feature = "pgsql")] impl FailureResponse for ResultError { - /// Consumes the error and creates a Failure 500 Internal server error response. - fn into_failure_response(self) -> Failure { + /// Consumes the error and creates a 500 Internal server error Status response. + fn into_failure_response(self) -> Status { build_internal_server_error_response(self.description()) } } /* ------------------------------------------------------------- Helper methods to prepare API responses - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -pub fn build_not_found_response() -> Failure { - Failure::from(Status::NotFound) +#[deprecated(since="0.6.0", note="Use directly Status::NotFound instead.")] +pub fn build_not_found_response() -> Status { + Status::NotFound } -pub fn build_bad_request_response() -> Failure { - Failure::from(Status::BadRequest) +#[deprecated(since="0.6.0", note="Use directly Status::BadRequest instead.")] +pub fn build_bad_request_response() -> Status { + Status::BadRequest } -pub fn build_internal_server_error_response(message: &str) -> Failure { +pub fn build_internal_server_error_response(message: &str) -> Status { warn!(target:"api", "{}", message); - Failure::from(Status::InternalServerError) + Status::InternalServerError } #[cfg(feature = "pgsql")] -fn build_database_error_response(error_kind: DatabaseErrorKind, info: Box) -> Failure { +fn build_database_error_response(error_kind: DatabaseErrorKind, info: Box) -> Status { match error_kind { // Case IIIa - The query tries to do an INSERT violating an unique constraint // e.g. two INSERT with the same unique value // We return a 409 Conflict - DatabaseErrorKind::UniqueViolation => Failure::from(Status::Conflict), + DatabaseErrorKind::UniqueViolation => Status::Conflict, // Case IIIb - The query violated a foreign key constraint // e.g. an INSERT referring to a non existing user 1004 // when there is no id 1004 in users table // We return a 400 Bad request - DatabaseErrorKind::ForeignKeyViolation => Failure::from(Status::BadRequest), + DatabaseErrorKind::ForeignKeyViolation => Status::BadRequest, // Case IIIc - For other databases errors, the client responsibility isn't involved. _ => build_internal_server_error_response(info.message()), } }