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_run_result, EventStream, StreamEventExt,
17};
18use crate::transport::{data_object, decode_data_response, enc, enc_path, Transport};
19use crate::{Error, JsonObject, Result};
20
21pub struct SubscribeWithEventOptions {
23 pub project: Option<String>,
25 pub reconnect: bool,
27 pub max_attempts: u32,
29}
30
31impl Default for SubscribeWithEventOptions {
32 fn default() -> Self {
33 SubscribeWithEventOptions {
34 project: None,
35 reconnect: true,
36 max_attempts: 5,
37 }
38 }
39}
40
41pub struct WaitOptions<'a> {
43 pub timeout: Duration,
45 pub on_chunk: Option<&'a mut (dyn FnMut(&str) + Send)>,
47}
48
49impl Default for WaitOptions<'_> {
50 fn default() -> Self {
51 WaitOptions {
52 timeout: Duration::from_secs(60),
53 on_chunk: None,
54 }
55 }
56}
57
58pub struct CallOptions<'a> {
60 pub timeout: Duration,
62 pub on_chunk: Option<&'a mut (dyn FnMut(&str) + Send)>,
64}
65
66impl Default for CallOptions<'_> {
67 fn default() -> Self {
68 CallOptions {
69 timeout: Duration::from_secs(60),
70 on_chunk: None,
71 }
72 }
73}
74
75pub struct BuildUpload {
77 pub content: Vec<u8>,
79 pub filename: Option<String>,
81 pub hash: String,
83 pub build_id: Option<String>,
85}
86
87macro_rules! resource {
88 ($(#[$doc:meta])* $name:ident) => {
89 $(#[$doc])*
90 pub struct $name {
91 transport: Arc<Transport>,
92 }
93
94 impl $name {
95 pub(crate) fn new(transport: Arc<Transport>) -> Self {
96 Self { transport }
97 }
98 }
99 };
100}
101
102resource!(
103 EventsResource
105);
106resource!(
107 RunsResource
109);
110resource!(
111 EnvResource
113);
114resource!(
115 OrgResource
117);
118resource!(
119 StreamsResource
121);
122resource!(
123 FilesResource
125);
126resource!(
127 ProjectsResource
129);
130resource!(
131 BuildsResource
133);
134resource!(
135 ContextResource
137);
138resource!(
139 DomainsResource
141);
142resource!(
143 SessionsResource
145);
146resource!(
147 ServiceKeysResource
149);
150
151impl EventsResource {
152 pub async fn publish(&self, body: impl Serialize) -> Result<JsonObject> {
155 let envelope = self
156 .transport
157 .request_json(
158 Method::POST,
159 "/events",
160 Some(&serde_json::to_value(body)?),
161 &[],
162 )
163 .await?;
164 Ok(data_object(envelope))
165 }
166
167 pub async fn call_hot(
170 &self,
171 fn_name: &str,
172 args: Vec<Value>,
173 opts: CallOptions<'_>,
174 ) -> Result<Value> {
175 let body = json!({
176 "event_type": "hot:call",
177 "event_data": { "fn": fn_name, "args": args },
178 });
179 let Value::Object(body) = body else {
180 unreachable!()
181 };
182 let published = self.publish(body).await?;
183
184 let stream_id = string_field(&published, "stream_id");
185 let event_id = string_field(&published, "event_id");
186 let run = wait_for_run_result(
187 self.transport.clone(),
188 &stream_id,
189 &event_id,
190 opts.timeout,
191 opts.on_chunk,
192 )
193 .await?;
194 Ok(extract_run_result(run.get("result").cloned()))
195 }
196
197 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
199 self.transport
200 .request_json(Method::GET, "/events", None, query)
201 .await
202 }
203
204 pub async fn get(&self, event_id: &str) -> Result<JsonObject> {
206 let path = format!("/events/{}", enc(event_id));
207 let envelope = self
208 .transport
209 .request_json(Method::GET, &path, None, &[])
210 .await?;
211 Ok(data_object(envelope))
212 }
213
214 pub async fn get_runs(&self, event_id: &str) -> Result<JsonObject> {
216 let path = format!("/events/{}/runs", enc(event_id));
217 self.transport
218 .request_json(Method::GET, &path, None, &[])
219 .await
220 }
221}
222
223impl RunsResource {
224 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
226 self.transport
227 .request_json(Method::GET, "/runs", None, query)
228 .await
229 }
230
231 pub async fn stats(&self) -> Result<JsonObject> {
233 let envelope = self
234 .transport
235 .request_json(Method::GET, "/runs/stats", None, &[])
236 .await?;
237 Ok(data_object(envelope))
238 }
239
240 pub async fn get(&self, run_id: &str) -> Result<JsonObject> {
242 let path = format!("/runs/{}", enc(run_id));
243 let envelope = self
244 .transport
245 .request_json(Method::GET, &path, None, &[])
246 .await?;
247 Ok(data_object(envelope))
248 }
249}
250
251impl EnvResource {
252 pub async fn get(&self) -> Result<JsonObject> {
254 let envelope = self
255 .transport
256 .request_json(Method::GET, "/env", None, &[])
257 .await?;
258 Ok(data_object(envelope))
259 }
260
261 pub fn subscribe(&self) -> EventStream {
264 iter_stream(
265 self.transport.clone(),
266 Method::GET,
267 "/env/subscribe".to_string(),
268 None,
269 Vec::new(),
270 )
271 }
272}
273
274impl OrgResource {
275 pub async fn usage(&self) -> Result<JsonObject> {
277 let envelope = self
278 .transport
279 .request_json(Method::GET, "/org/usage", None, &[])
280 .await?;
281 Ok(data_object(envelope))
282 }
283}
284
285impl StreamsResource {
286 pub fn subscribe(&self, stream_id: &str, project: Option<&str>) -> EventStream {
288 iter_stream(
289 self.transport.clone(),
290 Method::GET,
291 format!("/streams/{}/subscribe", enc(stream_id)),
292 None,
293 project_query(project),
294 )
295 }
296
297 pub fn subscribe_post(&self, stream_id: &str, project: Option<&str>) -> EventStream {
299 iter_stream(
300 self.transport.clone(),
301 Method::POST,
302 format!("/streams/{}/subscribe", enc(stream_id)),
303 None,
304 project_query(project),
305 )
306 }
307
308 pub async fn wait_for_run_result(
312 &self,
313 stream_id: &str,
314 event_id: &str,
315 opts: WaitOptions<'_>,
316 ) -> Result<JsonObject> {
317 wait_for_run_result(
318 self.transport.clone(),
319 stream_id,
320 event_id,
321 opts.timeout,
322 opts.on_chunk,
323 )
324 .await
325 }
326
327 pub fn subscribe_with_event(
337 &self,
338 body: impl Serialize,
339 opts: SubscribeWithEventOptions,
340 ) -> EventStream {
341 let body = match serde_json::to_value(body) {
342 Ok(body) => body,
343 Err(error) => {
344 return Box::pin(async_stream::stream! { yield Err(Error::Json(error)) });
345 }
346 };
347 let transport = self.transport.clone();
348 let query = project_query(opts.project.as_deref());
349 let once = move |transport: Arc<Transport>, query: Vec<(String, String)>| {
350 iter_stream(
351 transport,
352 Method::POST,
353 "/streams/subscribe-with-event".to_string(),
354 Some(body.clone()),
355 query,
356 )
357 };
358
359 if !opts.reconnect {
360 return once(transport, query);
361 }
362
363 Box::pin(async_stream::stream! {
364 let mut seen_start: HashSet<String> = HashSet::new();
365 let mut seen_terminal: HashSet<String> = HashSet::new();
366 let mut stream_id: Option<String> = None;
367 let mut attempts: u32 = 0;
368
369 loop {
370 let mut source = match &stream_id {
371 None => once(transport.clone(), query.clone()),
372 Some(id) => iter_stream(
373 transport.clone(),
374 Method::GET,
375 format!("/streams/{}/subscribe", enc(id)),
376 None,
377 query.clone(),
378 ),
379 };
380
381 let mut terminal = false;
382 while let Some(item) = source.next().await {
383 let event = match item {
384 Ok(event) => event,
385 Err(error) => {
386 if stream_id.is_none() || attempts >= opts.max_attempts {
387 yield Err(error);
388 return;
389 }
390 break;
391 }
392 };
393
394 match event.event_type() {
395 "event:published" => {
396 if let Some(published) =
397 event.get("stream_id").and_then(Value::as_str)
398 {
399 stream_id = Some(published.to_string());
400 }
401 }
402 "run:start" => {
403 let run_id = event.run_id().map(str::to_string);
404 if let Some(run_id) = run_id {
405 if !seen_start.insert(run_id) {
406 continue;
407 }
408 }
409 }
410 "run:stop" | "run:fail" | "run:cancel" => {
411 let run_id = event.run_id().map(str::to_string);
412 if let Some(run_id) = run_id {
413 if !seen_terminal.insert(run_id) {
414 continue;
415 }
416 }
417 terminal = true;
418 }
419 _ => {}
420 }
421
422 yield Ok(event);
423 }
424
425 if terminal {
426 return;
427 }
428 if stream_id.is_none() {
429 yield Err(Error::Protocol(
430 "stream ended before event:published was received".to_string(),
431 ));
432 return;
433 }
434
435 attempts += 1;
436 let delay = Duration::from_millis((250 * u64::from(attempts)).min(2000));
437 tokio::time::sleep(delay).await;
438 }
439 })
440 }
441}
442
443impl FilesResource {
444 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
446 self.transport
447 .request_json(Method::GET, "/files", None, query)
448 .await
449 }
450
451 pub async fn get(&self, file_id: &str) -> Result<JsonObject> {
453 let path = format!("/files/{}", enc(file_id));
454 let envelope = self
455 .transport
456 .request_json(Method::GET, &path, None, &[])
457 .await?;
458 Ok(data_object(envelope))
459 }
460
461 pub async fn delete(&self, file_id: &str) -> Result<()> {
463 let path = format!("/files/{}", enc(file_id));
464 let builder = self.transport.request_builder(Method::DELETE, &path);
465 self.transport.execute(builder).await?;
466 Ok(())
467 }
468
469 pub async fn download(&self, file_id: &str) -> Result<Response> {
471 let path = format!("/files/{}/download", enc(file_id));
472 let builder = self.transport.request_builder(Method::GET, &path);
473 self.transport.execute(builder).await
474 }
475
476 pub async fn upload(
478 &self,
479 path: &str,
480 content: Vec<u8>,
481 content_type: Option<&str>,
482 ) -> Result<JsonObject> {
483 let request_path = format!("/files/upload/{}", enc_path(path));
484 let mut builder = self
485 .transport
486 .request_builder(Method::PUT, &request_path)
487 .header(ACCEPT, "application/json")
488 .body(content);
489 if let Some(content_type) = content_type {
490 builder = builder.header(CONTENT_TYPE, content_type);
491 }
492 let response = self.transport.execute(builder).await?;
493 decode_data_response(response).await
494 }
495
496 pub async fn initiate_upload(&self, body: impl Serialize) -> Result<JsonObject> {
498 let envelope = self
499 .transport
500 .request_json(
501 Method::POST,
502 "/files/uploads",
503 Some(&serde_json::to_value(body)?),
504 &[],
505 )
506 .await?;
507 Ok(data_object(envelope))
508 }
509
510 pub async fn upload_part(
512 &self,
513 upload_id: &str,
514 part_number: u32,
515 content: Vec<u8>,
516 ) -> Result<JsonObject> {
517 let path = format!("/files/uploads/{}/{}", enc(upload_id), part_number);
518 let builder = self
519 .transport
520 .request_builder(Method::PUT, &path)
521 .header(ACCEPT, "application/json")
522 .body(content);
523 let response = self.transport.execute(builder).await?;
524 decode_data_response(response).await
525 }
526
527 pub async fn complete_upload(&self, upload_id: &str) -> Result<JsonObject> {
529 let path = format!("/files/uploads/{}/complete", enc(upload_id));
530 let envelope = self
531 .transport
532 .request_json(Method::POST, &path, None, &[])
533 .await?;
534 Ok(data_object(envelope))
535 }
536
537 pub async fn abort_upload(&self, upload_id: &str) -> Result<()> {
539 let path = format!("/files/uploads/{}", enc(upload_id));
540 let builder = self.transport.request_builder(Method::DELETE, &path);
541 self.transport.execute(builder).await?;
542 Ok(())
543 }
544}
545
546impl ProjectsResource {
547 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
549 self.transport
550 .request_json(Method::GET, "/projects", None, query)
551 .await
552 }
553
554 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
556 let envelope = self
557 .transport
558 .request_json(
559 Method::POST,
560 "/projects",
561 Some(&serde_json::to_value(body)?),
562 &[],
563 )
564 .await?;
565 Ok(data_object(envelope))
566 }
567
568 pub async fn get(&self, project: &str) -> Result<JsonObject> {
570 let path = format!("/projects/{}", enc(project));
571 let envelope = self
572 .transport
573 .request_json(Method::GET, &path, None, &[])
574 .await?;
575 Ok(data_object(envelope))
576 }
577
578 pub async fn update(&self, project: &str, body: impl Serialize) -> Result<JsonObject> {
580 let path = format!("/projects/{}", enc(project));
581 let envelope = self
582 .transport
583 .request_json(
584 Method::PATCH,
585 &path,
586 Some(&serde_json::to_value(body)?),
587 &[],
588 )
589 .await?;
590 Ok(data_object(envelope))
591 }
592
593 pub async fn delete(&self, project: &str) -> Result<()> {
595 let path = format!("/projects/{}", enc(project));
596 let builder = self.transport.request_builder(Method::DELETE, &path);
597 self.transport.execute(builder).await?;
598 Ok(())
599 }
600
601 pub async fn activate(&self, project: &str) -> Result<JsonObject> {
603 let path = format!("/projects/{}/activate", enc(project));
604 let envelope = self
605 .transport
606 .request_json(Method::POST, &path, None, &[])
607 .await?;
608 Ok(data_object(envelope))
609 }
610
611 pub async fn deactivate(&self, project: &str) -> Result<JsonObject> {
613 let path = format!("/projects/{}/deactivate", enc(project));
614 let envelope = self
615 .transport
616 .request_json(Method::POST, &path, None, &[])
617 .await?;
618 Ok(data_object(envelope))
619 }
620
621 pub async fn event_handlers(&self, project: &str) -> Result<JsonObject> {
623 let path = format!("/projects/{}/event-handlers", enc(project));
624 self.transport
625 .request_json(Method::GET, &path, None, &[])
626 .await
627 }
628
629 pub async fn schedules(&self, project: &str) -> Result<JsonObject> {
631 let path = format!("/projects/{}/schedules", enc(project));
632 self.transport
633 .request_json(Method::GET, &path, None, &[])
634 .await
635 }
636}
637
638impl BuildsResource {
639 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
641 self.transport
642 .request_json(Method::GET, "/builds", None, query)
643 .await
644 }
645
646 pub async fn list_for_project(
648 &self,
649 project: &str,
650 query: &[(&str, &str)],
651 ) -> Result<JsonObject> {
652 let path = format!("/projects/{}/builds", enc(project));
653 self.transport
654 .request_json(Method::GET, &path, None, query)
655 .await
656 }
657
658 pub async fn get(&self, project: &str, build_id: &str) -> Result<JsonObject> {
660 let path = format!("/projects/{}/builds/{}", enc(project), enc(build_id));
661 let envelope = self
662 .transport
663 .request_json(Method::GET, &path, None, &[])
664 .await?;
665 Ok(data_object(envelope))
666 }
667
668 pub async fn deployed(&self, project: &str) -> Result<JsonObject> {
670 let path = format!("/projects/{}/builds/deployed", enc(project));
671 let envelope = self
672 .transport
673 .request_json(Method::GET, &path, None, &[])
674 .await?;
675 Ok(data_object(envelope))
676 }
677
678 pub async fn live(&self, project: &str) -> Result<JsonObject> {
680 let path = format!("/projects/{}/builds/live", enc(project));
681 let envelope = self
682 .transport
683 .request_json(Method::GET, &path, None, &[])
684 .await?;
685 Ok(data_object(envelope))
686 }
687
688 pub async fn upload(&self, project: &str, upload: BuildUpload) -> Result<JsonObject> {
690 let mut form = Form::new().text("hash", upload.hash);
691 if let Some(build_id) = upload.build_id {
692 form = form.text("build_id", build_id);
693 }
694 let part = Part::bytes(upload.content)
695 .file_name(upload.filename.unwrap_or_else(|| "build".to_string()));
696 form = form.part("file", part);
697
698 let path = format!("/projects/{}/builds", enc(project));
699 let builder = self
700 .transport
701 .request_builder(Method::POST, &path)
702 .header(ACCEPT, "application/json")
703 .multipart(form);
704 let response = self.transport.execute(builder).await?;
705 decode_data_response(response).await
706 }
707
708 pub async fn download(&self, project: &str, build_id: &str) -> Result<Response> {
710 let path = format!(
711 "/projects/{}/builds/{}/download",
712 enc(project),
713 enc(build_id)
714 );
715 let builder = self.transport.request_builder(Method::GET, &path);
716 self.transport.execute(builder).await
717 }
718
719 pub async fn deploy(&self, project: &str, build_id: &str) -> Result<JsonObject> {
721 let path = format!("/projects/{}/builds/{}/deploy", enc(project), enc(build_id));
722 let envelope = self
723 .transport
724 .request_json(Method::POST, &path, None, &[])
725 .await?;
726 Ok(data_object(envelope))
727 }
728}
729
730impl ContextResource {
731 pub async fn list(&self, project: &str) -> Result<JsonObject> {
733 let path = format!("/projects/{}/context", enc(project));
734 self.transport
735 .request_json(Method::GET, &path, None, &[])
736 .await
737 }
738
739 pub async fn create(&self, project: &str, body: impl Serialize) -> Result<JsonObject> {
741 let path = format!("/projects/{}/context", enc(project));
742 let envelope = self
743 .transport
744 .request_json(Method::POST, &path, Some(&serde_json::to_value(body)?), &[])
745 .await?;
746 Ok(data_object(envelope))
747 }
748
749 pub async fn update(
751 &self,
752 project: &str,
753 key: &str,
754 body: impl Serialize,
755 ) -> Result<JsonObject> {
756 let path = format!("/projects/{}/context/{}", enc(project), enc(key));
757 let envelope = self
758 .transport
759 .request_json(Method::PUT, &path, Some(&serde_json::to_value(body)?), &[])
760 .await?;
761 Ok(data_object(envelope))
762 }
763
764 pub async fn delete(&self, project: &str, key: &str) -> Result<()> {
766 let path = format!("/projects/{}/context/{}", enc(project), enc(key));
767 let builder = self.transport.request_builder(Method::DELETE, &path);
768 self.transport.execute(builder).await?;
769 Ok(())
770 }
771}
772
773impl DomainsResource {
774 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
776 let envelope = self
777 .transport
778 .request_json(
779 Method::POST,
780 "/domains",
781 Some(&serde_json::to_value(body)?),
782 &[],
783 )
784 .await?;
785 Ok(data_object(envelope))
786 }
787
788 pub async fn list(&self) -> Result<JsonObject> {
790 self.transport
791 .request_json(Method::GET, "/domains", None, &[])
792 .await
793 }
794
795 pub async fn get(&self, domain_id: &str) -> Result<JsonObject> {
797 let path = format!("/domains/{}", enc(domain_id));
798 let envelope = self
799 .transport
800 .request_json(Method::GET, &path, None, &[])
801 .await?;
802 Ok(data_object(envelope))
803 }
804
805 pub async fn delete(&self, domain_id: &str) -> Result<()> {
807 let path = format!("/domains/{}", enc(domain_id));
808 let builder = self.transport.request_builder(Method::DELETE, &path);
809 self.transport.execute(builder).await?;
810 Ok(())
811 }
812
813 pub async fn verify(&self, domain_id: &str) -> Result<JsonObject> {
815 let path = format!("/domains/{}/verify", enc(domain_id));
816 let envelope = self
817 .transport
818 .request_json(Method::POST, &path, None, &[])
819 .await?;
820 Ok(data_object(envelope))
821 }
822}
823
824impl SessionsResource {
825 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
827 let envelope = self
828 .transport
829 .request_json(
830 Method::POST,
831 "/sessions",
832 Some(&serde_json::to_value(body)?),
833 &[],
834 )
835 .await?;
836 Ok(data_object(envelope))
837 }
838
839 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
841 self.transport
842 .request_json(Method::GET, "/sessions", None, query)
843 .await
844 }
845
846 pub async fn revoke(&self, session_id: &str) -> Result<()> {
848 let path = format!("/sessions/{}", enc(session_id));
849 let builder = self.transport.request_builder(Method::DELETE, &path);
850 self.transport.execute(builder).await?;
851 Ok(())
852 }
853
854 pub async fn revoke_all(&self) -> Result<JsonObject> {
856 let envelope = self
857 .transport
858 .request_json(Method::DELETE, "/sessions", None, &[])
859 .await?;
860 Ok(data_object(envelope))
861 }
862}
863
864impl ServiceKeysResource {
865 pub async fn create(&self, body: impl Serialize) -> Result<JsonObject> {
867 let envelope = self
868 .transport
869 .request_json(
870 Method::POST,
871 "/service-keys",
872 Some(&serde_json::to_value(body)?),
873 &[],
874 )
875 .await?;
876 Ok(data_object(envelope))
877 }
878
879 pub async fn list(&self, query: &[(&str, &str)]) -> Result<JsonObject> {
881 self.transport
882 .request_json(Method::GET, "/service-keys", None, query)
883 .await
884 }
885
886 pub async fn get(&self, service_key_id: &str) -> Result<JsonObject> {
888 let path = format!("/service-keys/{}", enc(service_key_id));
889 let envelope = self
890 .transport
891 .request_json(Method::GET, &path, None, &[])
892 .await?;
893 Ok(data_object(envelope))
894 }
895
896 pub async fn update(&self, service_key_id: &str, body: impl Serialize) -> Result<JsonObject> {
898 let path = format!("/service-keys/{}", enc(service_key_id));
899 let envelope = self
900 .transport
901 .request_json(
902 Method::PATCH,
903 &path,
904 Some(&serde_json::to_value(body)?),
905 &[],
906 )
907 .await?;
908 Ok(data_object(envelope))
909 }
910
911 pub async fn revoke(&self, service_key_id: &str) -> Result<()> {
913 let path = format!("/service-keys/{}", enc(service_key_id));
914 let builder = self.transport.request_builder(Method::DELETE, &path);
915 self.transport.execute(builder).await?;
916 Ok(())
917 }
918
919 pub async fn revoke_all(&self) -> Result<JsonObject> {
921 let envelope = self
922 .transport
923 .request_json(Method::DELETE, "/service-keys", None, &[])
924 .await?;
925 Ok(data_object(envelope))
926 }
927}
928
929fn project_query(project: Option<&str>) -> Vec<(String, String)> {
930 match project {
931 Some(project) => vec![("project".to_string(), project.to_string())],
932 None => Vec::new(),
933 }
934}
935
936fn string_field(object: &JsonObject, key: &str) -> String {
937 object
938 .get(key)
939 .and_then(Value::as_str)
940 .unwrap_or("")
941 .to_string()
942}