1use std::future::Future;
7
8use reqwest::header::{HeaderMap, HeaderValue};
9use secrecy::ExposeSecret;
10use serde::Serialize;
11use serde::de::DeserializeOwned;
12use tracing::debug;
13use url::Url;
14use uuid::Uuid;
15
16use super::types;
17use crate::Error;
18
19#[derive(serde::Deserialize)]
22struct ErrorResponse {
23 #[serde(default)]
24 message: Option<String>,
25 #[serde(default)]
26 code: Option<String>,
27}
28
29pub struct IntegrationClient {
36 http: reqwest::Client,
37 base_url: Url,
38}
39
40impl IntegrationClient {
41 pub fn from_api_key(
49 base_url: &str,
50 api_key: &secrecy::SecretString,
51 transport: &crate::TransportConfig,
52 platform: crate::ControllerPlatform,
53 ) -> Result<Self, Error> {
54 let mut headers = HeaderMap::new();
55 let mut key_value =
56 HeaderValue::from_str(api_key.expose_secret()).map_err(|e| Error::Authentication {
57 message: format!("invalid API key header value: {e}"),
58 })?;
59 key_value.set_sensitive(true);
60 headers.insert("X-API-KEY", key_value);
61
62 let http = transport.build_client_with_headers(headers)?;
63 let base_url = Self::normalize_base_url(base_url, platform)?;
64
65 Ok(Self { http, base_url })
66 }
67
68 pub fn from_reqwest(
70 base_url: &str,
71 http: reqwest::Client,
72 platform: crate::ControllerPlatform,
73 ) -> Result<Self, Error> {
74 let base_url = Self::normalize_base_url(base_url, platform)?;
75 Ok(Self { http, base_url })
76 }
77
78 fn normalize_base_url(raw: &str, platform: crate::ControllerPlatform) -> Result<Url, Error> {
83 let mut url = Url::parse(raw)?;
84
85 let path = url.path().trim_end_matches('/').to_owned();
87
88 if path.ends_with("/integration") {
89 url.set_path(&format!("{path}/"));
90 } else {
91 let prefix = platform.integration_prefix();
92 url.set_path(&format!("{path}{prefix}/"));
93 }
94
95 Ok(url)
96 }
97
98 fn url(&self, path: &str) -> Url {
102 self.base_url
104 .join(path)
105 .expect("path should be valid relative URL")
106 }
107
108 async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
111 let url = self.url(path);
112 debug!("GET {url}");
113
114 let resp = self.http.get(url).send().await?;
115 self.handle_response(resp).await
116 }
117
118 async fn get_with_params<T: DeserializeOwned>(
119 &self,
120 path: &str,
121 params: &[(&str, String)],
122 ) -> Result<T, Error> {
123 let url = self.url(path);
124 debug!("GET {url} params={params:?}");
125
126 let resp = self.http.get(url).query(params).send().await?;
127 self.handle_response(resp).await
128 }
129
130 async fn post<T: DeserializeOwned, B: Serialize + Sync>(
131 &self,
132 path: &str,
133 body: &B,
134 ) -> Result<T, Error> {
135 let url = self.url(path);
136 debug!("POST {url}");
137
138 let resp = self.http.post(url).json(body).send().await?;
139 self.handle_response(resp).await
140 }
141
142 async fn post_no_response<B: Serialize + Sync>(
143 &self,
144 path: &str,
145 body: &B,
146 ) -> Result<(), Error> {
147 let url = self.url(path);
148 debug!("POST {url}");
149
150 let resp = self.http.post(url).json(body).send().await?;
151 self.handle_empty(resp).await
152 }
153
154 async fn put<T: DeserializeOwned, B: Serialize + Sync>(
155 &self,
156 path: &str,
157 body: &B,
158 ) -> Result<T, Error> {
159 let url = self.url(path);
160 debug!("PUT {url}");
161
162 let resp = self.http.put(url).json(body).send().await?;
163 self.handle_response(resp).await
164 }
165
166 async fn patch<T: DeserializeOwned, B: Serialize + Sync>(
167 &self,
168 path: &str,
169 body: &B,
170 ) -> Result<T, Error> {
171 let url = self.url(path);
172 debug!("PATCH {url}");
173
174 let resp = self.http.patch(url).json(body).send().await?;
175 self.handle_response(resp).await
176 }
177
178 async fn delete(&self, path: &str) -> Result<(), Error> {
179 let url = self.url(path);
180 debug!("DELETE {url}");
181
182 let resp = self.http.delete(url).send().await?;
183 self.handle_empty(resp).await
184 }
185
186 async fn delete_with_response<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
187 let url = self.url(path);
188 debug!("DELETE {url}");
189
190 let resp = self.http.delete(url).send().await?;
191 self.handle_response(resp).await
192 }
193
194 async fn delete_with_params<T: DeserializeOwned>(
195 &self,
196 path: &str,
197 params: &[(&str, String)],
198 ) -> Result<T, Error> {
199 let url = self.url(path);
200 debug!("DELETE {url} params={params:?}");
201
202 let resp = self.http.delete(url).query(params).send().await?;
203 self.handle_response(resp).await
204 }
205
206 async fn handle_response<T: DeserializeOwned>(
209 &self,
210 resp: reqwest::Response,
211 ) -> Result<T, Error> {
212 let status = resp.status();
213 if status.is_success() {
214 let body = resp.text().await?;
215 serde_json::from_str(&body).map_err(|e| {
216 let preview = &body[..body.len().min(200)];
217 Error::Deserialization {
218 message: format!("{e} (body preview: {preview:?})"),
219 body,
220 }
221 })
222 } else {
223 Err(self.parse_error(status, resp).await)
224 }
225 }
226
227 async fn handle_empty(&self, resp: reqwest::Response) -> Result<(), Error> {
228 let status = resp.status();
229 if status.is_success() {
230 Ok(())
231 } else {
232 Err(self.parse_error(status, resp).await)
233 }
234 }
235
236 async fn parse_error(&self, status: reqwest::StatusCode, resp: reqwest::Response) -> Error {
237 if status == reqwest::StatusCode::UNAUTHORIZED {
238 return Error::InvalidApiKey;
239 }
240
241 let raw = resp.text().await.unwrap_or_default();
242
243 if let Ok(err) = serde_json::from_str::<ErrorResponse>(&raw) {
244 Error::Integration {
245 status: status.as_u16(),
246 message: err.message.unwrap_or_else(|| status.to_string()),
247 code: err.code,
248 }
249 } else {
250 Error::Integration {
251 status: status.as_u16(),
252 message: if raw.is_empty() {
253 status.to_string()
254 } else {
255 raw
256 },
257 code: None,
258 }
259 }
260 }
261
262 pub async fn paginate_all<T, F, Fut>(&self, limit: i32, fetch: F) -> Result<Vec<T>, Error>
266 where
267 F: Fn(i64, i32) -> Fut,
268 Fut: Future<Output = Result<types::Page<T>, Error>>,
269 {
270 let mut all = Vec::new();
271 let mut offset: i64 = 0;
272
273 loop {
274 let page = fetch(offset, limit).await?;
275 let received = page.data.len();
276 all.extend(page.data);
277
278 let limit_usize = usize::try_from(limit).unwrap_or(0);
279 if received < limit_usize
280 || i64::try_from(all.len()).unwrap_or(i64::MAX) >= page.total_count
281 {
282 break;
283 }
284
285 offset += i64::try_from(received).unwrap_or(i64::MAX);
286 }
287
288 Ok(all)
289 }
290
291 pub async fn get_info(&self) -> Result<types::ApplicationInfoResponse, Error> {
296 self.get("v1/info").await
297 }
298
299 pub async fn list_sites(
302 &self,
303 offset: i64,
304 limit: i32,
305 ) -> Result<types::Page<types::SiteResponse>, Error> {
306 self.get_with_params(
307 "v1/sites",
308 &[("offset", offset.to_string()), ("limit", limit.to_string())],
309 )
310 .await
311 }
312
313 pub async fn list_devices(
316 &self,
317 site_id: &Uuid,
318 offset: i64,
319 limit: i32,
320 ) -> Result<types::Page<types::DeviceResponse>, Error> {
321 self.get_with_params(
322 &format!("v1/sites/{site_id}/devices"),
323 &[("offset", offset.to_string()), ("limit", limit.to_string())],
324 )
325 .await
326 }
327
328 pub async fn get_device(
329 &self,
330 site_id: &Uuid,
331 device_id: &Uuid,
332 ) -> Result<types::DeviceDetailsResponse, Error> {
333 self.get(&format!("v1/sites/{site_id}/devices/{device_id}"))
334 .await
335 }
336
337 pub async fn get_device_statistics(
338 &self,
339 site_id: &Uuid,
340 device_id: &Uuid,
341 ) -> Result<types::DeviceStatisticsResponse, Error> {
342 self.get(&format!(
343 "v1/sites/{site_id}/devices/{device_id}/statistics/latest"
344 ))
345 .await
346 }
347
348 pub async fn adopt_device(
349 &self,
350 site_id: &Uuid,
351 mac: &str,
352 ) -> Result<types::DeviceDetailsResponse, Error> {
353 #[derive(Serialize)]
354 #[serde(rename_all = "camelCase")]
355 struct Body<'a> {
356 mac_address: &'a str,
357 ignore_device_limit: bool,
358 }
359
360 self.post(
361 &format!("v1/sites/{site_id}/devices"),
362 &Body {
363 mac_address: mac,
364 ignore_device_limit: false,
365 },
366 )
367 .await
368 }
369
370 pub async fn remove_device(&self, site_id: &Uuid, device_id: &Uuid) -> Result<(), Error> {
371 self.delete(&format!("v1/sites/{site_id}/devices/{device_id}"))
372 .await
373 }
374
375 pub async fn device_action(
376 &self,
377 site_id: &Uuid,
378 device_id: &Uuid,
379 action: &str,
380 ) -> Result<(), Error> {
381 #[derive(Serialize)]
382 struct Body<'a> {
383 action: &'a str,
384 }
385
386 self.post_no_response(
387 &format!("v1/sites/{site_id}/devices/{device_id}/actions"),
388 &Body { action },
389 )
390 .await
391 }
392
393 pub async fn port_action(
394 &self,
395 site_id: &Uuid,
396 device_id: &Uuid,
397 port_idx: u32,
398 action: &str,
399 ) -> Result<(), Error> {
400 #[derive(Serialize)]
401 struct Body<'a> {
402 action: &'a str,
403 }
404
405 self.post_no_response(
406 &format!("v1/sites/{site_id}/devices/{device_id}/interfaces/ports/{port_idx}/actions"),
407 &Body { action },
408 )
409 .await
410 }
411
412 pub async fn list_pending_devices(
413 &self,
414 offset: i64,
415 limit: i32,
416 ) -> Result<types::Page<types::PendingDeviceResponse>, Error> {
417 self.get_with_params(
418 "v1/pending-devices",
419 &[("offset", offset.to_string()), ("limit", limit.to_string())],
420 )
421 .await
422 }
423
424 pub async fn list_device_tags(
425 &self,
426 site_id: &Uuid,
427 offset: i64,
428 limit: i32,
429 ) -> Result<types::Page<types::DeviceTagResponse>, Error> {
430 self.get_with_params(
431 &format!("v1/sites/{site_id}/device-tags"),
432 &[("offset", offset.to_string()), ("limit", limit.to_string())],
433 )
434 .await
435 }
436
437 pub async fn list_clients(
440 &self,
441 site_id: &Uuid,
442 offset: i64,
443 limit: i32,
444 ) -> Result<types::Page<types::ClientResponse>, Error> {
445 self.get_with_params(
446 &format!("v1/sites/{site_id}/clients"),
447 &[("offset", offset.to_string()), ("limit", limit.to_string())],
448 )
449 .await
450 }
451
452 pub async fn get_client(
453 &self,
454 site_id: &Uuid,
455 client_id: &Uuid,
456 ) -> Result<types::ClientDetailsResponse, Error> {
457 self.get(&format!("v1/sites/{site_id}/clients/{client_id}"))
458 .await
459 }
460
461 pub async fn client_action(
462 &self,
463 site_id: &Uuid,
464 client_id: &Uuid,
465 action: &str,
466 ) -> Result<types::ClientActionResponse, Error> {
467 #[derive(Serialize)]
468 struct Body<'a> {
469 action: &'a str,
470 }
471
472 self.post(
473 &format!("v1/sites/{site_id}/clients/{client_id}/actions"),
474 &Body { action },
475 )
476 .await
477 }
478
479 pub async fn list_networks(
482 &self,
483 site_id: &Uuid,
484 offset: i64,
485 limit: i32,
486 ) -> Result<types::Page<types::NetworkResponse>, Error> {
487 self.get_with_params(
488 &format!("v1/sites/{site_id}/networks"),
489 &[("offset", offset.to_string()), ("limit", limit.to_string())],
490 )
491 .await
492 }
493
494 pub async fn get_network(
495 &self,
496 site_id: &Uuid,
497 network_id: &Uuid,
498 ) -> Result<types::NetworkDetailsResponse, Error> {
499 self.get(&format!("v1/sites/{site_id}/networks/{network_id}"))
500 .await
501 }
502
503 pub async fn create_network(
504 &self,
505 site_id: &Uuid,
506 body: &types::NetworkCreateUpdate,
507 ) -> Result<types::NetworkDetailsResponse, Error> {
508 self.post(&format!("v1/sites/{site_id}/networks"), body)
509 .await
510 }
511
512 pub async fn update_network(
513 &self,
514 site_id: &Uuid,
515 network_id: &Uuid,
516 body: &types::NetworkCreateUpdate,
517 ) -> Result<types::NetworkDetailsResponse, Error> {
518 self.put(&format!("v1/sites/{site_id}/networks/{network_id}"), body)
519 .await
520 }
521
522 pub async fn delete_network(&self, site_id: &Uuid, network_id: &Uuid) -> Result<(), Error> {
523 self.delete(&format!("v1/sites/{site_id}/networks/{network_id}"))
524 .await
525 }
526
527 pub async fn get_network_references(
528 &self,
529 site_id: &Uuid,
530 network_id: &Uuid,
531 ) -> Result<types::NetworkReferencesResponse, Error> {
532 self.get(&format!(
533 "v1/sites/{site_id}/networks/{network_id}/references"
534 ))
535 .await
536 }
537
538 pub async fn list_wifi_broadcasts(
541 &self,
542 site_id: &Uuid,
543 offset: i64,
544 limit: i32,
545 ) -> Result<types::Page<types::WifiBroadcastResponse>, Error> {
546 self.get_with_params(
547 &format!("v1/sites/{site_id}/wifi/broadcasts"),
548 &[("offset", offset.to_string()), ("limit", limit.to_string())],
549 )
550 .await
551 }
552
553 pub async fn get_wifi_broadcast(
554 &self,
555 site_id: &Uuid,
556 broadcast_id: &Uuid,
557 ) -> Result<types::WifiBroadcastDetailsResponse, Error> {
558 self.get(&format!(
559 "v1/sites/{site_id}/wifi/broadcasts/{broadcast_id}"
560 ))
561 .await
562 }
563
564 pub async fn create_wifi_broadcast(
565 &self,
566 site_id: &Uuid,
567 body: &types::WifiBroadcastCreateUpdate,
568 ) -> Result<types::WifiBroadcastDetailsResponse, Error> {
569 self.post(&format!("v1/sites/{site_id}/wifi/broadcasts"), body)
570 .await
571 }
572
573 pub async fn update_wifi_broadcast(
574 &self,
575 site_id: &Uuid,
576 broadcast_id: &Uuid,
577 body: &types::WifiBroadcastCreateUpdate,
578 ) -> Result<types::WifiBroadcastDetailsResponse, Error> {
579 self.put(
580 &format!("v1/sites/{site_id}/wifi/broadcasts/{broadcast_id}"),
581 body,
582 )
583 .await
584 }
585
586 pub async fn delete_wifi_broadcast(
587 &self,
588 site_id: &Uuid,
589 broadcast_id: &Uuid,
590 ) -> Result<(), Error> {
591 self.delete(&format!(
592 "v1/sites/{site_id}/wifi/broadcasts/{broadcast_id}"
593 ))
594 .await
595 }
596
597 pub async fn list_firewall_policies(
600 &self,
601 site_id: &Uuid,
602 offset: i64,
603 limit: i32,
604 ) -> Result<types::Page<types::FirewallPolicyResponse>, Error> {
605 self.get_with_params(
606 &format!("v1/sites/{site_id}/firewall/policies"),
607 &[("offset", offset.to_string()), ("limit", limit.to_string())],
608 )
609 .await
610 }
611
612 pub async fn get_firewall_policy(
613 &self,
614 site_id: &Uuid,
615 policy_id: &Uuid,
616 ) -> Result<types::FirewallPolicyResponse, Error> {
617 self.get(&format!("v1/sites/{site_id}/firewall/policies/{policy_id}"))
618 .await
619 }
620
621 pub async fn create_firewall_policy(
622 &self,
623 site_id: &Uuid,
624 body: &types::FirewallPolicyCreateUpdate,
625 ) -> Result<types::FirewallPolicyResponse, Error> {
626 self.post(&format!("v1/sites/{site_id}/firewall/policies"), body)
627 .await
628 }
629
630 pub async fn update_firewall_policy(
631 &self,
632 site_id: &Uuid,
633 policy_id: &Uuid,
634 body: &types::FirewallPolicyCreateUpdate,
635 ) -> Result<types::FirewallPolicyResponse, Error> {
636 self.put(
637 &format!("v1/sites/{site_id}/firewall/policies/{policy_id}"),
638 body,
639 )
640 .await
641 }
642
643 pub async fn patch_firewall_policy(
644 &self,
645 site_id: &Uuid,
646 policy_id: &Uuid,
647 body: &types::FirewallPolicyPatch,
648 ) -> Result<types::FirewallPolicyResponse, Error> {
649 self.patch(
650 &format!("v1/sites/{site_id}/firewall/policies/{policy_id}"),
651 body,
652 )
653 .await
654 }
655
656 pub async fn delete_firewall_policy(
657 &self,
658 site_id: &Uuid,
659 policy_id: &Uuid,
660 ) -> Result<(), Error> {
661 self.delete(&format!("v1/sites/{site_id}/firewall/policies/{policy_id}"))
662 .await
663 }
664
665 pub async fn get_firewall_policy_ordering(
666 &self,
667 site_id: &Uuid,
668 ) -> Result<types::FirewallPolicyOrdering, Error> {
669 self.get(&format!("v1/sites/{site_id}/firewall/policies/ordering"))
670 .await
671 }
672
673 pub async fn set_firewall_policy_ordering(
674 &self,
675 site_id: &Uuid,
676 body: &types::FirewallPolicyOrdering,
677 ) -> Result<types::FirewallPolicyOrdering, Error> {
678 self.put(
679 &format!("v1/sites/{site_id}/firewall/policies/ordering"),
680 body,
681 )
682 .await
683 }
684
685 pub async fn list_firewall_zones(
688 &self,
689 site_id: &Uuid,
690 offset: i64,
691 limit: i32,
692 ) -> Result<types::Page<types::FirewallZoneResponse>, Error> {
693 self.get_with_params(
694 &format!("v1/sites/{site_id}/firewall/zones"),
695 &[("offset", offset.to_string()), ("limit", limit.to_string())],
696 )
697 .await
698 }
699
700 pub async fn get_firewall_zone(
701 &self,
702 site_id: &Uuid,
703 zone_id: &Uuid,
704 ) -> Result<types::FirewallZoneResponse, Error> {
705 self.get(&format!("v1/sites/{site_id}/firewall/zones/{zone_id}"))
706 .await
707 }
708
709 pub async fn create_firewall_zone(
710 &self,
711 site_id: &Uuid,
712 body: &types::FirewallZoneCreateUpdate,
713 ) -> Result<types::FirewallZoneResponse, Error> {
714 self.post(&format!("v1/sites/{site_id}/firewall/zones"), body)
715 .await
716 }
717
718 pub async fn update_firewall_zone(
719 &self,
720 site_id: &Uuid,
721 zone_id: &Uuid,
722 body: &types::FirewallZoneCreateUpdate,
723 ) -> Result<types::FirewallZoneResponse, Error> {
724 self.put(
725 &format!("v1/sites/{site_id}/firewall/zones/{zone_id}"),
726 body,
727 )
728 .await
729 }
730
731 pub async fn delete_firewall_zone(&self, site_id: &Uuid, zone_id: &Uuid) -> Result<(), Error> {
732 self.delete(&format!("v1/sites/{site_id}/firewall/zones/{zone_id}"))
733 .await
734 }
735
736 pub async fn list_acl_rules(
739 &self,
740 site_id: &Uuid,
741 offset: i64,
742 limit: i32,
743 ) -> Result<types::Page<types::AclRuleResponse>, Error> {
744 self.get_with_params(
745 &format!("v1/sites/{site_id}/acl-rules"),
746 &[("offset", offset.to_string()), ("limit", limit.to_string())],
747 )
748 .await
749 }
750
751 pub async fn get_acl_rule(
752 &self,
753 site_id: &Uuid,
754 rule_id: &Uuid,
755 ) -> Result<types::AclRuleResponse, Error> {
756 self.get(&format!("v1/sites/{site_id}/acl-rules/{rule_id}"))
757 .await
758 }
759
760 pub async fn create_acl_rule(
761 &self,
762 site_id: &Uuid,
763 body: &types::AclRuleCreateUpdate,
764 ) -> Result<types::AclRuleResponse, Error> {
765 self.post(&format!("v1/sites/{site_id}/acl-rules"), body)
766 .await
767 }
768
769 pub async fn update_acl_rule(
770 &self,
771 site_id: &Uuid,
772 rule_id: &Uuid,
773 body: &types::AclRuleCreateUpdate,
774 ) -> Result<types::AclRuleResponse, Error> {
775 self.put(&format!("v1/sites/{site_id}/acl-rules/{rule_id}"), body)
776 .await
777 }
778
779 pub async fn delete_acl_rule(&self, site_id: &Uuid, rule_id: &Uuid) -> Result<(), Error> {
780 self.delete(&format!("v1/sites/{site_id}/acl-rules/{rule_id}"))
781 .await
782 }
783
784 pub async fn get_acl_rule_ordering(
785 &self,
786 site_id: &Uuid,
787 ) -> Result<types::AclRuleOrdering, Error> {
788 self.get(&format!("v1/sites/{site_id}/acl-rules/ordering"))
789 .await
790 }
791
792 pub async fn set_acl_rule_ordering(
793 &self,
794 site_id: &Uuid,
795 body: &types::AclRuleOrdering,
796 ) -> Result<types::AclRuleOrdering, Error> {
797 self.put(&format!("v1/sites/{site_id}/acl-rules/ordering"), body)
798 .await
799 }
800
801 pub async fn list_dns_policies(
804 &self,
805 site_id: &Uuid,
806 offset: i64,
807 limit: i32,
808 ) -> Result<types::Page<types::DnsPolicyResponse>, Error> {
809 self.get_with_params(
810 &format!("v1/sites/{site_id}/dns/policies"),
811 &[("offset", offset.to_string()), ("limit", limit.to_string())],
812 )
813 .await
814 }
815
816 pub async fn get_dns_policy(
817 &self,
818 site_id: &Uuid,
819 dns_id: &Uuid,
820 ) -> Result<types::DnsPolicyResponse, Error> {
821 self.get(&format!("v1/sites/{site_id}/dns/policies/{dns_id}"))
822 .await
823 }
824
825 pub async fn create_dns_policy(
826 &self,
827 site_id: &Uuid,
828 body: &types::DnsPolicyCreateUpdate,
829 ) -> Result<types::DnsPolicyResponse, Error> {
830 self.post(&format!("v1/sites/{site_id}/dns/policies"), body)
831 .await
832 }
833
834 pub async fn update_dns_policy(
835 &self,
836 site_id: &Uuid,
837 dns_id: &Uuid,
838 body: &types::DnsPolicyCreateUpdate,
839 ) -> Result<types::DnsPolicyResponse, Error> {
840 self.put(&format!("v1/sites/{site_id}/dns/policies/{dns_id}"), body)
841 .await
842 }
843
844 pub async fn delete_dns_policy(&self, site_id: &Uuid, dns_id: &Uuid) -> Result<(), Error> {
845 self.delete(&format!("v1/sites/{site_id}/dns/policies/{dns_id}"))
846 .await
847 }
848
849 pub async fn list_traffic_matching_lists(
852 &self,
853 site_id: &Uuid,
854 offset: i64,
855 limit: i32,
856 ) -> Result<types::Page<types::TrafficMatchingListResponse>, Error> {
857 self.get_with_params(
858 &format!("v1/sites/{site_id}/traffic-matching-lists"),
859 &[("offset", offset.to_string()), ("limit", limit.to_string())],
860 )
861 .await
862 }
863
864 pub async fn get_traffic_matching_list(
865 &self,
866 site_id: &Uuid,
867 list_id: &Uuid,
868 ) -> Result<types::TrafficMatchingListResponse, Error> {
869 self.get(&format!(
870 "v1/sites/{site_id}/traffic-matching-lists/{list_id}"
871 ))
872 .await
873 }
874
875 pub async fn create_traffic_matching_list(
876 &self,
877 site_id: &Uuid,
878 body: &types::TrafficMatchingListCreateUpdate,
879 ) -> Result<types::TrafficMatchingListResponse, Error> {
880 self.post(&format!("v1/sites/{site_id}/traffic-matching-lists"), body)
881 .await
882 }
883
884 pub async fn update_traffic_matching_list(
885 &self,
886 site_id: &Uuid,
887 list_id: &Uuid,
888 body: &types::TrafficMatchingListCreateUpdate,
889 ) -> Result<types::TrafficMatchingListResponse, Error> {
890 self.put(
891 &format!("v1/sites/{site_id}/traffic-matching-lists/{list_id}"),
892 body,
893 )
894 .await
895 }
896
897 pub async fn delete_traffic_matching_list(
898 &self,
899 site_id: &Uuid,
900 list_id: &Uuid,
901 ) -> Result<(), Error> {
902 self.delete(&format!(
903 "v1/sites/{site_id}/traffic-matching-lists/{list_id}"
904 ))
905 .await
906 }
907
908 pub async fn list_vouchers(
911 &self,
912 site_id: &Uuid,
913 offset: i64,
914 limit: i32,
915 ) -> Result<types::Page<types::VoucherResponse>, Error> {
916 self.get_with_params(
917 &format!("v1/sites/{site_id}/hotspot/vouchers"),
918 &[("offset", offset.to_string()), ("limit", limit.to_string())],
919 )
920 .await
921 }
922
923 pub async fn get_voucher(
924 &self,
925 site_id: &Uuid,
926 voucher_id: &Uuid,
927 ) -> Result<types::VoucherResponse, Error> {
928 self.get(&format!("v1/sites/{site_id}/hotspot/vouchers/{voucher_id}"))
929 .await
930 }
931
932 pub async fn create_vouchers(
933 &self,
934 site_id: &Uuid,
935 body: &types::VoucherCreateRequest,
936 ) -> Result<Vec<types::VoucherResponse>, Error> {
937 self.post(&format!("v1/sites/{site_id}/hotspot/vouchers"), body)
938 .await
939 }
940
941 pub async fn delete_voucher(
942 &self,
943 site_id: &Uuid,
944 voucher_id: &Uuid,
945 ) -> Result<types::VoucherDeletionResults, Error> {
946 self.delete_with_response(&format!("v1/sites/{site_id}/hotspot/vouchers/{voucher_id}"))
947 .await
948 }
949
950 pub async fn purge_vouchers(
951 &self,
952 site_id: &Uuid,
953 filter: &str,
954 ) -> Result<types::VoucherDeletionResults, Error> {
955 self.delete_with_params(
956 &format!("v1/sites/{site_id}/hotspot/vouchers"),
957 &[("filter", filter.to_owned())],
958 )
959 .await
960 }
961
962 pub async fn list_vpn_servers(
965 &self,
966 site_id: &Uuid,
967 offset: i64,
968 limit: i32,
969 ) -> Result<types::Page<types::VpnServerResponse>, Error> {
970 self.get_with_params(
971 &format!("v1/sites/{site_id}/vpn/servers"),
972 &[("offset", offset.to_string()), ("limit", limit.to_string())],
973 )
974 .await
975 }
976
977 pub async fn list_vpn_tunnels(
978 &self,
979 site_id: &Uuid,
980 offset: i64,
981 limit: i32,
982 ) -> Result<types::Page<types::VpnTunnelResponse>, Error> {
983 self.get_with_params(
984 &format!("v1/sites/{site_id}/vpn/tunnels"),
985 &[("offset", offset.to_string()), ("limit", limit.to_string())],
986 )
987 .await
988 }
989
990 pub async fn list_wans(
993 &self,
994 site_id: &Uuid,
995 offset: i64,
996 limit: i32,
997 ) -> Result<types::Page<types::WanResponse>, Error> {
998 self.get_with_params(
999 &format!("v1/sites/{site_id}/wans"),
1000 &[("offset", offset.to_string()), ("limit", limit.to_string())],
1001 )
1002 .await
1003 }
1004
1005 pub async fn list_dpi_categories(
1008 &self,
1009 site_id: &Uuid,
1010 offset: i64,
1011 limit: i32,
1012 ) -> Result<types::Page<types::DpiCategoryResponse>, Error> {
1013 self.get_with_params(
1014 &format!("v1/sites/{site_id}/dpi/categories"),
1015 &[("offset", offset.to_string()), ("limit", limit.to_string())],
1016 )
1017 .await
1018 }
1019
1020 pub async fn list_dpi_applications(
1021 &self,
1022 site_id: &Uuid,
1023 offset: i64,
1024 limit: i32,
1025 ) -> Result<types::Page<types::DpiApplicationResponse>, Error> {
1026 self.get_with_params(
1027 &format!("v1/sites/{site_id}/dpi/applications"),
1028 &[("offset", offset.to_string()), ("limit", limit.to_string())],
1029 )
1030 .await
1031 }
1032
1033 pub async fn list_radius_profiles(
1036 &self,
1037 site_id: &Uuid,
1038 offset: i64,
1039 limit: i32,
1040 ) -> Result<types::Page<types::RadiusProfileResponse>, Error> {
1041 self.get_with_params(
1042 &format!("v1/sites/{site_id}/radius/profiles"),
1043 &[("offset", offset.to_string()), ("limit", limit.to_string())],
1044 )
1045 .await
1046 }
1047
1048 pub async fn list_countries(
1051 &self,
1052 offset: i64,
1053 limit: i32,
1054 ) -> Result<types::Page<types::CountryResponse>, Error> {
1055 self.get_with_params(
1056 "v1/countries",
1057 &[("offset", offset.to_string()), ("limit", limit.to_string())],
1058 )
1059 .await
1060 }
1061}