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}