ts_webapi/
error_response.rs1use core::panic::Location;
4
5use bytes::Bytes;
6use http::{HeaderValue, Response, StatusCode, header};
7use serde::{Deserialize, Serialize};
8use ts_error::Report;
9
10#[derive(Clone, Debug)]
12#[non_exhaustive]
13pub enum ErrorResponse {
14 BadRequest(BadRequest),
16 NotFound,
18 InternalServerError,
20}
21
22impl ErrorResponse {
23 pub fn not_found() -> Self {
25 Self::NotFound
26 }
27
28 pub fn internal_server_error() -> Self {
30 Self::InternalServerError
31 }
32
33 pub fn bad_request<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
35 Self::BadRequest(BadRequest::basic(pointer, detail))
36 }
37
38 #[track_caller]
44 pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
45 match self {
46 Self::BadRequest(bad_request) => bad_request.into_response(),
47 Self::NotFound => Response::builder()
48 .status(StatusCode::NOT_FOUND)
49 .body(B::from(Bytes::new()))
50 .expect("response should be valid"),
51 Self::InternalServerError => Response::builder()
52 .status(StatusCode::INTERNAL_SERVER_ERROR)
53 .body(B::from(Bytes::new()))
54 .expect("response should be valid"),
55 }
56 }
57}
58
59impl From<BadRequest> for ErrorResponse {
60 fn from(value: BadRequest) -> Self {
61 Self::BadRequest(value)
62 }
63}
64
65impl<E: core::error::Error> From<E> for ErrorResponse {
66 #[track_caller]
67 fn from(value: E) -> Self {
68 let error = Report::new(value);
69 let location = Location::caller();
70 log::error!("[{location}] {error}");
71 Self::InternalServerError
72 }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct Problem {
79 pub pointer: String,
81 pub detail: String,
83}
84impl Problem {
85 pub fn new<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
87 Self {
88 pointer: pointer.to_string(),
89 detail: detail.to_string(),
90 }
91 }
92}
93
94#[derive(Clone, Debug, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct BadRequest {
98 pub problems: Vec<Problem>,
100}
101
102impl BadRequest {
103 pub fn basic<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
105 Self {
106 problems: vec![Problem::new(pointer, detail)],
107 }
108 }
109
110 pub fn new() -> Self {
112 Self { problems: vec![] }
113 }
114
115 pub fn add_problem(&mut self, problem: Problem) {
117 self.problems.push(problem);
118 }
119
120 pub fn has_problems(&self) -> bool {
122 !self.problems.is_empty()
123 }
124
125 #[track_caller]
130 pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
131 assert!(
132 self.has_problems(),
133 "{:?} An empty bad request cannot be returned",
134 Location::caller()
135 );
136
137 let json =
138 serde_json::to_string(&self).expect("error response should always be serializable");
139
140 Response::builder()
141 .status(StatusCode::BAD_REQUEST)
142 .header(
143 header::CONTENT_TYPE,
144 HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
145 )
146 .body(B::from(Bytes::from(json)))
147 .expect("error response should produce a valid response")
148 }
149}
150
151#[cfg(feature = "axum")]
152impl axum::response::IntoResponse for BadRequest {
153 fn into_response(self) -> axum::response::Response {
154 self.into_response::<axum::body::Body>().into_response()
155 }
156}
157
158#[cfg(feature = "axum")]
159impl axum::response::IntoResponse for ErrorResponse {
160 fn into_response(self) -> axum::response::Response {
161 self.into_response()
162 }
163}