1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use std::{collections::HashMap, fmt::Debug, time::Duration};

use reqwest::{header::HeaderMap, Method, StatusCode};
use url::Url;

/// # Request
/// Request is an internal struct used to create various OIDC requests.
#[derive(Debug)]
pub struct Request {
    /// Url of the request without query params
    pub url: String,
    /// Expected status code from the server
    pub expected: StatusCode,
    /// Http method of the request
    pub method: Method,
    /// Whether or not to expect body with the response
    pub expect_body: bool,
    /// Specifies if the request is using bearer auth, and checks for bearer token related errors
    pub bearer: bool,
    /// Headers that are sent in the request
    pub headers: HeaderMap,
    /// Query Params that are send with the request
    pub search_params: HashMap<String, Vec<String>>,
    /// The request body to be sent
    pub json: Option<serde_json::Value>,
    /// The request form body to be sent
    pub form: Option<HashMap<String, serde_json::Value>>,
    /// The request body to be sent
    pub body: Option<String>,
    /// Expected response type
    pub response_type: Option<String>,
    /// Specifies if the request is MTLS and needs client certificate
    pub mtls: bool,
}

impl Default for Request {
    fn default() -> Self {
        Self {
            expect_body: true,
            bearer: false,
            expected: StatusCode::OK,
            headers: HeaderMap::default(),
            method: Method::GET,
            url: "".to_string(),
            search_params: HashMap::new(),
            json: None,
            form: None,
            body: None,
            response_type: None,
            mtls: false,
        }
    }
}

impl Request {
    /// Converts `search_params` to a [reqwest] compatible query params format
    pub(crate) fn get_reqwest_query(&self) -> Vec<(String, String)> {
        let mut query_list: Vec<(String, String)> = vec![];

        for (k, v) in &self.search_params {
            for val in v {
                query_list.push((k.clone(), val.to_string()))
            }
        }

        query_list
    }

    pub(crate) fn merge_form(&mut self, request: &Self) {
        match (&mut self.form, &request.form) {
            (None, Some(_)) => self.form = request.form.clone(),
            (Some(own_form), Some(other_form)) => {
                for (k, v) in other_form {
                    own_form.insert(k.to_string(), v.to_owned());
                }
            }
            (None, None) | (Some(_), None) => {}
        }
    }

    pub(crate) fn merge_headers(&mut self, request: &Self) {
        for (k, v) in &request.headers {
            self.headers.insert(k, v.clone());
        }
    }
}

/// # Response
/// Response is the abstracted version of the [reqwest] Response (async and blocking).
#[derive(Debug, Clone)]
pub struct Response {
    /// Body from the response
    pub body: Option<String>,
    /// Status code of the response
    pub status: StatusCode,
    /// Response headers from the server
    pub headers: HeaderMap,
}

impl Response {
    /// Creates a new instance of Response from [reqwest::Response]
    pub(crate) async fn from_async(response: reqwest::Response) -> Self {
        let status = response.status();
        let headers = response.headers().clone();
        let body_result = response.text().await;
        let mut body: Option<String> = None;
        if let Ok(body_string) = body_result {
            if !body_string.is_empty() {
                body = Some(body_string);
            }
        }

        Self {
            body,
            status,
            headers,
        }
    }
}

/// # Request Interceptor
/// Type is a [Box]'ed [Interceptor] trait type.
pub type RequestInterceptor = Box<dyn Interceptor>;

/// # Lookup
/// Intention with this lookup is primarily for testing by
/// redirecting all requests to `localhost (127.0.0.1)` in combination with
/// the port of mock server.
///
/// *The url host, port and scheme will be swapped when building the request. No functionality is affected.*
///
/// ### *Example: *
///
/// ```
///     #[derive(Debug)]
///     struct CustomLookup;
///
///     impl Lookup for CustomLookup {
///          fn lookup(&mut self, _url: &Url) -> Url {
///              Url::parse("http://your-test-url:1234").unwrap()
///          }
///     }
///
///     RequestOptions {
///         lookup: Some(Box::new(CustomLookup{})),
///         ..Default::default()
///     }
/// ```
pub trait Lookup: Debug {
    /// The url with path (no query params) of the request url is passed as the `url`
    /// parameter in return expects a [Url] back.
    ///
    /// - The Scheme and host is required from the returned [Url]. Returns
    ///   an error otherwise. Port is optional
    ///
    /// - The entire url is just passed for reference.
    ///   Only the host:port will be replaced. Not the path.
    ///
    /// ### *Example:*
    ///
    /// ```
    ///     fn lookup(&mut self, _url: &Url) -> Url {
    ///         Url::parse("http://your-test-url:1234").unwrap()
    ///     }
    /// ```
    fn lookup(&mut self, url: &Url) -> Url;
}

/// # Interceptor
pub trait Interceptor: Debug {
    /// This method which is called before making a request
    fn intercept(&mut self, req: &Request) -> RequestOptions;

    /// Clones the [Interceptor]
    fn clone_box(&self) -> Box<dyn Interceptor>;
}

/// # RequestOptions
/// This struct is the return type of the [`Interceptor::intercept()`]
#[derive(Default, Debug)]
pub struct RequestOptions {
    /// ### Headers that are to be appended with the request that is going to be made
    pub headers: HeaderMap,
    /// ### Request timeout
    pub timeout: Duration,
    /// ### Client public certificate in pem format.
    /// The `client_crt` is ignored if `client_key` is not present
    pub client_crt: Option<String>,
    /// ### Client private certificate in pem format.
    /// The `client_key` is ignored if `client_crt` is not present
    pub client_key: Option<String>,
    /// ### Client certificate in pkcs 12 format `.p12` or `.pfx`
    /// Make sure to pass `client_pkcs_12_passphrase` if the
    /// certificate is protected.
    pub client_pkcs_12: Option<String>,
    /// Passphrase for pkcs_12 certificate
    pub client_pkcs_12_passphrase: Option<String>,
    /// ### Server certificate in pem format
    /// Useful when testing out with a self signed certificate and
    /// cannot switch on the `danger_accept_invalid_certs` property
    pub server_crt: Option<String>,
    /// ### Lookup
    /// [Lookup] trait allows you to resolve any url to a custom [Url].
    pub lookup: Option<Box<dyn Lookup>>,
    /// ### Accept invalid server certificates
    /// Accepts self signed or unverified or expired certificates.
    /// Use with caution.
    pub danger_accept_invalid_certs: bool,
}