ts_webapi/
error_response.rs1use core::panic::Location;
4
5use bytes::Bytes;
6use http::{HeaderValue, Response, StatusCode, header};
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug)]
11#[non_exhaustive]
12pub enum ErrorResponse {
13 BadRequest(BadRequest),
15 NotFound,
17 InternalServerError,
19}
20
21impl ErrorResponse {
22 pub fn not_found() -> Self {
24 Self::NotFound
25 }
26
27 pub fn internal_server_error() -> Self {
29 Self::InternalServerError
30 }
31
32 #[track_caller]
38 pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
39 match self {
40 Self::BadRequest(bad_request) => bad_request.into_response(),
41 Self::NotFound => Response::builder()
42 .status(StatusCode::NOT_FOUND)
43 .body(B::from(Bytes::new()))
44 .expect("response should be valid"),
45 Self::InternalServerError => Response::builder()
46 .status(StatusCode::INTERNAL_SERVER_ERROR)
47 .body(B::from(Bytes::new()))
48 .expect("response should be valid"),
49 }
50 }
51}
52
53impl From<BadRequest> for ErrorResponse {
54 fn from(value: BadRequest) -> Self {
55 Self::BadRequest(value)
56 }
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct Problem {
63 pub pointer: String,
65 pub detail: String,
67}
68impl Problem {
69 pub fn new<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
71 Self {
72 pointer: pointer.to_string(),
73 detail: detail.to_string(),
74 }
75 }
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct BadRequest {
82 pub problems: Vec<Problem>,
84}
85
86impl BadRequest {
87 pub fn basic<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
89 Self {
90 problems: vec![Problem::new(pointer, detail)],
91 }
92 }
93
94 pub fn new() -> Self {
96 Self { problems: vec![] }
97 }
98
99 pub fn add_problem(&mut self, problem: Problem) {
101 self.problems.push(problem);
102 }
103
104 pub fn has_problems(&self) -> bool {
106 !self.problems.is_empty()
107 }
108
109 #[track_caller]
114 pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
115 assert!(
116 self.has_problems(),
117 "{:?} An empty bad request cannot be returned",
118 Location::caller()
119 );
120
121 let json =
122 serde_json::to_string(&self).expect("error response should always be serializable");
123
124 Response::builder()
125 .status(StatusCode::BAD_REQUEST)
126 .header(
127 header::CONTENT_TYPE,
128 HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
129 )
130 .body(B::from(Bytes::from(json)))
131 .expect("error response should produce a valid response")
132 }
133}
134
135#[cfg(feature = "axum")]
136impl axum::response::IntoResponse for BadRequest {
137 fn into_response(self) -> axum::response::Response {
138 self.into_response::<axum::body::Body>().into_response()
139 }
140}
141
142#[cfg(feature = "axum")]
143impl axum::response::IntoResponse for ErrorResponse {
144 fn into_response(self) -> axum::response::Response {
145 self.into_response()
146 }
147}