1use axum::{extract::rejection::BytesRejection, http::StatusCode, response::Response};
2
3use crate::{ContentType, IntoCodecResponse};
4
5#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum CodecRejection {
12 #[error(transparent)]
13 Bytes(#[from] BytesRejection),
14 #[cfg(feature = "json")]
15 #[error(transparent)]
16 Json(#[from] serde_json::Error),
17 #[cfg(feature = "form")]
18 #[error(transparent)]
19 Form(#[from] serde_urlencoded::de::Error),
20 #[cfg(feature = "msgpack")]
21 #[error(transparent)]
22 MsgPack(#[from] rmp_serde::decode::Error),
23 #[cfg(feature = "cbor")]
24 #[error(transparent)]
25 Cbor(#[from] ciborium::de::Error<std::io::Error>),
26 #[cfg(feature = "bincode")]
27 #[error(transparent)]
28 Bincode(#[from] bincode::error::DecodeError),
29 #[cfg(feature = "bitcode")]
30 #[error(transparent)]
31 Bitcode(#[from] bitcode::Error),
32 #[cfg(feature = "yaml")]
33 #[error(transparent)]
34 Yaml(#[from] serde_yaml::Error),
35 #[cfg(feature = "toml")]
36 #[error(transparent)]
37 Toml(#[from] toml::de::Error),
38 #[cfg(any(feature = "toml", feature = "yaml"))]
39 #[error(transparent)]
40 Utf8Error(#[from] core::str::Utf8Error),
41 #[cfg(feature = "validator")]
42 #[error("validator error")]
43 Validator(#[from] validator::ValidationErrors),
44}
45
46#[cfg(not(feature = "pretty-errors"))]
47impl IntoCodecResponse for CodecRejection {
48 fn into_codec_response(self, _content_type: ContentType) -> Response {
49 use axum::response::IntoResponse;
50
51 let mut response = self.to_string().into_response();
52
53 *response.status_mut() = self.status_code();
54 response
55 }
56}
57
58#[cfg(all(feature = "aide", feature = "pretty-errors"))]
59impl aide::OperationOutput for CodecRejection {
60 type Inner = Message;
61
62 fn operation_response(
63 ctx: &mut aide::generate::GenContext,
64 operation: &mut aide::openapi::Operation,
65 ) -> Option<aide::openapi::Response> {
66 axum::Json::<Message>::operation_response(ctx, operation)
67 }
68
69 fn inferred_responses(
70 ctx: &mut aide::generate::GenContext,
71 operation: &mut aide::openapi::Operation,
72 ) -> Vec<(Option<u16>, aide::openapi::Response)> {
73 axum::Json::<Message>::inferred_responses(ctx, operation)
74 }
75}
76
77#[cfg(all(feature = "aide", not(feature = "pretty-errors")))]
78impl aide::OperationOutput for CodecRejection {
79 type Inner = String;
80
81 fn operation_response(
82 ctx: &mut aide::generate::GenContext,
83 operation: &mut aide::openapi::Operation,
84 ) -> Option<aide::openapi::Response> {
85 axum::Json::<String>::operation_response(ctx, operation)
86 }
87
88 fn inferred_responses(
89 ctx: &mut aide::generate::GenContext,
90 operation: &mut aide::openapi::Operation,
91 ) -> Vec<(Option<u16>, aide::openapi::Response)> {
92 axum::Json::<String>::inferred_responses(ctx, operation)
93 }
94}
95
96#[cfg(feature = "pretty-errors")]
97impl IntoCodecResponse for CodecRejection {
98 fn into_codec_response(self, content_type: ContentType) -> Response {
99 let mut response = crate::Codec(self.message()).into_codec_response(content_type);
100
101 *response.status_mut() = self.status_code();
102 response
103 }
104}
105
106#[cfg(feature = "pretty-errors")]
107#[crate::apply(encode, crate = "crate")]
108pub struct Message {
109 pub code: &'static str,
111 pub content: String,
114}
115
116#[cfg(all(feature = "aide", feature = "pretty-errors"))]
117impl aide::OperationOutput for Message {
118 type Inner = Self;
119
120 fn operation_response(
121 ctx: &mut aide::generate::GenContext,
122 operation: &mut aide::openapi::Operation,
123 ) -> Option<aide::openapi::Response> {
124 axum::Json::<Self>::operation_response(ctx, operation)
125 }
126
127 fn inferred_responses(
128 ctx: &mut aide::generate::GenContext,
129 operation: &mut aide::openapi::Operation,
130 ) -> Vec<(Option<u16>, aide::openapi::Response)> {
131 axum::Json::<Self>::inferred_responses(ctx, operation)
132 }
133}
134
135impl CodecRejection {
136 #[must_use]
138 pub fn status_code(&self) -> StatusCode {
139 if matches!(self, Self::Bytes(..)) {
140 StatusCode::PAYLOAD_TOO_LARGE
141 } else {
142 StatusCode::BAD_REQUEST
143 }
144 }
145
146 #[cfg(feature = "pretty-errors")]
152 #[must_use]
153 pub fn message(&self) -> Message {
154 let code = match self {
155 Self::Bytes(..) => {
156 return Message {
157 code: "payload_too_large",
158 content: "The request payload is too large.".into(),
159 }
160 }
161 #[cfg(feature = "json")]
162 Self::Json(..) => "decode",
163 #[cfg(feature = "form")]
164 Self::Form(..) => "decode",
165 #[cfg(feature = "msgpack")]
166 Self::MsgPack(..) => "decode",
167 #[cfg(feature = "cbor")]
168 Self::Cbor(..) => "decode",
169 #[cfg(feature = "bincode")]
170 Self::Bincode(..) => "decode",
171 #[cfg(feature = "bitcode")]
172 Self::Bitcode(..) => "decode",
173 #[cfg(feature = "yaml")]
174 Self::Yaml(..) => "decode",
175 #[cfg(feature = "toml")]
176 Self::Toml(..) => "decode",
177 #[cfg(any(feature = "toml", feature = "yaml"))]
178 Self::Utf8Error(..) => {
179 return Message {
180 code: "malformed_utf8",
181 content: "The request payload is not valid UTF-8 when it should be.".into(),
182 }
183 }
184 #[cfg(feature = "validator")]
185 Self::Validator(err) => {
186 return Message {
187 code: "invalid_input",
188 content: format_validator(err),
189 }
190 }
191 };
192
193 Message {
194 code,
195 content: self.to_string(),
196 }
197 }
198}
199
200#[cfg(all(feature = "pretty-errors", feature = "validator"))]
201fn format_validator(err: &validator::ValidationErrors) -> String {
202 let mut buf = String::new();
203
204 for (field, error) in err.errors() {
205 append_validator_errors(field, error, &mut buf);
206 }
207
208 buf
209}
210
211#[cfg(all(feature = "pretty-errors", feature = "validator"))]
212fn append_validator_errors(field: &str, err: &validator::ValidationErrorsKind, buf: &mut String) {
213 match err {
214 validator::ValidationErrorsKind::Field(errors) => {
215 for error in errors {
216 if !buf.is_empty() {
217 buf.push_str(", ");
218 }
219
220 buf.push_str(field);
221 buf.push_str(": ");
222
223 if let Some(message) = &error.message {
224 buf.push_str(message);
225 } else {
226 buf.push_str(&error.code);
227 }
228
229 if !error.params.is_empty() {
230 buf.push_str(" (");
231
232 let mut params = error.params.iter();
233
234 if let Some((key, value)) = params.next() {
235 buf.push_str(key);
236 buf.push_str(": ");
237 buf.push_str(&value.to_string());
238 }
239
240 for (key, value) in params {
241 buf.push_str(", ");
242 buf.push_str(key);
243 buf.push_str(": ");
244 buf.push_str(&value.to_string());
245 }
246
247 buf.push(')');
248 }
249 }
250 }
251 validator::ValidationErrorsKind::List(message) => {
252 for error in message.values() {
253 for (field, errors) in error.errors() {
254 append_validator_errors(field, errors, buf);
255 }
256 }
257 }
258 validator::ValidationErrorsKind::Struct(struct_) => {
259 for (field, errors) in struct_.errors() {
260 append_validator_errors(field, errors, buf);
261 }
262 }
263 }
264}