xapi_shared/rest/
mod.rs

1use crate::{
2    ratelimiter::SharedRatelimiterTrait,
3    rest::{
4        error::{SharedRestClientErrorTrait, SharedRestError},
5        payload::SharedRestPayloadTrait,
6        response::SharedRestResponseTrait,
7    },
8    signer::SharedSignerTrait,
9};
10use http::Method;
11use reqwest::Url;
12use std::{num::NonZeroU32, sync::Arc};
13
14pub mod error;
15pub mod payload;
16pub mod response;
17
18#[async_trait::async_trait]
19pub trait SharedRestClientTrait<RateLimitType: Sync> {
20    fn get_client(&self) -> &reqwest::Client;
21    fn get_signer(&self) -> &dyn SharedSignerTrait;
22    fn get_ratelimiter(&self) -> Arc<dyn SharedRatelimiterTrait<RateLimitType> + Sync + Send>;
23
24    async fn call_with_no_payload<
25        ResponseType: SharedRestResponseTrait,
26        Err: SharedRestClientErrorTrait,
27    >(
28        &self,
29        limits: &[(RateLimitType, NonZeroU32)],
30        signed: bool,
31        method: Method,
32        url: Url,
33    ) -> Result<ResponseType, SharedRestError<Err>> {
34        self.call::<(), ResponseType, Err>(limits, signed, method, url, None)
35            .await
36    }
37
38    async fn call_with_no_response<
39        PayloadType: Sync + Send + SharedRestPayloadTrait,
40        Err: SharedRestClientErrorTrait,
41    >(
42        &self,
43        limits: &[(RateLimitType, NonZeroU32)],
44        signed: bool,
45        method: Method,
46        url: Url,
47        payload: Option<&PayloadType>,
48    ) -> Result<(), SharedRestError<Err>> {
49        self.call::<PayloadType, (), Err>(limits, signed, method, url, payload)
50            .await
51    }
52
53    async fn call_with_no_payload_and_response<Err: SharedRestClientErrorTrait>(
54        &self,
55        limits: &[(RateLimitType, NonZeroU32)],
56        signed: bool,
57        method: Method,
58        url: Url,
59    ) -> Result<(), SharedRestError<Err>> {
60        self.call::<(), (), Err>(limits, signed, method, url, None)
61            .await
62    }
63
64    async fn call<
65        PayloadType: Sync + Send + SharedRestPayloadTrait,
66        ResponseType: SharedRestResponseTrait,
67        Err: SharedRestClientErrorTrait,
68    >(
69        &self,
70        limits: &[(RateLimitType, NonZeroU32)],
71        signed: bool,
72        method: Method,
73        url: Url,
74        payload: Option<&PayloadType>,
75    ) -> Result<ResponseType, SharedRestError<Err>> {
76        for (rate_limit_type, limit) in limits.iter() {
77            self.get_ratelimiter()
78                .limit_on(rate_limit_type, *limit)
79                .await?;
80        }
81
82        let mut request = self
83            .get_client()
84            .request(method, url)
85            .build()
86            .expect("failed to build request");
87
88        if let Some(payload) = payload {
89            payload.build_request(&mut request);
90        }
91
92        if signed {
93            self.get_signer().sign_request(&mut request);
94        }
95
96        let response = self.get_client().execute(request).await?;
97
98        let status = response.status();
99        let body = response
100            .text()
101            .await
102            .inspect_err(|err| tracing::error!("failed to get response body: {err}"))?;
103
104        if status.is_success() {
105            return ResponseType::from_body(&body);
106        }
107
108        if status.is_client_error() {
109            tracing::error!("client error: {status} - {body}");
110            return Err(SharedRestError::ClientError(Err::from_body(&body)));
111        }
112
113        if status.is_server_error() {
114            tracing::error!("server error: {status} - {body}");
115            return Err(SharedRestError::ServerError(body));
116        }
117
118        tracing::error!("uncaught response status: {status} - {body}");
119        panic!("uncaught response status: {status} - {body}");
120    }
121}