predawn_core/
response_error.rs

1use 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}