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
use std::{collections::HashMap, time::Duration};

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

/// `type RequestInterceptor` is the alias for the closure that can be passed through
/// one of several methods that will be executed every time a request is being made.
pub type RequestInterceptor = Box<dyn FnMut(&Request) -> RequestOptions>;

/// # Request
/// Request is an internal struct that is used by each OIDC protocol methods.
#[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>,
    /// Expected response type
    pub response_type: Option<String>,
}

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,
            response_type: None,
        }
    }
}

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

/// # 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::blocking::Response]
    pub fn from(response: reqwest::blocking::Response) -> Self {
        let status = response.status();
        let headers = response.headers().clone();
        let body_result = response.text();
        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,
        }
    }

    /// Creates a new instance of Response from [reqwest::Response]
    pub 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,
        }
    }
}

/// # RequestOptions
/// This struct is the return type of the request interceptor that can be passed to various methods
/// such as:
/// 1. [`Issuer::webfinger_with_interceptor_async()`]
/// 2. [`Issuer::webfinger_with_interceptor()`]
/// 3. [`Issuer::discover_with_interceptor_async()`]
/// 4. [`Issuer::discover_with_interceptor()`]
#[derive(Debug)]
pub struct RequestOptions {
    /// Headers that are tobe appended with the request that is going to be made
    pub headers: HeaderMap,
    /// Request timeout
    pub timeout: Duration,
}