predawn_core/
response_error.rs1use std::{
2 collections::{BTreeMap, BTreeSet},
3 convert::Infallible,
4 error::Error,
5 fmt,
6 string::FromUtf8Error,
7};
8
9use http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
10use mime::TEXT_PLAIN_UTF_8;
11use snafu::Snafu;
12
13use crate::{
14 error::BoxError,
15 error_ext::{ErrorExt, NextError},
16 location::Location,
17 media_type::MultiResponseMediaType,
18 openapi::{self, Schema},
19 response::Response,
20};
21
22pub trait ResponseError: ErrorExt + Send + Sync + Sized + 'static {
23 fn as_status(&self) -> StatusCode;
24
25 fn status_codes(codes: &mut BTreeSet<StatusCode>);
26
27 fn as_response(&self) -> Response {
28 Response::builder()
29 .status(self.as_status())
30 .header(
31 CONTENT_TYPE,
32 HeaderValue::from_static(TEXT_PLAIN_UTF_8.as_ref()),
33 )
34 .body(self.to_string().into())
35 .unwrap()
36 }
37
38 fn responses(
39 schemas: &mut BTreeMap<String, Schema>,
40 schemas_in_progress: &mut Vec<String>,
41 ) -> BTreeMap<StatusCode, openapi::Response> {
42 let mut codes = BTreeSet::new();
43
44 Self::status_codes(&mut codes);
45
46 codes
47 .into_iter()
48 .map(|status| {
49 (
50 status,
51 openapi::Response {
52 description: status.canonical_reason().unwrap_or_default().to_string(),
53 content: <String as MultiResponseMediaType>::content(
54 schemas,
55 schemas_in_progress,
56 ),
57 ..Default::default()
58 },
59 )
60 })
61 .collect()
62 }
63
64 #[doc(hidden)]
65 fn inner(self) -> BoxError {
66 Box::new(self)
67 }
68}
69
70impl ErrorExt for Infallible {
71 fn entry(&self) -> (Location, NextError<'_>) {
72 match *self {}
73 }
74}
75
76impl ResponseError for Infallible {
77 fn as_status(&self) -> StatusCode {
78 match *self {}
79 }
80
81 fn status_codes(_: &mut BTreeSet<StatusCode>) {}
82
83 fn as_response(&self) -> Response {
84 match *self {}
85 }
86
87 fn responses(
88 _: &mut BTreeMap<String, Schema>,
89 _: &mut Vec<String>,
90 ) -> BTreeMap<StatusCode, openapi::Response> {
91 BTreeMap::new()
92 }
93}
94
95#[derive(Debug)]
96pub struct RequestBodyLimitError {
97 pub location: Location,
98 pub actual: Option<usize>,
99 pub expected: usize,
100}
101
102impl fmt::Display for RequestBodyLimitError {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match self.actual {
105 Some(actual) => {
106 write!(
107 f,
108 "payload too large: expected `{}` but actual `{}`",
109 self.expected, actual
110 )
111 }
112 None => {
113 write!(
114 f,
115 "payload too large (no content length): expected `{}`",
116 self.expected
117 )
118 }
119 }
120 }
121}
122
123impl Error for RequestBodyLimitError {}
124
125impl ErrorExt for RequestBodyLimitError {
126 fn entry(&self) -> (Location, NextError<'_>) {
127 (self.location, NextError::None)
128 }
129}
130
131impl ResponseError for RequestBodyLimitError {
132 fn as_status(&self) -> StatusCode {
133 StatusCode::PAYLOAD_TOO_LARGE
134 }
135
136 fn status_codes(codes: &mut BTreeSet<StatusCode>) {
137 codes.insert(StatusCode::PAYLOAD_TOO_LARGE);
138 }
139}
140
141#[derive(Debug, Snafu)]
142#[snafu(visibility(pub(crate)))]
143pub enum ReadBytesError {
144 #[snafu(display("{source}"))]
145 RequestBodyLimitError {
146 #[snafu(implicit)]
147 location: Location,
148 source: RequestBodyLimitError,
149 },
150 #[snafu(display("failed to read bytes from request body"))]
151 UnknownBodyError {
152 #[snafu(implicit)]
153 location: Location,
154 source: BoxError,
155 },
156}
157
158impl ErrorExt for ReadBytesError {
159 fn entry(&self) -> (Location, NextError<'_>) {
160 match self {
161 ReadBytesError::RequestBodyLimitError { location, source } => {
162 (*location, NextError::Ext(source))
163 }
164 ReadBytesError::UnknownBodyError { location, source } => {
165 (*location, NextError::Std(source.as_ref()))
166 }
167 }
168 }
169}
170
171impl ResponseError for ReadBytesError {
172 fn as_status(&self) -> StatusCode {
173 match self {
174 ReadBytesError::RequestBodyLimitError { source, .. } => source.as_status(),
175 ReadBytesError::UnknownBodyError { .. } => StatusCode::BAD_REQUEST,
176 }
177 }
178
179 fn status_codes(codes: &mut BTreeSet<StatusCode>) {
180 RequestBodyLimitError::status_codes(codes);
181 codes.insert(StatusCode::BAD_REQUEST);
182 }
183}
184
185#[derive(Debug, Snafu)]
186#[snafu(visibility(pub(crate)))]
187pub enum ReadStringError {
188 #[snafu(display("{source}"))]
189 ReadBytes {
190 #[snafu(implicit)]
191 location: Location,
192 source: ReadBytesError,
193 },
194 #[snafu(display("{source}"))]
195 InvalidUtf8 {
196 #[snafu(implicit)]
197 location: Location,
198 source: FromUtf8Error,
199 },
200}
201
202impl ErrorExt for ReadStringError {
203 fn entry(&self) -> (Location, NextError<'_>) {
204 match self {
205 ReadStringError::ReadBytes { location, source } => (*location, NextError::Ext(source)),
206 ReadStringError::InvalidUtf8 { location, source } => {
207 (*location, NextError::Std(source))
208 }
209 }
210 }
211}
212
213impl ResponseError for ReadStringError {
214 fn as_status(&self) -> StatusCode {
215 match self {
216 ReadStringError::ReadBytes { source, .. } => source.as_status(),
217 ReadStringError::InvalidUtf8 { .. } => StatusCode::BAD_REQUEST,
218 }
219 }
220
221 fn status_codes(codes: &mut BTreeSet<StatusCode>) {
222 ReadBytesError::status_codes(codes);
223 codes.insert(StatusCode::BAD_REQUEST);
224 }
225}