Skip to main content

dingtalk_sdk/client/
async_client.rs

1use std::sync::Arc;
2
3use reqx::{advanced::PermissiveRetryEligibility, prelude::Client as HttpClient};
4use url::Url;
5
6use crate::{
7    api::{EnterpriseService, WebhookService},
8    auth::AppCredentials,
9    client::shared::{self, BuilderConfig, SharedClientState},
10    error::{Error, Result},
11};
12
13/// Builder for async [`Client`].
14#[derive(Debug, Clone, Default)]
15pub struct ClientBuilder {
16    config: BuilderConfig,
17}
18
19shared::impl_builder_methods!(ClientBuilder);
20
21impl ClientBuilder {
22    /// Builds an async [`Client`].
23    pub fn build(self) -> Result<Client> {
24        let base_urls = self.config.normalized_base_urls()?;
25        let webhook_http = self.build_http_client(&base_urls.webhook)?;
26        let enterprise_http = self.build_http_client(&base_urls.enterprise)?;
27
28        Ok(Client {
29            inner: Arc::new(Inner {
30                webhook_http,
31                enterprise_http,
32                shared: SharedClientState::new(
33                    base_urls,
34                    self.config.cache_access_token,
35                    self.config.token_refresh_margin,
36                    self.config.body_snippet,
37                ),
38            }),
39        })
40    }
41
42    fn build_http_client(&self, base_url: &Url) -> Result<HttpClient> {
43        let mut builder = HttpClient::builder(base_url.as_str())
44            .profile(self.config.profile)
45            .client_name(self.config.client_name.clone())
46            .connect_timeout(self.config.connect_timeout);
47
48        if let Some(request_timeout) = self.config.request_timeout {
49            builder = builder.request_timeout(request_timeout);
50        }
51
52        if let Some(total_timeout) = self.config.total_timeout {
53            builder = builder.total_timeout(total_timeout);
54        }
55
56        if self.config.no_system_proxy {
57            builder = builder.no_proxy(["*"]);
58        }
59
60        if let Some(retry_policy) = &self.config.retry_policy {
61            builder = builder.retry_policy(retry_policy.clone());
62        }
63
64        if self.config.retry_non_idempotent {
65            builder = builder.retry_eligibility(Arc::new(PermissiveRetryEligibility));
66        }
67
68        for (name, value) in &self.config.default_headers {
69            builder = builder.try_default_header(name, value)?;
70        }
71
72        builder.build().map_err(Error::from)
73    }
74}
75
76#[derive(Clone)]
77/// Async DingTalk SDK client.
78pub struct Client {
79    inner: Arc<Inner>,
80}
81
82struct Inner {
83    webhook_http: HttpClient,
84    enterprise_http: HttpClient,
85    shared: SharedClientState,
86}
87
88impl Client {
89    /// Returns a new builder.
90    #[must_use]
91    pub fn builder() -> ClientBuilder {
92        ClientBuilder::new()
93    }
94
95    /// Builds a client using defaults.
96    pub fn new() -> Result<Self> {
97        Self::builder().build()
98    }
99
100    /// Creates a webhook robot service.
101    #[must_use]
102    pub fn webhook(&self, token: impl Into<String>, secret: Option<String>) -> WebhookService {
103        WebhookService::new(self.clone(), token, secret)
104    }
105
106    /// Creates an enterprise robot service.
107    #[must_use]
108    pub fn enterprise(
109        &self,
110        appkey: impl Into<String>,
111        appsecret: impl Into<String>,
112        robot_code: impl Into<String>,
113    ) -> EnterpriseService {
114        EnterpriseService::new(self.clone(), appkey, appsecret, robot_code)
115    }
116
117    pub(crate) fn webhook_http(&self) -> &HttpClient {
118        &self.inner.webhook_http
119    }
120
121    pub(crate) fn enterprise_http(&self) -> &HttpClient {
122        &self.inner.enterprise_http
123    }
124
125    pub(crate) fn webhook_base_url(&self) -> &Url {
126        self.inner.shared.webhook_base_url()
127    }
128
129    pub(crate) fn webhook_endpoint(&self, segments: &[&str]) -> Result<Url> {
130        self.inner.shared.webhook_endpoint(segments)
131    }
132
133    pub(crate) fn enterprise_endpoint(&self, segments: &[&str]) -> Result<Url> {
134        self.inner.shared.enterprise_endpoint(segments)
135    }
136
137    pub(crate) fn cached_access_token(&self, credentials: &AppCredentials) -> Option<String> {
138        self.inner.shared.cached_access_token(credentials)
139    }
140
141    pub(crate) fn store_access_token(
142        &self,
143        credentials: &AppCredentials,
144        token: String,
145        expires_in_seconds: Option<i64>,
146    ) {
147        self.inner
148            .shared
149            .store_access_token(credentials, token, expires_in_seconds);
150    }
151
152    pub(crate) fn body_snippet(&self) -> crate::transport::BodySnippetConfig {
153        self.inner.shared.body_snippet()
154    }
155}