use crate::{ResourceType, StatusCode}; #[cfg(feature = "openapi")] use crate::{OpenapiSchema, OpenapiType}; use mime::{Mime, APPLICATION_JSON}; #[cfg(feature = "openapi")] use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty}; use serde::Serialize; use serde_json::error::Error as SerdeJsonError; use std::error::Error; pub struct Response { pub status : StatusCode, pub body : String, pub mime : Option } impl Response { pub fn new(status : StatusCode, body : String, mime : Option) -> Self { Self { status, body, mime } } pub fn json(status : StatusCode, body : String) -> Self { Self { status, body, mime: Some(APPLICATION_JSON) } } pub fn no_content() -> Self { Self { status: StatusCode::NO_CONTENT, body: String::new(), mime: None } } } /// A trait provided to convert a resource's result to json. pub trait ResourceResult { /// Turn this into a response that can be returned to the browser. This api will likely /// change in the future. fn to_response(&self) -> Result; /// Return a list of supported mime types. fn accepted_types() -> Option> { None } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema; #[cfg(feature = "openapi")] fn default_status() -> StatusCode { StatusCode::OK } } #[cfg(feature = "openapi")] impl crate::OpenapiType for Res { fn schema() -> OpenapiSchema { Self::schema() } } /// The default json returned on an 500 Internal Server Error. #[derive(Debug, Serialize)] pub struct ResourceError { error : bool, message : String } impl From for ResourceError { fn from(message : T) -> Self { Self { error: true, message: message.to_string() } } } impl ResourceResult for Result { fn to_response(&self) -> Result { Ok(match self { Ok(r) => Response::json(StatusCode::OK, serde_json::to_string(r)?), Err(e) => { let err : ResourceError = e.into(); Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?) } }) } fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { R::schema() } } /** This can be returned from a resource when there is no cause of an error. For example: ``` # #[macro_use] extern crate gotham_restful_derive; # use gotham::state::State; # use gotham_restful::*; # use serde::{Deserialize, Serialize}; # # #[derive(Resource)] # struct MyResource; # #[derive(Deserialize, Serialize)] # #[derive(OpenapiType)] struct MyResponse { message: String } #[rest_read_all(MyResource)] fn read_all(_state: &mut State) -> Success { let res = MyResponse { message: "I'm always happy".to_string() }; res.into() } ``` */ pub struct Success(T); impl From for Success { fn from(t : T) -> Self { Self(t) } } impl ResourceResult for Success { fn to_response(&self) -> Result { Ok(Response::json(StatusCode::OK, serde_json::to_string(&self.0)?)) } fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { T::schema() } } /** This is the return type of a resource that doesn't actually return something. It will result in a _204 No Content_ answer by default. You don't need to use this type directly if using the function attributes: ``` # #[macro_use] extern crate gotham_restful_derive; # use gotham::state::State; # use gotham_restful::*; # # #[derive(Resource)] # struct MyResource; # #[rest_read_all(MyResource)] fn read_all(_state: &mut State) { // do something } ``` */ #[derive(Default)] pub struct NoContent; impl From<()> for NoContent { fn from(_ : ()) -> Self { Self {} } } impl ResourceResult for NoContent { /// This will always be a _204 No Content_ together with an empty string. fn to_response(&self) -> Result { Ok(Response::no_content()) } /// Returns the schema of the `()` type. #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { <()>::schema() } /// This will always be a _204 No Content_ #[cfg(feature = "openapi")] fn default_status() -> StatusCode { StatusCode::NO_CONTENT } } impl ResourceResult for Result { fn to_response(&self) -> Result { match self { Ok(nc) => nc.to_response(), Err(e) => { let err : ResourceError = e.into(); Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)) } } } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { ::schema() } #[cfg(feature = "openapi")] fn default_status() -> StatusCode { NoContent::default_status() } } pub struct Raw { pub raw : T, pub mime : Mime } impl Raw { pub fn new(raw : T, mime : Mime) -> Self { Self { raw, mime } } } impl ResourceResult for Raw { fn to_response(&self) -> Result { Ok(Response::new(StatusCode::OK, self.raw.to_string(), Some(self.mime.clone()))) } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary), pattern: None, enumeration: Vec::new() }))) } } impl ResourceResult for Result, E> where Raw : ResourceResult { fn to_response(&self) -> Result { match self { Ok(raw) => raw.to_response(), Err(e) => { let err : ResourceError = e.into(); Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)) } } } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { as ResourceResult>::schema() } } #[cfg(test)] mod test { use super::*; use mime::TEXT_PLAIN; use thiserror::Error; #[derive(Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] struct Msg { msg : String } #[derive(Debug, Default, Error)] #[error("An Error")] struct MsgError; #[test] fn resource_result_ok() { let ok : Result = Ok(Msg::default()); let res = ok.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.body, r#"{"msg":""}"#); assert_eq!(res.mime, Some(APPLICATION_JSON)); } #[test] fn resource_result_err() { let err : Result = Err(MsgError::default()); let res = err.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(res.body, format!(r#"{{"error":true,"message":"{}"}}"#, err.unwrap_err())); assert_eq!(res.mime, Some(APPLICATION_JSON)); } #[test] fn success_always_successfull() { let success : Success = Msg::default().into(); let res = success.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.body, r#"{"msg":""}"#); assert_eq!(res.mime, Some(APPLICATION_JSON)); } #[test] fn no_content_has_empty_response() { let no_content = NoContent::default(); let res = no_content.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.body, ""); assert_eq!(res.mime, None); } #[test] fn no_content_result() { let no_content : Result = Ok(NoContent::default()); let res = no_content.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.body, ""); assert_eq!(res.mime, None); } #[test] fn raw_response() { let msg = "Test"; let raw = Raw::new(msg, TEXT_PLAIN); let res = raw.to_response().expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.body, msg); assert_eq!(res.mime, Some(TEXT_PLAIN)); } }