Skip to main content

ironflow_sdk/
provider_account_methods.rs

1//! Provider Account methods for [`IronflowClient`]. Admin only.
2//!
3//! No response carries the account credential.
4
5use ironflow_types::ApiResponse;
6use serde_json::json;
7
8use crate::client::IronflowClient;
9use crate::error::Error;
10use crate::types;
11
12/// Percent-encode one path segment (an account UUID or name).
13fn segment(raw: &str) -> String {
14    raw.bytes()
15        .map(|b| {
16            if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
17                (b as char).to_string()
18            } else {
19                format!("%{b:02X}")
20            }
21        })
22        .collect()
23}
24
25impl IronflowClient {
26    /// List Provider Accounts with their latest usage windows.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if the HTTP request fails or the response cannot be deserialized.
31    ///
32    /// # Examples
33    ///
34    /// ```no_run
35    /// use ironflow_sdk::IronflowClient;
36    ///
37    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
38    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
39    /// let accounts = client.list_provider_accounts().await?;
40    /// # Ok(())
41    /// # }
42    /// ```
43    pub async fn list_provider_accounts(
44        &self,
45    ) -> Result<ApiResponse<Vec<types::ProviderAccountResponse>>, Error> {
46        self.send_envelope(self.get("/api/v1/provider-accounts?per_page=100"))
47            .await
48    }
49
50    /// Add a Provider Account. The token is checked against the provider first.
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if the HTTP request fails, the token is rejected (422)
55    /// or the name is taken (409).
56    ///
57    /// # Examples
58    ///
59    /// ```no_run
60    /// use ironflow_sdk::IronflowClient;
61    /// use ironflow_sdk::types::CreateProviderAccountRequest;
62    ///
63    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
64    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
65    /// let account = client.create_provider_account(&CreateProviderAccountRequest {
66    ///     name: "perso".to_string(),
67    ///     kind: "claude_subscription".to_string(),
68    ///     token: "sk-ant-oat01-...".to_string(),
69    ///     alert_threshold: None,
70    ///     display_name: None,
71    ///     enabled: None,
72    ///     expires_at: None,
73    ///     max_concurrency: None,
74    ///     plan: None,
75    ///     priority: None,
76    ///     tags: None,
77    /// }).await?;
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub async fn create_provider_account(
82        &self,
83        request: &types::CreateProviderAccountRequest,
84    ) -> Result<ApiResponse<types::ProviderAccountResponse>, Error> {
85        self.send_envelope(self.post("/api/v1/provider-accounts").json(request))
86            .await
87    }
88
89    /// Get a Provider Account by UUID or name.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the HTTP request fails or the account does not exist.
94    ///
95    /// # Examples
96    ///
97    /// ```no_run
98    /// use ironflow_sdk::IronflowClient;
99    ///
100    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
101    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
102    /// let account = client.get_provider_account("perso").await?;
103    /// # Ok(())
104    /// # }
105    /// ```
106    pub async fn get_provider_account(
107        &self,
108        account: &str,
109    ) -> Result<ApiResponse<types::ProviderAccountResponse>, Error> {
110        self.send_envelope(self.get(&format!("/api/v1/provider-accounts/{}", segment(account))))
111            .await
112    }
113
114    /// Update a Provider Account by UUID or name.
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the HTTP request fails, the account does not exist
119    /// or a new token is rejected.
120    ///
121    /// # Examples
122    ///
123    /// ```no_run
124    /// use ironflow_sdk::IronflowClient;
125    /// use ironflow_sdk::types::UpdateProviderAccountRequest;
126    ///
127    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
128    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
129    /// let update = UpdateProviderAccountRequest { enabled: Some(false), ..Default::default() };
130    /// client.update_provider_account("perso", &update).await?;
131    /// # Ok(())
132    /// # }
133    /// ```
134    pub async fn update_provider_account(
135        &self,
136        account: &str,
137        request: &types::UpdateProviderAccountRequest,
138    ) -> Result<ApiResponse<types::ProviderAccountResponse>, Error> {
139        self.send_envelope(
140            self.patch(&format!("/api/v1/provider-accounts/{}", segment(account)))
141                .json(request),
142        )
143        .await
144    }
145
146    /// Remove the `max_concurrency` limit of a Provider Account.
147    ///
148    /// [`update_provider_account`](Self::update_provider_account) leaves an
149    /// absent field unchanged, so clearing needs an explicit `null`.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the HTTP request fails or the account does not exist.
154    ///
155    /// # Examples
156    ///
157    /// ```no_run
158    /// use ironflow_sdk::IronflowClient;
159    ///
160    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
161    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
162    /// client.clear_provider_account_max_concurrency("perso").await?;
163    /// # Ok(())
164    /// # }
165    /// ```
166    pub async fn clear_provider_account_max_concurrency(
167        &self,
168        account: &str,
169    ) -> Result<ApiResponse<types::ProviderAccountResponse>, Error> {
170        self.send_envelope(
171            self.patch(&format!("/api/v1/provider-accounts/{}", segment(account)))
172                .json(&json!({ "max_concurrency": null })),
173        )
174        .await
175    }
176
177    /// Delete a Provider Account and its credential.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the HTTP request fails or the account does not exist.
182    ///
183    /// # Examples
184    ///
185    /// ```no_run
186    /// use ironflow_sdk::IronflowClient;
187    ///
188    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
189    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
190    /// client.delete_provider_account("perso").await?;
191    /// # Ok(())
192    /// # }
193    /// ```
194    pub async fn delete_provider_account(&self, account: &str) -> Result<(), Error> {
195        self.send_no_content(
196            self.delete(&format!("/api/v1/provider-accounts/{}", segment(account))),
197        )
198        .await
199    }
200
201    /// Check the stored credential of a Provider Account against the provider.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if the HTTP request fails or the provider is unreachable.
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// use ironflow_sdk::IronflowClient;
211    ///
212    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
213    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
214    /// let outcome = client.test_provider_account("perso").await?;
215    /// # Ok(())
216    /// # }
217    /// ```
218    pub async fn test_provider_account(
219        &self,
220        account: &str,
221    ) -> Result<ApiResponse<types::ProviderAccountTestResponse>, Error> {
222        self.send_envelope(self.post(&format!(
223            "/api/v1/provider-accounts/{}/test",
224            segment(account)
225        )))
226        .await
227    }
228
229    /// Current windows and 30-day history of a Provider Account.
230    ///
231    /// # Errors
232    ///
233    /// Returns an error if the HTTP request fails or the account does not exist.
234    ///
235    /// # Examples
236    ///
237    /// ```no_run
238    /// use ironflow_sdk::IronflowClient;
239    ///
240    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
241    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
242    /// let usage = client.provider_account_usage("perso").await?;
243    /// # Ok(())
244    /// # }
245    /// ```
246    pub async fn provider_account_usage(
247        &self,
248        account: &str,
249    ) -> Result<ApiResponse<types::ProviderAccountUsageResponse>, Error> {
250        self.send_envelope(self.get(&format!(
251            "/api/v1/provider-accounts/{}/usage",
252            segment(account)
253        )))
254        .await
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::segment;
261
262    #[test]
263    fn segment_keeps_slugs_and_encodes_the_rest() {
264        assert_eq!(segment("perso-max"), "perso-max");
265        assert_eq!(
266            segment("0192f0c1-0000-7000-8000-000000000000"),
267            "0192f0c1-0000-7000-8000-000000000000"
268        );
269        assert_eq!(segment("a/b c"), "a%2Fb%20c");
270        assert_eq!(segment("../x"), "..%2Fx");
271    }
272}