eio_okta_client/
client.rs

1use crate::MapInto;
2use crate::Options;
3use eio_okta_api::authorization::SSWS;
4use eio_okta_api::traits::Endpoint;
5use eio_okta_api::traits::IntoRequest;
6use eio_okta_api::traits::Pagination;
7use eio_okta_api::traits::Response;
8use eio_okta_api::traits::Service;
9use headers::Authorization;
10use headers::HeaderMapExt;
11use http::Request;
12use http::Uri;
13
14impl From<Options> for Client<crate::OktaService> {
15  fn from(options: Options) -> Self {
16    let Options {
17      agent,
18      authorization,
19      auto_paginate,
20      service,
21    } = options;
22
23    Self {
24      agent: agent.into(),
25      authorization,
26      auto_paginate,
27      service,
28    }
29  }
30}
31
32pub struct Client<S>
33where
34  S: Service,
35{
36  pub agent: ureq::Agent,
37  pub authorization: Authorization<SSWS>,
38  pub auto_paginate: bool,
39  pub service: S,
40}
41
42use crate::Error;
43
44impl<S> Client<S>
45where
46  S: Service,
47{
48  pub fn uri<T>(&self, endpoint: &T) -> Result<Uri, Error>
49  where
50    T: Endpoint,
51  {
52    let base = self.service.uri()?;
53    match base.authority_str() {
54      Some(authority) => Uri::builder().authority(authority),
55      None => Uri::builder(),
56    }
57    .scheme(base.scheme_str())
58    .path_and_query(endpoint.uri()?.as_str())
59    .build()
60    .map_into()
61  }
62
63  pub fn request<T>(&self, endpoint: T) -> Result<Request<<T as IntoRequest>::Body>, Error>
64  where
65    T: Endpoint,
66  {
67    let uri = self.uri(&endpoint)?;
68    let mut request = Request::builder().version(T::VERSION).method(T::METHOD).uri(uri);
69
70    if let Some(headers) = request.headers_mut() {
71      headers.typed_insert(self.authorization.clone());
72      headers.typed_insert(endpoint.accept());
73      headers.typed_insert(endpoint.content_type());
74    }
75
76    let body = endpoint.as_ref().clone();
77    request.body(body).map_into()
78  }
79
80  pub fn callable<T>(&self, endpoint: T) -> Result<(ureq::Request, <T as IntoRequest>::Body), Error>
81  where
82    T: Endpoint,
83  {
84    let request = self.request(endpoint)?;
85    let (parts, body) = request.into_parts();
86    let request = ureq::Request::from(parts);
87    Ok((request, body))
88  }
89
90  pub fn call<T>(&self, endpoint: T) -> Result<<T as Response>::Body, Error>
91  where
92    T: Endpoint,
93  {
94    let (request, _body) = self.callable(endpoint)?;
95    let response = request.call()?;
96    let body = if response.status() == 204 {
97      serde_json::from_str("null")?
98    } else {
99      response.into_json()?
100    };
101
102    Ok(body)
103  }
104
105  pub fn paginate<T>(&self, endpoint: T) -> Result<Vec<<T as Pagination>::Item>, Error>
106  where
107    T: Endpoint + Pagination,
108  {
109    let (original_request, _body) = self.callable(endpoint)?;
110    let mut request = original_request.clone();
111    let mut items = Vec::new();
112
113    let mut done = false;
114
115    while !done {
116      eprintln!("request: {}", request.url());
117      let response = request.clone().call()?;
118
119      match response
120        .all(T::HEADER.as_str())
121        .into_iter()
122        .flat_map(parse_link_header::parse_with_rel)
123        .filter_map(|mut rel| rel.remove(T::REL))
124        .find_map(|mut link| link.queries.remove(T::QUERY))
125      {
126        Some(value) => request = original_request.clone().query(T::QUERY, &value),
127        None => done = true,
128      }
129
130      let batch = response.into_json::<Vec<_>>()?;
131      items.extend_from_slice(&batch);
132    }
133
134    Ok(items)
135  }
136
137  pub fn autopaginate_json<T>(&self, endpoint: T, pretty: bool) -> Result<String, Error>
138  where
139    T: Endpoint + Pagination,
140  {
141    let value = if self.auto_paginate {
142      self.paginate(endpoint).map(serde_json::to_value)?
143    } else {
144      self.call(endpoint).map(serde_json::to_value)?
145    }?;
146
147    let json = if pretty {
148      serde_json::to_string_pretty(&value)?
149    } else {
150      serde_json::to_string(&value)?
151    };
152
153    Ok(json)
154  }
155
156  pub fn json<T>(&self, endpoint: T, pretty: bool) -> Result<String, Error>
157  where
158    T: Endpoint,
159  {
160    let value = self.call(endpoint).map(serde_json::to_value)??;
161
162    let json = if pretty {
163      serde_json::to_string_pretty(&value)?
164    } else {
165      serde_json::to_string(&value)?
166    };
167
168    Ok(json)
169  }
170}
171
172//   fn expected_headers() -> Vec<HeaderName> {
173//     vec![
174//       HeaderName::from_static("accept-ch"),
175//       HeaderName::from_static("p3p"),
176//       HeaderName::from_static("x-okta-request-id"),
177//       HeaderName::from_static("x-rate-limit-limit"),
178//       HeaderName::from_static("x-rate-limit-remaining"),
179//       HeaderName::from_static("x-rate-limit-reset"),
180//       http::header::ACCEPT_CHARSET,
181//       http::header::CACHE_CONTROL,
182//       http::header::CONNECTION,
183//       http::header::CONTENT_SECURITY_POLICY,
184//       http::header::CONTENT_TYPE,
185//       http::header::DATE,
186//       http::header::EXPIRES,
187//       http::header::LINK,
188//       http::header::PRAGMA,
189//       http::header::REFERRER_POLICY,
190//       http::header::SERVER,
191//       http::header::SET_COOKIE,
192//       http::header::STRICT_TRANSPORT_SECURITY,
193//       http::header::TRANSFER_ENCODING,
194//       http::header::VARY,
195//       http::header::X_CONTENT_TYPE_OPTIONS,
196//       http::header::X_XSS_PROTECTION,
197//     ]
198//   }
199// }