lit_sdk/
common.rs

1use crate::{EncryptedPayload, SdkError, SdkResult};
2use futures::{AsyncWrite, AsyncWriteExt, StreamExt};
3use lit_node_core::NodeSet;
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use std::collections::HashMap;
7use std::fmt;
8use std::fmt::{Display, Formatter};
9use std::marker::PhantomData;
10use std::str::FromStr;
11use uuid::Uuid;
12
13/// The allowed http prefix
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
15pub enum UrlPrefix {
16    /// http
17    Http,
18    #[default]
19    /// https
20    Https,
21}
22
23impl Display for UrlPrefix {
24    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
25        match self {
26            Self::Http => write!(f, "http"),
27            Self::Https => write!(f, "https"),
28        }
29    }
30}
31
32impl FromStr for UrlPrefix {
33    type Err = SdkError;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        match s.to_lowercase().as_str() {
37            "http" => Ok(Self::Http),
38            "https" => Ok(Self::Https),
39            _ => Err(SdkError::Parse(format!(
40                "invalid url prefix '{}'. Expected 'http' or 'https'",
41                s
42            ))),
43        }
44    }
45}
46
47/// A single request for a single endpoint
48#[derive(Clone, Debug)]
49pub struct EndpointRequest<T>
50where
51    T: Serialize + DeserializeOwned + Sync,
52{
53    /// The node set information
54    pub node_set: NodeSet,
55    /// The node's identity key
56    pub identity_key: NodeIdentityKey,
57    /// The request to send to this node set
58    pub body: T,
59}
60
61/// Node identity keys
62pub type NodeIdentityKey = [u8; 32];
63
64/// Admin endpoint requests
65#[derive(Clone, Debug)]
66pub struct AdminRequest<B, T, R>
67where
68    B: Sized + Default,
69    T: Serialize + DeserializeOwned,
70    R: Serialize + DeserializeOwned,
71{
72    pub(crate) url_prefix: UrlPrefix,
73    pub(crate) api_path: &'static str,
74    pub(crate) custom_headers: HashMap<String, String>,
75    pub(crate) public_address: String,
76    pub(crate) inner: T,
77    pub(crate) _builder: PhantomData<B>,
78    pub(crate) _response: PhantomData<R>,
79}
80
81impl<B, T, R> AdminRequest<B, T, R>
82where
83    B: Sized + Default,
84    T: Serialize + DeserializeOwned,
85    R: Serialize + DeserializeOwned,
86{
87    /// Create a new request builder
88    #[allow(clippy::new_ret_no_self)]
89    pub fn new() -> B {
90        B::default()
91    }
92
93    /// The url prefix
94    pub fn url_prefix(&self) -> &UrlPrefix {
95        &self.url_prefix
96    }
97
98    /// The url suffix
99    pub fn url_suffix(&self) -> &'static str {
100        self.api_path
101    }
102
103    /// The public address to send the request
104    pub fn public_address(&self) -> &str {
105        &self.public_address
106    }
107
108    /// The custom headers to use
109    pub fn custom_headers(&self) -> &HashMap<String, String> {
110        &self.custom_headers
111    }
112
113    /// The request body
114    pub fn inner(&self) -> &T {
115        &self.inner
116    }
117
118    /// Send the request to the specific [`NodeSet`]
119    pub async fn send(&self) -> SdkResult<AdminResponse<R>> {
120        let response_output = crate::request(
121            self.url_prefix,
122            &self.public_address,
123            self.api_path,
124            "",
125            &self.custom_headers,
126            &self.inner,
127        )
128        .await?;
129        let out_headers = crate::extract_headers(&response_output)?;
130        let output = response_output.text().await?;
131        let response = serde_json::from_str(&output)?;
132        Ok(AdminResponse {
133            headers: out_headers,
134            results: response,
135        })
136    }
137
138    /// Write the output to a stream vs a string as returned by `send`
139    pub async fn download<W>(&self, mut output: W) -> SdkResult<AdminResponse<()>>
140    where
141        W: AsyncWrite + Unpin,
142    {
143        let response = crate::request(
144            self.url_prefix,
145            &self.public_address,
146            self.api_path,
147            "",
148            &self.custom_headers,
149            &self.inner,
150        )
151        .await?;
152        let out_headers = crate::extract_headers(&response)?;
153        let mut stream = response.bytes_stream();
154        while let Some(chunk) = stream.next().await {
155            let bytes = chunk?;
156            output.write_all(&bytes).await?;
157            output.flush().await?;
158        }
159        Ok(AdminResponse {
160            headers: out_headers,
161            results: (),
162        })
163    }
164}
165
166/// The response for an admin request
167#[derive(Clone, Debug)]
168pub struct AdminResponse<R>
169where
170    R: Serialize + DeserializeOwned,
171{
172    pub(crate) headers: HashMap<String, String>,
173    pub(crate) results: R,
174}
175
176impl<R> AdminResponse<R>
177where
178    R: Serialize + DeserializeOwned,
179{
180    /// The results from the admin request
181    pub fn results(&self) -> &R {
182        &self.results
183    }
184
185    /// The response headers from the admin request
186    pub fn headers(&self) -> &HashMap<String, String> {
187        &self.headers
188    }
189}
190
191/// The general request for all SDK requests
192#[derive(Clone, Debug)]
193pub struct Request<B, T, R>
194where
195    B: Sized + Default,
196    T: Serialize + DeserializeOwned,
197    R: Serialize + DeserializeOwned,
198{
199    pub(crate) url_prefix: UrlPrefix,
200    pub(crate) api_path: &'static str,
201    pub(crate) node_set: Vec<NodeSet>,
202    pub(crate) custom_headers: HashMap<String, String>,
203    pub(crate) inner: T,
204    pub(crate) request_id: Uuid,
205    pub(crate) _builder: PhantomData<B>,
206    pub(crate) _response: PhantomData<R>,
207}
208
209impl<B, T, R> Request<B, T, R>
210where
211    B: Sized + Default,
212    T: Serialize + DeserializeOwned,
213    R: Serialize + DeserializeOwned,
214{
215    /// Create a new request builder
216    #[allow(clippy::new_ret_no_self)]
217    pub fn new() -> B {
218        B::default()
219    }
220
221    /// The url prefix
222    pub fn url_prefix(&self) -> &UrlPrefix {
223        &self.url_prefix
224    }
225
226    /// The url suffix
227    pub fn url_suffix(&self) -> &'static str {
228        self.api_path
229    }
230
231    /// The node set
232    pub fn node_set(&self) -> &[NodeSet] {
233        &self.node_set
234    }
235
236    /// The custom headers to use
237    pub fn custom_headers(&self) -> &HashMap<String, String> {
238        &self.custom_headers
239    }
240
241    /// The request body
242    pub fn inner(&self) -> &T {
243        &self.inner
244    }
245
246    /// The request id
247    pub fn request_id(&self) -> &Uuid {
248        &self.request_id
249    }
250
251    /// Send the request to the [`NodeSet`]
252    pub async fn send(&self) -> SdkResult<Response<R>> {
253        let mut headers = Vec::with_capacity(self.node_set.len());
254        let mut responses = Vec::with_capacity(self.node_set.len());
255        let request_id = self.request_id.to_string();
256        let mut requests = Vec::with_capacity(self.node_set.len());
257
258        for node in &self.node_set {
259            requests.push(crate::request(
260                self.url_prefix,
261                &node.socket_address,
262                self.api_path,
263                &request_id,
264                &self.custom_headers,
265                &self.inner,
266            ));
267        }
268        let results = futures::future::join_all(requests).await;
269        for result in results {
270            let response_output = result?;
271            let out_headers = crate::extract_headers(&response_output)?;
272            headers.push(out_headers);
273            let output = response_output.text().await?;
274            responses.push(serde_json::from_str(&output)?);
275        }
276        Ok(Response {
277            headers,
278            results: responses,
279        })
280    }
281}
282
283/// The general encrypted request for SDK requests
284/// That sends the same request to each node
285#[derive(Clone, Debug)]
286pub struct EncryptedBroadcastRequest<B, T, R>
287where
288    B: Sized + Default,
289    T: Serialize + DeserializeOwned + Sync,
290    R: Serialize + DeserializeOwned + Sync,
291{
292    pub(crate) url_prefix: UrlPrefix,
293    pub(crate) api_path: &'static str,
294    pub(crate) node_set: HashMap<NodeSet, NodeIdentityKey>,
295    pub(crate) custom_headers: HashMap<String, String>,
296    pub(crate) inner: T,
297    pub(crate) request_id: Uuid,
298    pub(crate) _builder: PhantomData<B>,
299    pub(crate) _response: PhantomData<R>,
300}
301
302impl<B, T, R> EncryptedBroadcastRequest<B, T, R>
303where
304    B: Sized + Default,
305    T: Serialize + DeserializeOwned + Sync,
306    R: Serialize + DeserializeOwned + Sync,
307{
308    /// Create a new request builder
309    #[allow(clippy::new_ret_no_self)]
310    pub fn new() -> B {
311        B::default()
312    }
313
314    /// The url prefix
315    pub fn url_prefix(&self) -> &UrlPrefix {
316        &self.url_prefix
317    }
318
319    /// The url suffix
320    pub fn url_suffix(&self) -> &'static str {
321        self.api_path
322    }
323
324    /// The node set
325    pub fn node_set(&self) -> &HashMap<NodeSet, NodeIdentityKey> {
326        &self.node_set
327    }
328
329    /// The custom headers
330    pub fn custom_headers(&self) -> &HashMap<String, String> {
331        &self.custom_headers
332    }
333
334    /// The request body
335    pub fn inner(&self) -> &T {
336        &self.inner
337    }
338
339    /// The request id
340    pub fn request_id(&self) -> &Uuid {
341        &self.request_id
342    }
343
344    /// Send the request to the [`NodeSet`]
345    pub async fn send(&self, my_secret_key: &[u8; 32]) -> SdkResult<Response<R>> {
346        let mut headers = Vec::with_capacity(self.node_set.len());
347        let mut responses = Vec::with_capacity(self.node_set.len());
348        let body = serde_json::to_vec(&self.inner)?;
349        let request_id = self.request_id.to_string();
350        let mut requests = Vec::with_capacity(self.node_set.len());
351
352        for (node, key) in &self.node_set {
353            let payload = EncryptedPayload::<T>::encrypt(my_secret_key, key, &body);
354            requests.push(crate::request(
355                self.url_prefix,
356                &node.socket_address,
357                self.api_path,
358                &request_id,
359                &self.custom_headers,
360                payload,
361            ));
362        }
363        let results = futures::future::join_all(requests).await;
364        for result in results {
365            let response_output = result?;
366            let out_headers = crate::extract_headers(&response_output)?;
367            headers.push(out_headers);
368            let output = response_output.text().await?;
369            let des = serde_json::from_str::<EncryptedPayload<R>>(&output)?;
370            let (r, _) = des.json_decrypt(my_secret_key)?;
371            responses.push(r);
372        }
373
374        Ok(Response {
375            headers,
376            results: responses,
377        })
378    }
379}
380
381/// The general encrypted request for SDK requests
382/// That sends a different request to each node
383#[derive(Clone, Debug)]
384pub struct EncryptedMulticastRequest<B, T, R>
385where
386    B: Sized + Default,
387    T: Serialize + DeserializeOwned + Sync,
388    R: Serialize + DeserializeOwned + Sync,
389{
390    pub(crate) url_prefix: UrlPrefix,
391    pub(crate) api_path: &'static str,
392    pub(crate) node_set: Vec<EndpointRequest<T>>,
393    pub(crate) custom_headers: HashMap<String, String>,
394    pub(crate) request_id: Uuid,
395    pub(crate) _builder: PhantomData<B>,
396    pub(crate) _response: PhantomData<R>,
397}
398
399impl<B, T, R> EncryptedMulticastRequest<B, T, R>
400where
401    B: Sized + Default,
402    T: Serialize + DeserializeOwned + Sync,
403    R: Serialize + DeserializeOwned + Sync,
404{
405    /// Create a new request builder
406    #[allow(clippy::new_ret_no_self)]
407    pub fn new() -> B {
408        B::default()
409    }
410
411    /// The url prefix
412    pub fn url_prefix(&self) -> &UrlPrefix {
413        &self.url_prefix
414    }
415
416    /// The url suffix
417    pub fn url_suffix(&self) -> &'static str {
418        self.api_path
419    }
420
421    /// The node set and requests
422    pub fn node_set(&self) -> &[EndpointRequest<T>] {
423        &self.node_set
424    }
425
426    /// The custom headers
427    pub fn custom_headers(&self) -> &HashMap<String, String> {
428        &self.custom_headers
429    }
430
431    /// The request id
432    pub fn request_id(&self) -> &Uuid {
433        &self.request_id
434    }
435
436    /// Send a request to each node in [`NodeSet`]
437    pub async fn send(&self, my_secret_key: &[u8; 32]) -> SdkResult<Response<R>> {
438        let mut headers = Vec::with_capacity(self.node_set.len());
439        let mut responses = Vec::with_capacity(self.node_set.len());
440        let request_id = self.request_id.to_string();
441        let mut requests = Vec::with_capacity(self.node_set.len());
442
443        for endpoint in &self.node_set {
444            let body = serde_json::to_vec(&endpoint.body)?;
445            let payload =
446                EncryptedPayload::<T>::encrypt(my_secret_key, &endpoint.identity_key, &body);
447            requests.push(crate::request(
448                self.url_prefix,
449                &endpoint.node_set.socket_address,
450                self.api_path,
451                &request_id,
452                &self.custom_headers,
453                payload,
454            ));
455        }
456        let results = futures::future::join_all(requests).await;
457        for result in results {
458            let response_output = result?;
459            let out_headers = crate::extract_headers(&response_output)?;
460            headers.push(out_headers);
461            let output = response_output.text().await?;
462            let des = serde_json::from_str::<EncryptedPayload<R>>(&output)?;
463            let (r, _) = des.json_decrypt(my_secret_key)?;
464            responses.push(r);
465        }
466
467        Ok(Response {
468            headers,
469            results: responses,
470        })
471    }
472}
473
474/// The general response for all SDK responses
475#[derive(Clone, Debug)]
476pub struct Response<T>
477where
478    T: Serialize + DeserializeOwned,
479{
480    pub(crate) results: Vec<T>,
481    pub(crate) headers: Vec<HashMap<String, String>>,
482}
483
484impl<T> Response<T>
485where
486    T: Serialize + DeserializeOwned,
487{
488    /// The results from the SDK request
489    ///
490    /// The ordering is the same as the node_set
491    pub fn results(&self) -> &Vec<T> {
492        &self.results
493    }
494
495    /// The Response headers from the SDK request
496    ///
497    /// The ordering is the same as the node_set
498    pub fn headers(&self) -> &Vec<HashMap<String, String>> {
499        &self.headers
500    }
501}