use anyhow::Error;
use df_rocket_okapi::gen::OpenApiGenerator;
use df_rocket_okapi::response::OpenApiResponder;
use df_rocket_okapi::Result as OpenApiResult;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use okapi::openapi3::Responses;
use rocket::catch;
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
fn add_204_error(responses: &mut Responses) {
responses
.responses
.entry("204".to_owned())
.or_insert_with(|| {
let response = okapi::openapi3::Response {
description: "\
# [204 No Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204)\n\
This response is given when you request an item using a filter and there is no \
item with the exact filter criteria.\n\n\
In most cases you requested an `id` that does not exists in the database. \
The body of this response will be empty.\
"
.to_owned(),
..Default::default()
};
response.into()
});
}
fn add_304_error(responses: &mut Responses) {
responses
.responses
.entry("304".to_owned())
.or_insert_with(|| {
let response = okapi::openapi3::Response{
description: "\
# [304 Not Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304)\n\
This response is given when the ETag of in the query parameters matches the \
generated response by the server. This can then be used to enable caching of \
responses.\n\n\
If the ETags do not match it will give you the full response (with a 200 OK). \
The response will give include the new ETag.\n\n\
[More info can be found here]\
(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match).\
"
.to_owned(),
..Default::default()
};
response.into()
});
}
fn add_400_error(responses: &mut Responses) {
responses
.responses
.entry("400".to_owned())
.or_insert_with(|| {
let response = okapi::openapi3::Response{
description: "\
# [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)\n\
This request given is wrongly formatted or data asked could not be fulfilled.\n\n\
This is most likely because of the `filter_by`, `order_by` or `group_by` parameters. \
The properties you are asking for do not exist or does not allow filtering or ordering. \
"
.to_owned(),
..Default::default()
};
response.into()
});
}
fn add_404_error(responses: &mut Responses) {
responses.responses.entry("404".to_owned())
.or_insert_with(|| {
let response = okapi::openapi3::Response{
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.\n\n\
**Note:** This is not exactly a response by this endpoint. But might returned when \
you wrongly input one or more of the path or query parameters. An example would be \
that the server expects and `int32` and you have given it \"100m\", which is a `string` \
because of the `m` character.\n\n\
So when you get this error and you expect a result. Check all the types of the parameters. \
".to_owned(),
..Default::default()
};
response.into()
});
}
fn add_500_error(responses: &mut Responses) {
responses
.responses
.entry("500".to_owned())
.or_insert_with(|| {
let response = okapi::openapi3::Response {
description: format!(
"\
# [500 Internal Server Error]\
(https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)\n\
This response is given when the server has an internal error that it could not \
recover from.\n\n\
If you get this response please report this as an [issue here]({}).\
",
df_st_core::git_issue::link_to_issue_template("bug")
),
..Default::default()
};
response.into()
});
}
#[derive(Serialize, Deserialize)]
pub struct APIErrorResponse {
errors: Vec<APIError>,
}
#[derive(Serialize, Deserialize)]
pub struct APIError {
pub message: String,
pub issue_link: Option<String>,
pub code: u16,
}
#[derive(Serialize, Deserialize)]
pub struct APIMessageResponse {
message: APIMessage,
}
#[derive(Serialize, Deserialize)]
pub struct APIMessage {
pub message: String,
pub code: u16,
}
#[derive(Debug, Default)]
pub struct APIErrorBadRequest {
pub err: Option<Error>,
pub message: Option<String>,
}
impl From<Error> for APIErrorBadRequest {
fn from(error: Error) -> Self {
APIErrorBadRequest {
err: Some(error),
message: None,
}
}
}
impl From<String> for APIErrorBadRequest {
fn from(message: String) -> Self {
APIErrorBadRequest {
err: None,
message: Some(message),
}
}
}
impl From<&str> for APIErrorBadRequest {
fn from(message: &str) -> Self {
APIErrorBadRequest {
err: None,
message: Some(message.to_owned()),
}
}
}
#[derive(Debug, Default)]
pub struct APIErrorNoContent {
pub err: Option<Error>,
pub bad_request: bool,
pub message: Option<String>,
}
impl APIErrorNoContent {
pub fn new() -> Self {
APIErrorNoContent::default()
}
}
impl From<Error> for APIErrorNoContent {
fn from(error: Error) -> Self {
APIErrorNoContent {
err: Some(error),
bad_request: false,
message: None,
}
}
}
impl From<APIErrorBadRequest> for APIErrorNoContent {
fn from(error: APIErrorBadRequest) -> Self {
APIErrorNoContent {
err: error.err,
bad_request: true,
message: error.message,
}
}
}
impl<'r> Responder<'r> for APIErrorNoContent {
fn respond_to(self, _: &Request) -> response::Result<'r> {
let mut error_response = APIErrorResponse { errors: vec![] };
if let Some(error) = self.err {
error!("{}", error);
error_response.errors.push(APIError {
message: error.to_string(),
issue_link: None,
code: match self.bad_request {
true => 400,
false => 500,
},
});
}
if let Some(message) = self.message {
error_response.errors.push(APIError {
message,
issue_link: None,
code: match self.bad_request {
true => 400,
false => 500,
},
});
}
let mut response = Response::build();
response.status(if error_response.errors.is_empty() {
Status::NoContent
} else if self.bad_request {
Status::BadRequest
} else {
Status::InternalServerError
});
if !error_response.errors.is_empty() {
response.header(ContentType::JSON);
response.sized_body(std::io::Cursor::new(
serde_json::to_string(&error_response).unwrap(),
));
}
response.ok()
}
}
impl OpenApiResponder<'_> for APIErrorNoContent {
fn responses(_: &mut OpenApiGenerator) -> OpenApiResult<Responses> {
let mut responses = Responses::default();
add_204_error(&mut responses);
add_400_error(&mut responses);
add_404_error(&mut responses);
add_500_error(&mut responses);
Ok(responses)
}
}
#[derive(Debug, Default)]
pub struct APIErrorNotModified {
pub err: Option<Error>,
pub bad_request: bool,
pub message: Option<String>,
}
impl APIErrorNotModified {
pub fn new() -> Self {
APIErrorNotModified::default()
}
}
impl From<Error> for APIErrorNotModified {
fn from(error: Error) -> Self {
APIErrorNotModified {
err: Some(error),
bad_request: false,
message: None,
}
}
}
impl From<APIErrorBadRequest> for APIErrorNotModified {
fn from(error: APIErrorBadRequest) -> Self {
APIErrorNotModified {
err: error.err,
bad_request: true,
message: error.message,
}
}
}
impl<'r> Responder<'r> for APIErrorNotModified {
fn respond_to(self, _: &Request) -> response::Result<'r> {
let mut error_response = APIErrorResponse { errors: vec![] };
if let Some(error) = self.err {
error!("{}", error);
error_response.errors.push(APIError {
message: error.to_string(),
issue_link: None,
code: match self.bad_request {
true => 400,
false => 500,
},
});
}
if let Some(message) = self.message {
error_response.errors.push(APIError {
message,
issue_link: None,
code: match self.bad_request {
true => 400,
false => 500,
},
});
}
let mut response = Response::build();
response.status(if error_response.errors.is_empty() {
Status::NotModified
} else if self.bad_request {
Status::BadRequest
} else {
Status::InternalServerError
});
if !error_response.errors.is_empty() {
response.header(ContentType::JSON);
response.sized_body(std::io::Cursor::new(
serde_json::to_string(&error_response).unwrap(),
));
}
response.ok()
}
}
impl OpenApiResponder<'_> for APIErrorNotModified {
fn responses(_: &mut OpenApiGenerator) -> OpenApiResult<Responses> {
let mut responses = Responses::default();
add_304_error(&mut responses);
add_400_error(&mut responses);
add_404_error(&mut responses);
add_500_error(&mut responses);
Ok(responses)
}
}
#[catch(500)]
pub fn internal_error() -> Json<APIErrorResponse> {
Json(APIErrorResponse {
errors: vec![APIError {
message: "I don't know what happened there... \
Did someone leave the caverns open?\n\
Please open a new issue for this: \
https://gitlab.com/df_storyteller/df-storyteller/issues/new"
.to_owned(),
issue_link: Some(
"https://gitlab.com/df_storyteller/df-storyteller/issues/new".to_owned(),
),
code: 500,
}],
})
}
#[catch(400)]
pub fn bad_request(req: &Request) -> Json<APIErrorResponse> {
Json(APIErrorResponse {
errors: vec![APIError {
message: format!("Bad request: URL `{}`.", req.uri()),
issue_link: None,
code: 400,
}],
})
}
#[catch(404)]
pub fn not_found(req: &Request) -> Json<APIErrorResponse> {
Json(APIErrorResponse {
errors: vec![APIError {
message: format!("This requested URL `{}` was not found.", req.uri()),
issue_link: None,
code: 404,
}],
})
}
#[catch(204)]
pub fn no_content(req: &Request) -> Json<APIMessageResponse> {
Json(APIMessageResponse {
message: APIMessage {
message: format!("No Content found here `{}`.", req.uri()),
code: 204,
},
})
}
#[catch(304)]
pub fn not_modified(_req: &Request) -> Json<APIMessageResponse> {
Json(APIMessageResponse {
message: APIMessage {
message: "Content has not been modified.".to_owned(),
code: 304,
},
})
}