oxide_auth/frontends/simple/
request.rs

1//! Simple, owning request and response types.
2use std::marker::PhantomData;
3
4use crate::endpoint::{QueryParameter, WebRequest, WebResponse};
5
6use std::borrow::Cow;
7use std::collections::HashMap;
8
9use url::Url;
10
11/// Open and simple implementation of `WebRequest`.
12#[derive(Clone, Debug, Default)]
13pub struct Request {
14    /// The key-value pairs in the url query component.
15    pub query: HashMap<String, String>,
16
17    /// The key-value pairs of a `x-www-form-urlencoded` body.
18    pub urlbody: HashMap<String, String>,
19
20    /// Provided authorization header.
21    pub auth: Option<String>,
22}
23
24/// Open and simple implementation of `WebResponse`.
25#[derive(Clone, Debug, Default)]
26pub struct Response {
27    /// HTTP status code.
28    pub status: Status,
29
30    /// A location header, for example for redirects.
31    pub location: Option<Url>,
32
33    /// Indicates how the client should have authenticated.
34    ///
35    /// Only set with `Unauthorized` status.
36    pub www_authenticate: Option<String>,
37
38    /// Encoded body of the response.
39    ///
40    /// One variant for each possible encoding type.
41    pub body: Option<Body>,
42}
43
44/// An enum containing the necessary HTTP status codes.
45#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
46pub enum Status {
47    /// Http status code 200.
48    Ok,
49
50    /// Http status code 302.
51    Redirect,
52
53    /// Http status code 400.
54    BadRequest,
55
56    /// Http status code 401.
57    Unauthorized,
58}
59
60/// Models the necessary body contents.
61///
62/// Real HTTP protocols should set a content type header for each of the body variants.
63#[derive(Clone, Debug)]
64pub enum Body {
65    /// A pure text body.
66    Text(String),
67
68    /// A json encoded body, `application/json`.
69    Json(String),
70}
71
72/// An uninhabited error type for simple requests and responses.
73///
74/// Since these types are built to never error on their operation, and `!` is not the stable unique
75/// representation for uninhabited types, this simple enum without variants is used instead.
76#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub enum NoError {}
78
79/// Changes the error type of a web request and response.
80pub struct MapErr<W, F, T>(W, F, PhantomData<T>);
81
82impl Body {
83    /// View the content of the body.
84    pub fn as_str(&self) -> &str {
85        match self {
86            Body::Text(ref body) => body,
87            Body::Json(ref body) => body,
88        }
89    }
90}
91
92impl<W, F, T> MapErr<W, F, T> {
93    /// Map all errors in request methods using the given function.
94    pub fn request(request: W, f: F) -> Self
95    where
96        W: WebRequest,
97        F: FnMut(W::Error) -> T,
98    {
99        MapErr(request, f, PhantomData)
100    }
101
102    /// Map all errors in response methods using the given function.
103    pub fn response(response: W, f: F) -> Self
104    where
105        W: WebResponse,
106        F: FnMut(W::Error) -> T,
107    {
108        MapErr(response, f, PhantomData)
109    }
110
111    /// Recover original response or request.
112    pub fn into_inner(self) -> W {
113        self.0
114    }
115}
116
117impl WebRequest for Request {
118    type Error = NoError;
119    type Response = Response;
120
121    fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
122        Ok(Cow::Borrowed(&self.query))
123    }
124
125    fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
126        Ok(Cow::Borrowed(&self.urlbody))
127    }
128
129    fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
130        Ok(self.auth.as_ref().map(|string| Cow::Borrowed(string.as_str())))
131    }
132}
133
134impl WebResponse for Response {
135    type Error = NoError;
136
137    fn ok(&mut self) -> Result<(), Self::Error> {
138        self.status = Status::Ok;
139        self.location = None;
140        self.www_authenticate = None;
141        Ok(())
142    }
143
144    /// A response which will redirect the user-agent to which the response is issued.
145    fn redirect(&mut self, url: Url) -> Result<(), Self::Error> {
146        self.status = Status::Redirect;
147        self.location = Some(url);
148        self.www_authenticate = None;
149        Ok(())
150    }
151
152    /// Set the response status to 400.
153    fn client_error(&mut self) -> Result<(), Self::Error> {
154        self.status = Status::BadRequest;
155        self.location = None;
156        self.www_authenticate = None;
157        Ok(())
158    }
159
160    /// Set the response status to 401 and add a `WWW-Authenticate` header.
161    fn unauthorized(&mut self, header_value: &str) -> Result<(), Self::Error> {
162        self.status = Status::Unauthorized;
163        self.location = None;
164        self.www_authenticate = Some(header_value.to_owned());
165        Ok(())
166    }
167
168    /// A pure text response with no special media type set.
169    fn body_text(&mut self, text: &str) -> Result<(), Self::Error> {
170        self.body = Some(Body::Text(text.to_owned()));
171        Ok(())
172    }
173
174    /// Json repsonse data, with media type `aplication/json.
175    fn body_json(&mut self, data: &str) -> Result<(), Self::Error> {
176        self.body = Some(Body::Json(data.to_owned()));
177        Ok(())
178    }
179}
180
181impl NoError {
182    /// Turn this into any type.
183    ///
184    /// Since `NoError` is uninhabited, this always works but is never executed.
185    pub fn into<T>(self) -> T {
186        match self {}
187    }
188}
189
190impl Default for Status {
191    fn default() -> Self {
192        Status::Ok
193    }
194}
195
196impl<W: WebRequest, F, T> WebRequest for MapErr<W, F, T>
197where
198    F: FnMut(W::Error) -> T,
199{
200    type Error = T;
201    type Response = MapErr<W::Response, F, T>;
202
203    fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
204        self.0.query().map_err(&mut self.1)
205    }
206
207    fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
208        self.0.urlbody().map_err(&mut self.1)
209    }
210
211    fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
212        self.0.authheader().map_err(&mut self.1)
213    }
214}
215
216impl<W: WebResponse, F, T> WebResponse for MapErr<W, F, T>
217where
218    F: FnMut(W::Error) -> T,
219{
220    type Error = T;
221
222    fn ok(&mut self) -> Result<(), Self::Error> {
223        self.0.ok().map_err(&mut self.1)
224    }
225
226    /// A response which will redirect the user-agent to which the response is issued.
227    fn redirect(&mut self, url: Url) -> Result<(), Self::Error> {
228        self.0.redirect(url).map_err(&mut self.1)
229    }
230
231    /// Set the response status to 400.
232    fn client_error(&mut self) -> Result<(), Self::Error> {
233        self.0.client_error().map_err(&mut self.1)
234    }
235
236    /// Set the response status to 401 and add a `WWW-Authenticate` header.
237    fn unauthorized(&mut self, header_value: &str) -> Result<(), Self::Error> {
238        self.0.unauthorized(header_value).map_err(&mut self.1)
239    }
240
241    /// A pure text response with no special media type set.
242    fn body_text(&mut self, text: &str) -> Result<(), Self::Error> {
243        self.0.body_text(text).map_err(&mut self.1)
244    }
245
246    /// Json repsonse data, with media type `aplication/json.
247    fn body_json(&mut self, data: &str) -> Result<(), Self::Error> {
248        self.0.body_json(data).map_err(&mut self.1)
249    }
250}