dfns_sdk_rs/utils/
fetch.rs1use crate::{
4 error::{DfnsError, PolicyPendingError},
5 models::generic::DfnsBaseApiOptions,
6 utils::nonce::generate_nonce,
7};
8use reqwest::{Client, Method, Response, StatusCode};
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12use url::Url;
13
14const DEFAULT_DFNS_BASE_URL: &str = "https://api.dfns.io";
15const VERSION: &str = env!("CARGO_PKG_VERSION");
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(rename_all = "UPPERCASE")]
19pub enum HttpMethod {
20 GET,
21 POST,
22 PUT,
23 DELETE,
24}
25
26impl From<HttpMethod> for Method {
27 fn from(method: HttpMethod) -> Self {
28 match method {
29 HttpMethod::GET => Method::GET,
30 HttpMethod::POST => Method::POST,
31 HttpMethod::PUT => Method::PUT,
32 HttpMethod::DELETE => Method::DELETE,
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
38pub struct FetchOptions<T> {
39 pub method: HttpMethod,
40 pub headers: Option<HashMap<String, String>>,
41 pub body: Option<Value>,
42 pub api_options: T,
43}
44
45pub type FetchResult = Result<Response, DfnsError>;
46
47pub trait Fetch {
48 #[allow(async_fn_in_trait)]
49 async fn execute(&self, url: &str, options: FetchOptions<DfnsBaseApiOptions>) -> FetchResult;
50}
51
52pub struct DfnsFetch {
53 client: Client,
54}
55
56impl Clone for DfnsFetch {
57 fn clone(&self) -> Self {
58 Self {
59 client: Client::new(),
60 }
61 }
62}
63
64impl std::fmt::Debug for DfnsFetch {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("DfnsFetch")
67 .field("client", &"Client")
68 .finish()
69 }
70}
71
72impl PartialEq for DfnsFetch {
73 fn eq(&self, _other: &Self) -> bool {
74 true
75 }
76}
77
78impl DfnsFetch {
79 pub fn new() -> Self {
80 Self {
81 client: Client::new(),
82 }
83 }
84
85 pub async fn handle_response(&self, response: Response) -> FetchResult {
86 if response.status().is_success() && response.status() != StatusCode::ACCEPTED {
87 Ok(response)
88 } else {
89 let status = response.status();
90 let body: Value = response.json().await.unwrap_or_default();
91
92 if status == StatusCode::ACCEPTED {
93 Err(PolicyPendingError::new(Some(body)).into())
94 } else {
95 let message = body
96 .get("error")
97 .and_then(|e| e.get("message"))
98 .or_else(|| body.get("message"))
99 .and_then(|m| m.as_str())
100 .unwrap_or("Unknown error")
101 .to_string();
102
103 Err(DfnsError::new(status.as_u16(), message, Some(body)))
104 }
105 }
106 }
107}
108
109impl Fetch for DfnsFetch {
110 async fn execute(
111 &self,
112 resource: &str,
113 options: FetchOptions<DfnsBaseApiOptions>,
114 ) -> FetchResult {
115 let base_url = options
116 .api_options
117 .base_url
118 .unwrap_or_else(|| DEFAULT_DFNS_BASE_URL.to_string());
119 let url = Url::parse(&base_url)?.join(resource)?;
120
121 let mut headers = reqwest::header::HeaderMap::new();
122
123 headers.insert("x-dfns-appid", options.api_options.app_id.parse()?);
124 headers.insert("x-dfns-nonce", generate_nonce().parse()?);
125 headers.insert("x-dfns-sdk-version", VERSION.parse()?);
126
127 if let Some(app_secret) = options.api_options.app_secret {
128 headers.insert("x-dfns-appsecret", app_secret.parse()?);
129 }
130
131 if let Some(auth_token) = options.api_options.auth_token {
132 headers.insert("authorization", format!("Bearer {}", auth_token).parse()?);
133 }
134
135 if let Some(custom_headers) = options.headers {
136 for (key, value) in custom_headers {
137 let key_str: &'static str = Box::leak(key.into_boxed_str());
138 headers.insert(key_str, value.parse()?);
139 }
140 }
141
142 let mut request = self
143 .client
144 .request(options.method.into(), url)
145 .headers(headers);
146
147 if let Some(body) = options.body {
148 request = request
149 .header("content-type", "application/json")
150 .json(&body);
151 }
152
153 request.send().await.map_err(|e| e.into())
154 }
155}
156
157pub async fn simple_fetch<T: Serialize + DeserializeOwned>(
158 resource: &str,
159 options: FetchOptions<DfnsBaseApiOptions>,
160) -> Result<T, DfnsError> {
161 let fetch = DfnsFetch::new();
162 let response = fetch.execute(resource, options).await?;
163 response.json::<T>().await.map_err(|e| e.into())
164}