1use std::collections::HashSet;
5use std::sync::Arc;
6use std::time::Duration;
7
8use futures_util::StreamExt;
9use reqwest::header::{ACCEPT, CONTENT_TYPE};
10use reqwest::multipart::{Form, Part};
11use reqwest::{Method, Response};
12use serde::Serialize;
13use serde_json::{json, Value};
14
15use crate::streaming::{
16 extract_run_result, iter_stream, wait_for_call_result, wait_for_run, wait_for_run_result,
17 wait_for_task, EventStream, StreamEventExt,
18};
19use crate::transport::{data_object, decode_data_response, enc, enc_path, Transport};
20use crate::{Error, JsonObject, Result};
21
22pub struct SubscribeWithEventOptions {
24 pub project: Option<String>,
26 pub reconnect: bool,
28 pub max_attempts: u32,
30}
31
32impl Default for SubscribeWithEventOptions {
33 fn default() -> Self {
34 SubscribeWithEventOptions {
35 project: None,
36 reconnect: true,
37 max_attempts: 5,
38 }
39 }
40}
41
42pub struct WaitOptions<'a> {
44 pub timeout: Duration,
46 pub on_chunk: Option<&'a mut (dyn FnMut(&str) + Send)>,
48}
49
50impl Default for WaitOptions<'_> {
51 fn default() -> Self {
52 WaitOptions {
53 timeout: Duration::from_secs(60),
54 on_chunk: None,
55 }
56 }
57}
58
59pub struct TaskWaitOptions {
61 pub timeout: Duration,
63 pub max_attempts: u32,
65}
66
67pub struct RunWaitOptions {
69 pub timeout: Duration,
71 pub max_attempts: u32,
73}
74
75impl Default for RunWaitOptions {
76 fn default() -> Self {
77 RunWaitOptions {
78 timeout: Duration::from_secs(60),
79 max_attempts: 5,
80 }
81 }
82}
83
84impl Default for TaskWaitOptions {
85 fn default() -> Self {
86 TaskWaitOptions {
87 timeout: Duration::from_secs(60),
88 max_attempts: 5,
89 }
90 }
91}
92
93pub struct CallOptions<'a> {
95 pub timeout: Duration,
97 pub on_chunk: Option<&'a mut (dyn FnMut(&str) + Send)>,
99}
100
101impl Default for CallOptions<'_> {
102 fn default() -> Self {
103 CallOptions {
104 timeout: Duration::from_secs(60),
105 on_chunk: None,
106 }
107 }
108}
109
110pub struct BuildUpload {
112 pub content: Vec<u8>,
114 pub filename: Option<String>,
116 pub hash: String,
118 pub build_id: Option<String>,
120}
121
122macro_rules! resource {
123 ($(#[$doc:meta])* $name:ident) => {
124 $(#[$doc])*
125 pub struct $name {
126 transport: Arc<Transport>,
127 }
128
129 impl $name {
130 pub(crate) fn new(transport: Arc<Transport>) -> Self {
131 Self { transport }
132 }
133 }
134 };
135}
136
137resource!(
138 EventsResource
140);
141resource!(
142 RunsResource
144);
145resource!(
146 EnvResource
148);
149resource!(
150 OrgResource
152);
153resource!(
154 StreamsResource
156);
157resource!(
158 TasksResource
160);
161resource!(
162 FilesResource
164);
165resource!(
166 ProjectsResource
168);
169resource!(
170 BuildsResource
172);
173resource!(
174 ContextResource
176);
177resource!(
178 DomainsResource
180);
181resource!(
182 SessionsResource
184);
185resource!(
186 ServiceKeysResource
188);
189
190impl EventsResource {
191 pub async fn publish(&self, body: impl Serialize) -> Result<JsonObject> {
194 let envelope = self
195 .transport
196 .request_json(
197 Method::POST,
198 "/events",
199 Some(&serde_json::to_value(body)?),
200 &[],
201 )
202 .await?;
203 Ok(data_object(envelope))
204 }
205
206 pub async fn call_hot(
209 &self,
210 fn_name: &str,
211 args: Vec<Value>,
212 opts: CallOptions<'_>,
213 ) -> Result<Value> {
214 let body = json!({
215 "event_type": "hot:call",
216 "event_data": { "fn": fn_name, "args": args },
217 });
218 let Value::Object(body) = body else {
219 unreachable!()
220 };
221 let run =
222 wait_for_call_result(self.transport.clone(), body, opts.timeout, opts.on_chunk).await?;
223 Ok(extract_run_result(run.get("result").cloned()))
224 }
225
226 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
228 self.transport
229 .request_json(Method::GET, "/events", None, query)
230 .await
231 }
232
233 pub async fn get(&self, event_id: &str) -> Result<JsonObject> {
235 let path = format!("/events/{}", enc(event_id));
236 let envelope = self
237 .transport
238 .request_json(Method::GET, &path, None, &[])
239 .await?;
240 Ok(data_object(envelope))
241 }
242
243 pub async fn get_runs(&self, event_id: &str) -> Result<JsonObject> {
245 let path = format!("/events/{}/runs", enc(event_id));
246 self.transport
247 .request_json(Method::GET, &path, None, &[])
248 .await
249 }
250}
251
252impl RunsResource {
253 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
255 self.transport
256 .request_json(Method::GET, "/runs", None, query)
257 .await
258 }
259
260 pub async fn stats(&self) -> Result<JsonObject> {
262 let envelope = self
263 .transport
264 .request_json(Method::GET, "/runs/stats", None, &[])
265 .await?;
266 Ok(data_object(envelope))
267 }
268
269 pub async fn get(&self, run_id: &str) -> Result<JsonObject> {
271 let path = format!("/runs/{}", enc(run_id));
272 let envelope = self
273 .transport
274 .request_json(Method::GET, &path, None, &[])
275 .await?;
276 Ok(data_object(envelope))
277 }
278
279 pub fn subscribe(&self, run_id: &str) -> EventStream {
281 iter_stream(
282 self.transport.clone(),
283 Method::GET,
284 format!("/runs/{}/subscribe", enc(run_id)),
285 None,
286 Vec::new(),
287 )
288 }
289
290 pub async fn wait(&self, run_id: &str, opts: RunWaitOptions) -> Result<JsonObject> {
293 wait_for_run(
294 self.transport.clone(),
295 run_id,
296 opts.timeout,
297 opts.max_attempts,
298 )
299 .await
300 }
301}
302
303impl EnvResource {
304 pub async fn get(&self) -> Result<JsonObject> {
306 let envelope = self
307 .transport
308 .request_json(Method::GET, "/env", None, &[])
309 .await?;
310 Ok(data_object(envelope))
311 }
312
313 pub fn subscribe(&self) -> EventStream {
316 iter_stream(
317 self.transport.clone(),
318 Method::GET,
319 "/env/subscribe".to_string(),
320 None,
321 Vec::new(),
322 )
323 }
324}
325
326impl OrgResource {
327 pub async fn usage(&self) -> Result<JsonObject> {
329 let envelope = self
330 .transport
331 .request_json(Method::GET, "/org/usage", None, &[])
332 .await?;
333 Ok(data_object(envelope))
334 }
335}
336
337impl StreamsResource {
338 pub fn subscribe(&self, stream_id: &str, project: Option<&str>) -> EventStream {
340 iter_stream(
341 self.transport.clone(),
342 Method::GET,
343 format!("/streams/{}/subscribe", enc(stream_id)),
344 None,
345 project_query(project),
346 )
347 }
348
349 pub fn subscribe_post(&self, stream_id: &str, project: Option<&str>) -> EventStream {
351 iter_stream(
352 self.transport.clone(),
353 Method::POST,
354 format!("/streams/{}/subscribe", enc(stream_id)),
355 None,
356 project_query(project),
357 )
358 }
359
360 pub async fn wait_for_run_result(
364 &self,
365 stream_id: &str,
366 event_id: &str,
367 opts: WaitOptions<'_>,
368 ) -> Result<JsonObject> {
369 wait_for_run_result(
370 self.transport.clone(),
371 stream_id,
372 event_id,
373 opts.timeout,
374 opts.on_chunk,
375 )
376 .await
377 }
378
379 pub fn subscribe_with_event(
389 &self,
390 body: impl Serialize,
391 opts: SubscribeWithEventOptions,
392 ) -> EventStream {
393 let body = match serde_json::to_value(body) {
394 Ok(body) => body,
395 Err(error) => {
396 return Box::pin(async_stream::stream! { yield Err(Error::Json(error)) });
397 }
398 };
399 let transport = self.transport.clone();
400 let query = project_query(opts.project.as_deref());
401 let once = move |transport: Arc<Transport>, query: Vec<(String, String)>| {
402 iter_stream(
403 transport,
404 Method::POST,
405 "/streams/subscribe-with-event".to_string(),
406 Some(body.clone()),
407 query,
408 )
409 };
410
411 if !opts.reconnect {
412 return once(transport, query);
413 }
414
415 Box::pin(async_stream::stream! {
416 let mut seen_start: HashSet<String> = HashSet::new();
417 let mut seen_terminal: HashSet<String> = HashSet::new();
418 let mut stream_id: Option<String> = None;
419 let mut event_id: Option<String> = None;
420 let mut attempts: u32 = 0;
421
422 loop {
423 let mut source = match &stream_id {
424 None => once(transport.clone(), query.clone()),
425 Some(id) => iter_stream(
426 transport.clone(),
427 Method::GET,
428 format!("/streams/{}/subscribe", enc(id)),
429 None,
430 query.clone(),
431 ),
432 };
433
434 let mut terminal = false;
435 while let Some(item) = source.next().await {
436 let event = match item {
437 Ok(event) => event,
438 Err(error) => {
439 if stream_id.is_none() || attempts >= opts.max_attempts {
440 yield Err(error);
441 return;
442 }
443 break;
444 }
445 };
446
447 match event.event_type() {
448 "event:published" => {
449 if let Some(published) =
450 event.get("stream_id").and_then(Value::as_str)
451 {
452 stream_id = Some(published.to_string());
453 }
454 if let Some(published) =
455 event.get("event_id").and_then(Value::as_str)
456 {
457 event_id = Some(published.to_string());
458 }
459 }
460 "run:start" => {
461 let run_id = event.run_id().map(str::to_string);
462 if let Some(run_id) = run_id {
463 if !seen_start.insert(run_id) {
464 continue;
465 }
466 }
467 }
468 "run:stop" | "run:fail" | "run:cancel" => {
469 let run_id = event.run_id().map(str::to_string);
470 if let Some(run_id) = run_id {
471 if !seen_terminal.insert(run_id) {
472 continue;
473 }
474 }
475 terminal = event_id.as_deref().is_some_and(|published| {
476 crate::streaming::event_id_of_run(event.run()) == Some(published)
477 });
478 }
479 _ => {}
480 }
481
482 yield Ok(event);
483 if terminal {
484 return;
485 }
486 }
487
488 if terminal {
489 return;
490 }
491 if stream_id.is_none() {
492 yield Err(Error::Protocol(
493 "stream ended before event:published was received".to_string(),
494 ));
495 return;
496 }
497 if attempts >= opts.max_attempts {
498 yield Err(Error::Protocol(
499 "stream ended before the published event's run completed".to_string(),
500 ));
501 return;
502 }
503
504 attempts += 1;
505 let delay = Duration::from_millis((250 * u64::from(attempts)).min(2000));
506 tokio::time::sleep(delay).await;
507 }
508 })
509 }
510}
511
512impl TasksResource {
513 pub async fn get(&self, task_id: &str) -> Result<JsonObject> {
515 let path = format!("/tasks/{}", enc(task_id));
516 let envelope = self
517 .transport
518 .request_json(Method::GET, &path, None, &[])
519 .await?;
520 Ok(data_object(envelope))
521 }
522
523 pub fn subscribe(&self, task_id: &str) -> EventStream {
525 iter_stream(
526 self.transport.clone(),
527 Method::GET,
528 format!("/tasks/{}/subscribe", enc(task_id)),
529 None,
530 Vec::new(),
531 )
532 }
533
534 pub async fn wait(&self, task_id: &str, opts: TaskWaitOptions) -> Result<JsonObject> {
537 wait_for_task(
538 self.transport.clone(),
539 task_id,
540 opts.timeout,
541 opts.max_attempts,
542 )
543 .await
544 }
545}
546
547impl FilesResource {
548 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
550 self.transport
551 .request_json(Method::GET, "/files", None, query)
552 .await
553 }
554
555 pub async fn get(&self, file_id: &str) -> Result<JsonObject> {
557 let path = format!("/files/{}", enc(file_id));
558 let envelope = self
559 .transport
560 .request_json(Method::GET, &path, None, &[])
561 .await?;
562 Ok(data_object(envelope))
563 }
564
565 pub async fn delete(&self, file_id: &str) -> Result<()> {
567 let path = format!("/files/{}", enc(file_id));
568 let builder = self.transport.request_builder(Method::DELETE, &path);
569 self.transport.execute(builder).await?;
570 Ok(())
571 }
572
573 pub async fn download(&self, file_id: &str) -> Result<Response> {
575 let path = format!("/files/{}/download", enc(file_id));
576 let builder = self.transport.request_builder(Method::GET, &path);
577 self.transport.execute(builder).await
578 }
579
580 pub async fn upload(
582 &self,
583 path: &str,
584 content: Vec<u8>,
585 content_type: Option<&str>,
586 ) -> Result<JsonObject> {
587 let request_path = format!("/files/upload/{}", enc_path(path));
588 let mut builder = self
589 .transport
590 .request_builder(Method::PUT, &request_path)
591 .header(ACCEPT, "application/json")
592 .body(content);
593 if let Some(content_type) = content_type {
594 builder = builder.header(CONTENT_TYPE, content_type);
595 }
596 let response = self.transport.execute(builder).await?;
597 decode_data_response(response).await
598 }
599
600 pub async fn initiate_upload(&self, body: impl Serialize) -> Result<JsonObject> {
602 let envelope = self
603 .transport
604 .request_json(
605 Method::POST,
606 "/files/uploads",
607 Some(&serde_json::to_value(body)?),
608 &[],
609 )
610 .await?;
611 Ok(data_object(envelope))
612 }
613
614 pub async fn upload_part(
616 &self,
617 upload_id: &str,
618 part_number: u32,
619 content: Vec<u8>,
620 ) -> Result<JsonObject> {
621 let path = format!("/files/uploads/{}/{}", enc(upload_id), part_number);
622 let builder = self
623 .transport
624 .request_builder(Method::PUT, &path)
625 .header(ACCEPT, "application/json")
626 .body(content);
627 let response = self.transport.execute(builder).await?;
628 decode_data_response(response).await
629 }
630
631 pub async fn complete_upload(&self, upload_id: &str) -> Result<JsonObject> {
633 let path = format!("/files/uploads/{}/complete", enc(upload_id));
634 let envelope = self
635 .transport
636 .request_json(Method::POST, &path, None, &[])
637 .await?;
638 Ok(data_object(envelope))
639 }
640
641 pub async fn abort_upload(&self, upload_id: &str) -> Result<()> {
643 let path = format!("/files/uploads/{}", enc(upload_id));
644 let builder = self.transport.request_builder(Method::DELETE, &path);
645 self.transport.execute(builder).await?;
646 Ok(())
647 }
648}
649
650impl ProjectsResource {
651 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
653 self.transport
654 .request_json(Method::GET, "/projects", None, query)
655 .await
656 }
657
658 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
660 let envelope = self
661 .transport
662 .request_json(
663 Method::POST,
664 "/projects",
665 Some(&serde_json::to_value(body)?),
666 &[],
667 )
668 .await?;
669 Ok(data_object(envelope))
670 }
671
672 pub async fn get(&self, project: &str) -> Result<JsonObject> {
674 let path = format!("/projects/{}", enc(project));
675 let envelope = self
676 .transport
677 .request_json(Method::GET, &path, None, &[])
678 .await?;
679 Ok(data_object(envelope))
680 }
681
682 pub async fn update(&self, project: &str, body: impl Serialize) -> Result<JsonObject> {
684 let path = format!("/projects/{}", enc(project));
685 let envelope = self
686 .transport
687 .request_json(
688 Method::PATCH,
689 &path,
690 Some(&serde_json::to_value(body)?),
691 &[],
692 )
693 .await?;
694 Ok(data_object(envelope))
695 }
696
697 pub async fn delete(&self, project: &str) -> Result<()> {
699 let path = format!("/projects/{}", enc(project));
700 let builder = self.transport.request_builder(Method::DELETE, &path);
701 self.transport.execute(builder).await?;
702 Ok(())
703 }
704
705 pub async fn activate(&self, project: &str) -> Result<JsonObject> {
707 let path = format!("/projects/{}/activate", enc(project));
708 let envelope = self
709 .transport
710 .request_json(Method::POST, &path, None, &[])
711 .await?;
712 Ok(data_object(envelope))
713 }
714
715 pub async fn deactivate(&self, project: &str) -> Result<JsonObject> {
717 let path = format!("/projects/{}/deactivate", enc(project));
718 let envelope = self
719 .transport
720 .request_json(Method::POST, &path, None, &[])
721 .await?;
722 Ok(data_object(envelope))
723 }
724
725 pub async fn event_handlers(&self, project: &str) -> Result<JsonObject> {
727 let path = format!("/projects/{}/event-handlers", enc(project));
728 self.transport
729 .request_json(Method::GET, &path, None, &[])
730 .await
731 }
732
733 pub async fn schedules(&self, project: &str) -> Result<JsonObject> {
735 let path = format!("/projects/{}/schedules", enc(project));
736 self.transport
737 .request_json(Method::GET, &path, None, &[])
738 .await
739 }
740}
741
742impl BuildsResource {
743 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
745 self.transport
746 .request_json(Method::GET, "/builds", None, query)
747 .await
748 }
749
750 pub async fn list_for_project(
752 &self,
753 project: &str,
754 query: &[(&str, &str)],
755 ) -> Result<JsonObject> {
756 let path = format!("/projects/{}/builds", enc(project));
757 self.transport
758 .request_json(Method::GET, &path, None, query)
759 .await
760 }
761
762 pub async fn get(&self, project: &str, build_id: &str) -> Result<JsonObject> {
764 let path = format!("/projects/{}/builds/{}", enc(project), enc(build_id));
765 let envelope = self
766 .transport
767 .request_json(Method::GET, &path, None, &[])
768 .await?;
769 Ok(data_object(envelope))
770 }
771
772 pub async fn deployed(&self, project: &str) -> Result<JsonObject> {
774 let path = format!("/projects/{}/builds/deployed", enc(project));
775 let envelope = self
776 .transport
777 .request_json(Method::GET, &path, None, &[])
778 .await?;
779 Ok(data_object(envelope))
780 }
781
782 pub async fn live(&self, project: &str) -> Result<JsonObject> {
784 let path = format!("/projects/{}/builds/live", enc(project));
785 let envelope = self
786 .transport
787 .request_json(Method::GET, &path, None, &[])
788 .await?;
789 Ok(data_object(envelope))
790 }
791
792 pub async fn upload(&self, project: &str, upload: BuildUpload) -> Result<JsonObject> {
794 let mut form = Form::new().text("hash", upload.hash);
795 if let Some(build_id) = upload.build_id {
796 form = form.text("build_id", build_id);
797 }
798 let part = Part::bytes(upload.content)
799 .file_name(upload.filename.unwrap_or_else(|| "build".to_string()));
800 form = form.part("file", part);
801
802 let path = format!("/projects/{}/builds", enc(project));
803 let builder = self
804 .transport
805 .request_builder(Method::POST, &path)
806 .header(ACCEPT, "application/json")
807 .multipart(form);
808 let response = self.transport.execute(builder).await?;
809 decode_data_response(response).await
810 }
811
812 pub async fn download(&self, project: &str, build_id: &str) -> Result<Response> {
814 let path = format!(
815 "/projects/{}/builds/{}/download",
816 enc(project),
817 enc(build_id)
818 );
819 let builder = self.transport.request_builder(Method::GET, &path);
820 self.transport.execute(builder).await
821 }
822
823 pub async fn deploy(&self, project: &str, build_id: &str) -> Result<JsonObject> {
825 let path = format!("/projects/{}/builds/{}/deploy", enc(project), enc(build_id));
826 let envelope = self
827 .transport
828 .request_json(Method::POST, &path, None, &[])
829 .await?;
830 Ok(data_object(envelope))
831 }
832}
833
834impl ContextResource {
835 pub async fn list(&self, project: &str) -> Result<JsonObject> {
837 let path = format!("/projects/{}/context", enc(project));
838 self.transport
839 .request_json(Method::GET, &path, None, &[])
840 .await
841 }
842
843 pub async fn create(&self, project: &str, body: impl Serialize) -> Result<JsonObject> {
845 let path = format!("/projects/{}/context", enc(project));
846 let envelope = self
847 .transport
848 .request_json(Method::POST, &path, Some(&serde_json::to_value(body)?), &[])
849 .await?;
850 Ok(data_object(envelope))
851 }
852
853 pub async fn update(
855 &self,
856 project: &str,
857 key: &str,
858 body: impl Serialize,
859 ) -> Result<JsonObject> {
860 let path = format!("/projects/{}/context/{}", enc(project), enc(key));
861 let envelope = self
862 .transport
863 .request_json(Method::PUT, &path, Some(&serde_json::to_value(body)?), &[])
864 .await?;
865 Ok(data_object(envelope))
866 }
867
868 pub async fn delete(&self, project: &str, key: &str) -> Result<()> {
870 let path = format!("/projects/{}/context/{}", enc(project), enc(key));
871 let builder = self.transport.request_builder(Method::DELETE, &path);
872 self.transport.execute(builder).await?;
873 Ok(())
874 }
875}
876
877impl DomainsResource {
878 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
880 let envelope = self
881 .transport
882 .request_json(
883 Method::POST,
884 "/domains",
885 Some(&serde_json::to_value(body)?),
886 &[],
887 )
888 .await?;
889 Ok(data_object(envelope))
890 }
891
892 pub async fn list(&self) -> Result<JsonObject> {
894 self.transport
895 .request_json(Method::GET, "/domains", None, &[])
896 .await
897 }
898
899 pub async fn get(&self, domain_id: &str) -> Result<JsonObject> {
901 let path = format!("/domains/{}", enc(domain_id));
902 let envelope = self
903 .transport
904 .request_json(Method::GET, &path, None, &[])
905 .await?;
906 Ok(data_object(envelope))
907 }
908
909 pub async fn delete(&self, domain_id: &str) -> Result<()> {
911 let path = format!("/domains/{}", enc(domain_id));
912 let builder = self.transport.request_builder(Method::DELETE, &path);
913 self.transport.execute(builder).await?;
914 Ok(())
915 }
916
917 pub async fn verify(&self, domain_id: &str) -> Result<JsonObject> {
919 let path = format!("/domains/{}/verify", enc(domain_id));
920 let envelope = self
921 .transport
922 .request_json(Method::POST, &path, None, &[])
923 .await?;
924 Ok(data_object(envelope))
925 }
926}
927
928impl SessionsResource {
929 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
931 let envelope = self
932 .transport
933 .request_json(
934 Method::POST,
935 "/sessions",
936 Some(&serde_json::to_value(body)?),
937 &[],
938 )
939 .await?;
940 Ok(data_object(envelope))
941 }
942
943 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
945 self.transport
946 .request_json(Method::GET, "/sessions", None, query)
947 .await
948 }
949
950 pub async fn revoke(&self, session_id: &str) -> Result<()> {
952 let path = format!("/sessions/{}", enc(session_id));
953 let builder = self.transport.request_builder(Method::DELETE, &path);
954 self.transport.execute(builder).await?;
955 Ok(())
956 }
957
958 pub async fn revoke_all(&self) -> Result<JsonObject> {
960 let envelope = self
961 .transport
962 .request_json(Method::DELETE, "/sessions", None, &[])
963 .await?;
964 Ok(data_object(envelope))
965 }
966}
967
968impl ServiceKeysResource {
969 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
971 let envelope = self
972 .transport
973 .request_json(
974 Method::POST,
975 "/service-keys",
976 Some(&serde_json::to_value(body)?),
977 &[],
978 )
979 .await?;
980 Ok(data_object(envelope))
981 }
982
983 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
985 self.transport
986 .request_json(Method::GET, "/service-keys", None, query)
987 .await
988 }
989
990 pub async fn get(&self, service_key_id: &str) -> Result<JsonObject> {
992 let path = format!("/service-keys/{}", enc(service_key_id));
993 let envelope = self
994 .transport
995 .request_json(Method::GET, &path, None, &[])
996 .await?;
997 Ok(data_object(envelope))
998 }
999
1000 pub async fn update(&self, service_key_id: &str, body: impl Serialize) -> Result<JsonObject> {
1002 let path = format!("/service-keys/{}", enc(service_key_id));
1003 let envelope = self
1004 .transport
1005 .request_json(
1006 Method::PATCH,
1007 &path,
1008 Some(&serde_json::to_value(body)?),
1009 &[],
1010 )
1011 .await?;
1012 Ok(data_object(envelope))
1013 }
1014
1015 pub async fn revoke(&self, service_key_id: &str) -> Result<()> {
1017 let path = format!("/service-keys/{}", enc(service_key_id));
1018 let builder = self.transport.request_builder(Method::DELETE, &path);
1019 self.transport.execute(builder).await?;
1020 Ok(())
1021 }
1022
1023 pub async fn revoke_all(&self) -> Result<JsonObject> {
1025 let envelope = self
1026 .transport
1027 .request_json(Method::DELETE, "/service-keys", None, &[])
1028 .await?;
1029 Ok(data_object(envelope))
1030 }
1031}
1032
1033fn project_query(project: Option<&str>) -> Vec<(String, String)> {
1034 match project {
1035 Some(project) => vec![("project".to_string(), project.to_string())],
1036 None => Vec::new(),
1037 }
1038}