mocktail/
response.rs

1//! Mock response
2use std::num::NonZeroU16;
3
4use super::{body::Body, headers::Headers};
5use crate::{ext::CodeExt, Error};
6
7/// Represents a HTTP response.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Response {
10    pub status: StatusCode,
11    pub headers: Headers,
12    pub body: Body,
13    pub message: Option<String>,
14}
15
16impl Response {
17    pub fn new(body: impl Into<Body>) -> Self {
18        Self {
19            status: StatusCode::default(),
20            headers: Headers::default(),
21            body: body.into(),
22            message: None,
23        }
24    }
25
26    pub fn with_status(mut self, status: impl Into<StatusCode>) -> Self {
27        self.status = status.into();
28        self
29    }
30
31    pub fn with_headers(mut self, headers: Headers) -> Self {
32        self.headers = headers;
33        self
34    }
35
36    pub fn with_message(mut self, message: impl Into<String>) -> Self {
37        self.message = Some(message.into());
38        self
39    }
40
41    pub fn status(&self) -> &StatusCode {
42        &self.status
43    }
44
45    pub fn headers(&self) -> &Headers {
46        &self.headers
47    }
48
49    pub fn body(&self) -> &Body {
50        &self.body
51    }
52
53    pub fn message(&self) -> Option<&str> {
54        self.message.as_deref()
55    }
56
57    pub fn is_ok(&self) -> bool {
58        self.status.is_ok()
59    }
60
61    pub fn is_error(&self) -> bool {
62        self.status.is_error()
63    }
64}
65
66impl Default for Response {
67    fn default() -> Self {
68        Self {
69            status: StatusCode::OK,
70            headers: Headers::default(),
71            body: Body::default(),
72            message: None,
73        }
74    }
75}
76
77/// Represents a HTTP status code.
78#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct StatusCode(NonZeroU16);
80
81impl StatusCode {
82    pub fn from_u16(code: u16) -> Result<Self, Error> {
83        if !(100..1000).contains(&code) {
84            return Err(Error::Invalid("invalid status code".into()));
85        }
86        Ok(Self(NonZeroU16::new(code).unwrap()))
87    }
88
89    pub fn as_u16(&self) -> u16 {
90        self.0.get()
91    }
92
93    pub fn is_informational(&self) -> bool {
94        (100..200).contains(&self.as_u16())
95    }
96
97    pub fn is_success(&self) -> bool {
98        (200..300).contains(&self.as_u16())
99    }
100
101    pub fn is_redirection(&self) -> bool {
102        (300..400).contains(&self.as_u16())
103    }
104
105    pub fn is_error(&self) -> bool {
106        (400..600).contains(&self.as_u16())
107    }
108
109    pub fn is_ok(&self) -> bool {
110        self.is_success()
111    }
112
113    pub fn as_http(&self) -> http::StatusCode {
114        http::StatusCode::from_u16(self.as_u16()).unwrap()
115    }
116
117    pub fn as_grpc(&self) -> tonic::Code {
118        tonic::Code::from_u16(self.as_u16()).unwrap()
119    }
120
121    pub fn as_grpc_i32(&self) -> i32 {
122        self.as_grpc() as i32
123    }
124}
125
126impl StatusCode {
127    pub const CONTINUE: StatusCode = StatusCode(NonZeroU16::new(100).unwrap());
128    pub const SWITCHING_PROTOCOLS: StatusCode = StatusCode(NonZeroU16::new(101).unwrap());
129    pub const PROCESSING: StatusCode = StatusCode(NonZeroU16::new(102).unwrap());
130    pub const EARLY_HINTS: StatusCode = StatusCode(NonZeroU16::new(103).unwrap());
131
132    pub const OK: StatusCode = StatusCode(NonZeroU16::new(200).unwrap());
133    pub const CREATED: StatusCode = StatusCode(NonZeroU16::new(201).unwrap());
134    pub const ACCEPTED: StatusCode = StatusCode(NonZeroU16::new(202).unwrap());
135    pub const NON_AUTHORITATIVE_INFORMATION: StatusCode = StatusCode(NonZeroU16::new(203).unwrap());
136    pub const NO_CONTENT: StatusCode = StatusCode(NonZeroU16::new(204).unwrap());
137    pub const RESET_CONTENT: StatusCode = StatusCode(NonZeroU16::new(205).unwrap());
138    pub const PARTIAL_CONTENT: StatusCode = StatusCode(NonZeroU16::new(206).unwrap());
139    pub const MULTI_STATUS: StatusCode = StatusCode(NonZeroU16::new(207).unwrap());
140    pub const ALREADY_REPORTED: StatusCode = StatusCode(NonZeroU16::new(208).unwrap());
141    pub const IM_USED: StatusCode = StatusCode(NonZeroU16::new(226).unwrap());
142
143    pub const MULTIPLE_CHOICES: StatusCode = StatusCode(NonZeroU16::new(300).unwrap());
144    pub const MOVED_PERMANENTLY: StatusCode = StatusCode(NonZeroU16::new(301).unwrap());
145    pub const FOUND: StatusCode = StatusCode(NonZeroU16::new(302).unwrap());
146    pub const SEE_OTHER: StatusCode = StatusCode(NonZeroU16::new(303).unwrap());
147    pub const NOT_MODIFIED: StatusCode = StatusCode(NonZeroU16::new(304).unwrap());
148    pub const USE_PROXY: StatusCode = StatusCode(NonZeroU16::new(305).unwrap());
149    pub const TEMPORARY_REDIRECT: StatusCode = StatusCode(NonZeroU16::new(307).unwrap());
150    pub const PERMANENT_REDIRECT: StatusCode = StatusCode(NonZeroU16::new(308).unwrap());
151
152    pub const BAD_REQUEST: StatusCode = StatusCode(NonZeroU16::new(400).unwrap());
153    pub const UNAUTHORIZED: StatusCode = StatusCode(NonZeroU16::new(401).unwrap());
154    pub const PAYMENT_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(402).unwrap());
155    pub const FORBIDDEN: StatusCode = StatusCode(NonZeroU16::new(403).unwrap());
156    pub const NOT_FOUND: StatusCode = StatusCode(NonZeroU16::new(404).unwrap());
157    pub const METHOD_NOT_ALLOWED: StatusCode = StatusCode(NonZeroU16::new(405).unwrap());
158    pub const NOT_ACCEPTABLE: StatusCode = StatusCode(NonZeroU16::new(406).unwrap());
159    pub const PROXY_AUTHENTICATION_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(407).unwrap());
160    pub const REQUEST_TIMEOUT: StatusCode = StatusCode(NonZeroU16::new(408).unwrap());
161    pub const CONFLICT: StatusCode = StatusCode(NonZeroU16::new(409).unwrap());
162    pub const GONE: StatusCode = StatusCode(NonZeroU16::new(410).unwrap());
163    pub const LENGTH_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(411).unwrap());
164    pub const PRECONDITION_FAILED: StatusCode = StatusCode(NonZeroU16::new(412).unwrap());
165    pub const PAYLOAD_TOO_LARGE: StatusCode = StatusCode(NonZeroU16::new(413).unwrap());
166    pub const URI_TOO_LONG: StatusCode = StatusCode(NonZeroU16::new(414).unwrap());
167    pub const UNSUPPORTED_MEDIA_TYPE: StatusCode = StatusCode(NonZeroU16::new(415).unwrap());
168    pub const RANGE_NOT_SATISFIABLE: StatusCode = StatusCode(NonZeroU16::new(416).unwrap());
169    pub const EXPECTATION_FAILED: StatusCode = StatusCode(NonZeroU16::new(417).unwrap());
170    pub const IM_A_TEAPOT: StatusCode = StatusCode(NonZeroU16::new(418).unwrap());
171    pub const MISDIRECTED_REQUEST: StatusCode = StatusCode(NonZeroU16::new(421).unwrap());
172    pub const UNPROCESSABLE_ENTITY: StatusCode = StatusCode(NonZeroU16::new(422).unwrap());
173    pub const LOCKED: StatusCode = StatusCode(NonZeroU16::new(423).unwrap());
174    pub const FAILED_DEPENDENCY: StatusCode = StatusCode(NonZeroU16::new(424).unwrap());
175    pub const TOO_EARLY: StatusCode = StatusCode(NonZeroU16::new(425).unwrap());
176    pub const UPGRADE_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(426).unwrap());
177    pub const PRECONDITION_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(428).unwrap());
178    pub const TOO_MANY_REQUESTS: StatusCode = StatusCode(NonZeroU16::new(429).unwrap());
179    pub const REQUEST_HEADER_FIELDS_TOO_LARGE: StatusCode = StatusCode(NonZeroU16::new(431).unwrap());
180    pub const UNAVAILABLE_FOR_LEGAL_REASONS: StatusCode = StatusCode(NonZeroU16::new(451).unwrap());
181
182    pub const INTERNAL_SERVER_ERROR: StatusCode = StatusCode(NonZeroU16::new(500).unwrap());
183    pub const NOT_IMPLEMENTED: StatusCode = StatusCode(NonZeroU16::new(501).unwrap());
184    pub const BAD_GATEWAY: StatusCode = StatusCode(NonZeroU16::new(502).unwrap());
185    pub const SERVICE_UNAVAILABLE: StatusCode = StatusCode(NonZeroU16::new(503).unwrap());
186    pub const GATEWAY_TIMEOUT: StatusCode = StatusCode(NonZeroU16::new(504).unwrap());
187    pub const HTTP_VERSION_NOT_SUPPORTED: StatusCode = StatusCode(NonZeroU16::new(505).unwrap());
188    pub const VARIANT_ALSO_NEGOTIATES: StatusCode = StatusCode(NonZeroU16::new(506).unwrap());
189    pub const INSUFFICIENT_STORAGE: StatusCode = StatusCode(NonZeroU16::new(507).unwrap());
190    pub const LOOP_DETECTED: StatusCode = StatusCode(NonZeroU16::new(508).unwrap());
191    pub const NOT_EXTENDED: StatusCode = StatusCode(NonZeroU16::new(510).unwrap());
192    pub const NETWORK_AUTHENTICATION_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(511).unwrap());
193}
194
195impl Default for StatusCode {
196    fn default() -> Self {
197        Self::OK
198    }
199}
200
201impl PartialEq<u16> for StatusCode {
202    fn eq(&self, other: &u16) -> bool {
203        self.as_u16() == *other
204    }
205}
206
207impl PartialEq<StatusCode> for u16 {
208    fn eq(&self, other: &StatusCode) -> bool {
209        *self == other.as_u16()
210    }
211}
212
213impl PartialEq<StatusCode> for http::StatusCode {
214    fn eq(&self, other: &StatusCode) -> bool {
215        *self == other.as_u16()
216    }
217}
218
219impl TryFrom<u16> for StatusCode {
220    type Error = Error;
221
222    fn try_from(value: u16) -> Result<Self, Self::Error> {
223        Self::from_u16(value)
224    }
225}
226
227impl From<StatusCode> for u16 {
228    fn from(status: StatusCode) -> u16 {
229        status.0.get()
230    }
231}
232
233impl From<http::StatusCode> for StatusCode {
234    fn from(value: http::StatusCode) -> Self {
235        Self::from_u16(value.as_u16()).unwrap()
236    }
237}
238
239impl From<StatusCode> for http::StatusCode {
240    fn from(value: StatusCode) -> Self {
241        Self::from_u16(value.0.into()).unwrap()
242    }
243}
244
245impl From<StatusCode> for tonic::Code {
246    fn from(value: StatusCode) -> Self {
247        tonic::Code::from_u16(value.as_u16()).unwrap()
248    }
249}