1mod github_context;
7mod spec;
8
9pub use github_context::GhOutput;
10pub use github_context::GhToRust;
11pub use github_context::RustToGh;
12
13use self::steps::ado::AdoRuntimeVar;
14use self::steps::ado::AdoStepServices;
15use self::steps::github::GhStepBuilder;
16use self::steps::rust::RustRuntimeServices;
17use self::user_facing::ClaimedGhParam;
18use self::user_facing::GhPermission;
19use self::user_facing::GhPermissionValue;
20use crate::node::github_context::GhContextVarReader;
21use github_context::state::Root;
22use serde::Deserialize;
23use serde::Serialize;
24use serde::de::DeserializeOwned;
25use std::cell::RefCell;
26use std::collections::BTreeMap;
27use std::path::PathBuf;
28use std::rc::Rc;
29use user_facing::GhParam;
30
31pub mod user_facing {
34 pub use super::ClaimVar;
35 pub use super::ClaimedReadVar;
36 pub use super::ClaimedWriteVar;
37 pub use super::FlowArch;
38 pub use super::FlowBackend;
39 pub use super::FlowNode;
40 pub use super::FlowPlatform;
41 pub use super::FlowPlatformKind;
42 pub use super::GhUserSecretVar;
43 pub use super::ImportCtx;
44 pub use super::IntoRequest;
45 pub use super::NodeCtx;
46 pub use super::ReadVar;
47 pub use super::SideEffect;
48 pub use super::SimpleFlowNode;
49 pub use super::StepCtx;
50 pub use super::VarClaimed;
51 pub use super::VarEqBacking;
52 pub use super::VarNotClaimed;
53 pub use super::WriteVar;
54 pub use super::steps::ado::AdoResourcesRepositoryId;
55 pub use super::steps::ado::AdoRuntimeVar;
56 pub use super::steps::ado::AdoStepServices;
57 pub use super::steps::github::ClaimedGhParam;
58 pub use super::steps::github::GhParam;
59 pub use super::steps::github::GhPermission;
60 pub use super::steps::github::GhPermissionValue;
61 pub use super::steps::rust::RustRuntimeServices;
62 pub use crate::flowey_request;
63 pub use crate::new_flow_node;
64 pub use crate::new_simple_flow_node;
65 pub use crate::node::FlowPlatformLinuxDistro;
66 pub use crate::pipeline::Artifact;
67
68 pub fn same_across_all_reqs<T: PartialEq>(
71 req_name: &str,
72 var: &mut Option<T>,
73 new: T,
74 ) -> anyhow::Result<()> {
75 match (var.as_ref(), new) {
76 (None, v) => *var = Some(v),
77 (Some(old), new) => {
78 if *old != new {
79 anyhow::bail!("`{}` must be consistent across requests", req_name);
80 }
81 }
82 }
83
84 Ok(())
85 }
86
87 pub fn same_across_all_reqs_backing_var<V: VarEqBacking>(
91 req_name: &str,
92 var: &mut Option<V>,
93 new: V,
94 ) -> anyhow::Result<()> {
95 match (var.as_ref(), new) {
96 (None, v) => *var = Some(v),
97 (Some(old), new) => {
98 if !old.eq(&new) {
99 anyhow::bail!("`{}` must be consistent across requests", req_name);
100 }
101 }
102 }
103
104 Ok(())
105 }
106}
107
108pub trait VarEqBacking {
132 fn eq(&self, other: &Self) -> bool;
134}
135
136impl<T> VarEqBacking for WriteVar<T>
137where
138 T: Serialize + DeserializeOwned,
139{
140 fn eq(&self, other: &Self) -> bool {
141 self.backing_var == other.backing_var
142 }
143}
144
145impl<T> VarEqBacking for ReadVar<T>
146where
147 T: Serialize + DeserializeOwned + PartialEq + Eq + Clone,
148{
149 fn eq(&self, other: &Self) -> bool {
150 self.backing_var == other.backing_var
151 }
152}
153
154impl<T, U> VarEqBacking for (T, U)
156where
157 T: VarEqBacking,
158 U: VarEqBacking,
159{
160 fn eq(&self, other: &Self) -> bool {
161 (self.0.eq(&other.0)) && (self.1.eq(&other.1))
162 }
163}
164
165pub type SideEffect = ();
172
173#[derive(Clone, Debug, Serialize, Deserialize)]
176pub enum VarNotClaimed {}
177
178#[derive(Clone, Debug, Serialize, Deserialize)]
181pub enum VarClaimed {}
182
183#[derive(Debug, Serialize, Deserialize)]
203pub struct WriteVar<T: Serialize + DeserializeOwned, C = VarNotClaimed> {
204 backing_var: String,
205 is_side_effect: bool,
208
209 #[serde(skip)]
210 _kind: core::marker::PhantomData<(T, C)>,
211}
212
213pub type ClaimedWriteVar<T> = WriteVar<T, VarClaimed>;
216
217impl<T: Serialize + DeserializeOwned> WriteVar<T, VarNotClaimed> {
218 fn into_claimed(self) -> WriteVar<T, VarClaimed> {
220 let Self {
221 backing_var,
222 is_side_effect,
223 _kind,
224 } = self;
225
226 WriteVar {
227 backing_var,
228 is_side_effect,
229 _kind: std::marker::PhantomData,
230 }
231 }
232
233 #[track_caller]
235 pub fn write_static(self, ctx: &mut NodeCtx<'_>, val: T)
236 where
237 T: 'static,
238 {
239 let val = ReadVar::from_static(val);
240 val.write_into(ctx, self, |v| v);
241 }
242
243 pub(crate) fn into_json(self) -> WriteVar<serde_json::Value> {
244 WriteVar {
245 backing_var: self.backing_var,
246 is_side_effect: self.is_side_effect,
247 _kind: std::marker::PhantomData,
248 }
249 }
250}
251
252impl WriteVar<SideEffect, VarNotClaimed> {
253 pub fn discard_result<T: Serialize + DeserializeOwned>(self) -> WriteVar<T> {
258 WriteVar {
259 backing_var: self.backing_var,
260 is_side_effect: true,
261 _kind: std::marker::PhantomData,
262 }
263 }
264}
265
266pub trait ClaimVar {
274 type Claimed;
276 fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed;
278}
279
280pub trait ReadVarValue {
286 type Value;
288 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value;
290}
291
292impl<T: Serialize + DeserializeOwned> ClaimVar for ReadVar<T> {
293 type Claimed = ClaimedReadVar<T>;
294
295 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedReadVar<T> {
296 if let ReadVarBacking::RuntimeVar {
297 var,
298 is_side_effect: _,
299 } = &self.backing_var
300 {
301 ctx.backend.borrow_mut().on_claimed_runtime_var(var, true);
302 }
303 self.into_claimed()
304 }
305}
306
307impl<T: Serialize + DeserializeOwned> ClaimVar for WriteVar<T> {
308 type Claimed = ClaimedWriteVar<T>;
309
310 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedWriteVar<T> {
311 ctx.backend
312 .borrow_mut()
313 .on_claimed_runtime_var(&self.backing_var, false);
314 self.into_claimed()
315 }
316}
317
318impl<T: Serialize + DeserializeOwned> ReadVarValue for ClaimedReadVar<T> {
319 type Value = T;
320
321 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
322 match self.backing_var {
323 ReadVarBacking::RuntimeVar {
324 var,
325 is_side_effect,
326 } => {
327 let data = rt.get_var(&var, is_side_effect);
329 if is_side_effect {
330 serde_json::from_slice(b"null").expect("should be deserializing into ()")
334 } else {
335 serde_json::from_slice(&data).expect("improve this error path")
337 }
338 }
339 ReadVarBacking::Inline(val) => val,
340 }
341 }
342}
343
344impl<T: ClaimVar> ClaimVar for Vec<T> {
345 type Claimed = Vec<T::Claimed>;
346
347 fn claim(self, ctx: &mut StepCtx<'_>) -> Vec<T::Claimed> {
348 self.into_iter().map(|v| v.claim(ctx)).collect()
349 }
350}
351
352impl<T: ReadVarValue> ReadVarValue for Vec<T> {
353 type Value = Vec<T::Value>;
354
355 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
356 self.into_iter().map(|v| v.read_value(rt)).collect()
357 }
358}
359
360impl<T: ClaimVar> ClaimVar for Option<T> {
361 type Claimed = Option<T::Claimed>;
362
363 fn claim(self, ctx: &mut StepCtx<'_>) -> Option<T::Claimed> {
364 self.map(|x| x.claim(ctx))
365 }
366}
367
368impl<T: ReadVarValue> ReadVarValue for Option<T> {
369 type Value = Option<T::Value>;
370
371 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
372 self.map(|x| x.read_value(rt))
373 }
374}
375
376impl<U: Ord, T: ClaimVar> ClaimVar for BTreeMap<U, T> {
377 type Claimed = BTreeMap<U, T::Claimed>;
378
379 fn claim(self, ctx: &mut StepCtx<'_>) -> BTreeMap<U, T::Claimed> {
380 self.into_iter().map(|(k, v)| (k, v.claim(ctx))).collect()
381 }
382}
383
384impl<U: Ord, T: ReadVarValue> ReadVarValue for BTreeMap<U, T> {
385 type Value = BTreeMap<U, T::Value>;
386
387 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
388 self.into_iter()
389 .map(|(k, v)| (k, v.read_value(rt)))
390 .collect()
391 }
392}
393
394macro_rules! impl_tuple_claim {
395 ($($T:tt)*) => {
396 impl<$($T,)*> $crate::node::ClaimVar for ($($T,)*)
397 where
398 $($T: $crate::node::ClaimVar,)*
399 {
400 type Claimed = ($($T::Claimed,)*);
401
402 #[expect(non_snake_case)]
403 fn claim(self, ctx: &mut $crate::node::StepCtx<'_>) -> Self::Claimed {
404 let ($($T,)*) = self;
405 ($($T.claim(ctx),)*)
406 }
407 }
408
409 impl<$($T,)*> $crate::node::ReadVarValue for ($($T,)*)
410 where
411 $($T: $crate::node::ReadVarValue,)*
412 {
413 type Value = ($($T::Value,)*);
414
415 #[expect(non_snake_case)]
416 fn read_value(self, rt: &mut $crate::node::RustRuntimeServices<'_>) -> Self::Value {
417 let ($($T,)*) = self;
418 ($($T.read_value(rt),)*)
419 }
420 }
421 };
422}
423
424impl_tuple_claim!(A B C D E F G H I J);
425impl_tuple_claim!(A B C D E F G H I);
426impl_tuple_claim!(A B C D E F G H);
427impl_tuple_claim!(A B C D E F G);
428impl_tuple_claim!(A B C D E F);
429impl_tuple_claim!(A B C D E);
430impl_tuple_claim!(A B C D);
431impl_tuple_claim!(A B C);
432impl_tuple_claim!(A B);
433impl_tuple_claim!(A);
434
435impl ClaimVar for () {
436 type Claimed = ();
437
438 fn claim(self, _ctx: &mut StepCtx<'_>) -> Self::Claimed {}
439}
440
441impl ReadVarValue for () {
442 type Value = ();
443
444 fn read_value(self, _rt: &mut RustRuntimeServices<'_>) -> Self::Value {}
445}
446
447#[derive(Serialize, Deserialize, Clone)]
452pub struct GhUserSecretVar(pub(crate) String);
453
454#[derive(Debug, Serialize, Deserialize)]
473pub struct ReadVar<T, C = VarNotClaimed> {
474 backing_var: ReadVarBacking<T>,
475 #[serde(skip)]
476 _kind: std::marker::PhantomData<C>,
477}
478
479pub type ClaimedReadVar<T> = ReadVar<T, VarClaimed>;
482
483impl<T: Serialize + DeserializeOwned, C> Clone for ReadVar<T, C> {
485 fn clone(&self) -> Self {
486 ReadVar {
487 backing_var: self.backing_var.clone(),
488 _kind: std::marker::PhantomData,
489 }
490 }
491}
492
493#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
494enum ReadVarBacking<T> {
495 RuntimeVar {
496 var: String,
497 is_side_effect: bool,
504 },
505 Inline(T),
506}
507
508impl<T: Serialize + DeserializeOwned> Clone for ReadVarBacking<T> {
510 fn clone(&self) -> Self {
511 match self {
512 Self::RuntimeVar {
513 var,
514 is_side_effect,
515 } => Self::RuntimeVar {
516 var: var.clone(),
517 is_side_effect: *is_side_effect,
518 },
519 Self::Inline(v) => {
520 Self::Inline(serde_json::from_value(serde_json::to_value(v).unwrap()).unwrap())
521 }
522 }
523 }
524}
525
526impl<T: Serialize + DeserializeOwned> ReadVar<T> {
527 fn into_claimed(self) -> ReadVar<T, VarClaimed> {
529 let Self { backing_var, _kind } = self;
530
531 ReadVar {
532 backing_var,
533 _kind: std::marker::PhantomData,
534 }
535 }
536
537 #[must_use]
546 pub fn into_side_effect(self) -> ReadVar<SideEffect> {
547 ReadVar {
548 backing_var: match self.backing_var {
549 ReadVarBacking::RuntimeVar {
550 var,
551 is_side_effect: _,
552 } => ReadVarBacking::RuntimeVar {
553 var,
554 is_side_effect: true,
555 },
556 ReadVarBacking::Inline(_) => ReadVarBacking::Inline(()),
557 },
558 _kind: std::marker::PhantomData,
559 }
560 }
561
562 #[track_caller]
565 #[must_use]
566 pub fn map<F, U>(&self, ctx: &mut NodeCtx<'_>, f: F) -> ReadVar<U>
567 where
568 T: 'static,
569 U: Serialize + DeserializeOwned + 'static,
570 F: FnOnce(T) -> U + 'static,
571 {
572 let (read_from, write_into) = ctx.new_var();
573 self.write_into(ctx, write_into, f);
574 read_from
575 }
576
577 #[track_caller]
580 pub fn write_into<F, U>(&self, ctx: &mut NodeCtx<'_>, write_into: WriteVar<U>, f: F)
581 where
582 T: 'static,
583 U: Serialize + DeserializeOwned + 'static,
584 F: FnOnce(T) -> U + 'static,
585 {
586 let this = self.clone();
587 ctx.emit_minor_rust_step("🌼 write_into Var", move |ctx| {
588 let this = this.claim(ctx);
589 let write_into = write_into.claim(ctx);
590 move |rt| {
591 let this = rt.read(this);
592 rt.write(write_into, &f(this));
593 }
594 });
595 }
596
597 #[track_caller]
600 #[must_use]
601 pub fn zip<U>(&self, ctx: &mut NodeCtx<'_>, other: ReadVar<U>) -> ReadVar<(T, U)>
602 where
603 T: 'static,
604 U: Serialize + DeserializeOwned + 'static,
605 {
606 let (read_from, write_into) = ctx.new_var();
607 let this = self.clone();
608 ctx.emit_minor_rust_step("🌼 Zip Vars", move |ctx| {
609 let this = this.claim(ctx);
610 let other = other.claim(ctx);
611 let write_into = write_into.claim(ctx);
612 move |rt| {
613 let this = rt.read(this);
614 let other = rt.read(other);
615 rt.write(write_into, &(this, other));
616 }
617 });
618 read_from
619 }
620
621 #[track_caller]
626 #[must_use]
627 pub fn from_static(val: T) -> ReadVar<T>
628 where
629 T: 'static,
630 {
631 ReadVar {
632 backing_var: ReadVarBacking::Inline(val),
633 _kind: std::marker::PhantomData,
634 }
635 }
636
637 pub fn get_static(&self) -> Option<T> {
646 match self.clone().backing_var {
647 ReadVarBacking::Inline(v) => Some(v),
648 _ => None,
649 }
650 }
651
652 #[track_caller]
654 #[must_use]
655 pub fn transpose_vec(ctx: &mut NodeCtx<'_>, vec: Vec<ReadVar<T>>) -> ReadVar<Vec<T>>
656 where
657 T: 'static,
658 {
659 let (read_from, write_into) = ctx.new_var();
660 ctx.emit_minor_rust_step("🌼 Transpose Vec<ReadVar<T>>", move |ctx| {
661 let vec = vec.claim(ctx);
662 let write_into = write_into.claim(ctx);
663 move |rt| {
664 let mut v = Vec::new();
665 for var in vec {
666 v.push(rt.read(var));
667 }
668 rt.write(write_into, &v);
669 }
670 });
671 read_from
672 }
673
674 #[must_use]
690 pub fn depending_on<U>(&self, ctx: &mut NodeCtx<'_>, other: &ReadVar<U>) -> Self
691 where
692 T: 'static,
693 U: Serialize + DeserializeOwned + 'static,
694 {
695 ctx.emit_minor_rust_stepv("🌼 Add dependency", |ctx| {
698 let this = self.clone().claim(ctx);
699 other.clone().claim(ctx);
700 move |rt| rt.read(this)
701 })
702 }
703
704 pub fn claim_unused(self, ctx: &mut NodeCtx<'_>) {
707 match self.backing_var {
708 ReadVarBacking::RuntimeVar {
709 var,
710 is_side_effect: _,
711 } => ctx.backend.borrow_mut().on_unused_read_var(&var),
712 ReadVarBacking::Inline(_) => {}
713 }
714 }
715
716 pub(crate) fn into_json(self) -> ReadVar<serde_json::Value> {
717 match self.backing_var {
718 ReadVarBacking::RuntimeVar {
719 var,
720 is_side_effect,
721 } => ReadVar {
722 backing_var: ReadVarBacking::RuntimeVar {
723 var,
724 is_side_effect,
725 },
726 _kind: std::marker::PhantomData,
727 },
728 ReadVarBacking::Inline(v) => ReadVar {
729 backing_var: ReadVarBacking::Inline(serde_json::to_value(v).unwrap()),
730 _kind: std::marker::PhantomData,
731 },
732 }
733 }
734}
735
736#[must_use]
742pub fn thin_air_read_runtime_var<T>(backing_var: String) -> ReadVar<T>
743where
744 T: Serialize + DeserializeOwned,
745{
746 ReadVar {
747 backing_var: ReadVarBacking::RuntimeVar {
748 var: backing_var,
749 is_side_effect: false,
750 },
751 _kind: std::marker::PhantomData,
752 }
753}
754
755#[must_use]
761pub fn thin_air_write_runtime_var<T>(backing_var: String) -> WriteVar<T>
762where
763 T: Serialize + DeserializeOwned,
764{
765 WriteVar {
766 backing_var,
767 is_side_effect: false,
768 _kind: std::marker::PhantomData,
769 }
770}
771
772pub fn read_var_internals<T: Serialize + DeserializeOwned, C>(
778 var: &ReadVar<T, C>,
779) -> (Option<String>, bool) {
780 match var.backing_var {
781 ReadVarBacking::RuntimeVar {
782 var: ref s,
783 is_side_effect,
784 } => (Some(s.clone()), is_side_effect),
785 ReadVarBacking::Inline(_) => (None, false),
786 }
787}
788
789pub trait ImportCtxBackend {
790 fn on_possible_dep(&mut self, node_handle: NodeHandle);
791}
792
793pub struct ImportCtx<'a> {
795 backend: &'a mut dyn ImportCtxBackend,
796}
797
798impl ImportCtx<'_> {
799 pub fn import<N: FlowNodeBase + 'static>(&mut self) {
801 self.backend.on_possible_dep(NodeHandle::from_type::<N>())
802 }
803}
804
805pub fn new_import_ctx(backend: &mut dyn ImportCtxBackend) -> ImportCtx<'_> {
806 ImportCtx { backend }
807}
808
809#[derive(Debug)]
810pub enum CtxAnchor {
811 PostJob,
812}
813
814pub trait NodeCtxBackend {
815 fn current_node(&self) -> NodeHandle;
817
818 fn on_new_var(&mut self) -> String;
823
824 fn on_claimed_runtime_var(&mut self, var: &str, is_read: bool);
826
827 fn on_unused_read_var(&mut self, var: &str);
829
830 fn on_request(&mut self, node_handle: NodeHandle, req: anyhow::Result<Box<[u8]>>);
838
839 fn on_emit_rust_step(
840 &mut self,
841 label: &str,
842 can_merge: bool,
843 code: Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
844 );
845
846 fn on_emit_ado_step(
847 &mut self,
848 label: &str,
849 yaml_snippet: Box<dyn for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String>,
850 inline_script: Option<
851 Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
852 >,
853 condvar: Option<String>,
854 );
855
856 fn on_emit_gh_step(
857 &mut self,
858 label: &str,
859 uses: &str,
860 with: BTreeMap<String, ClaimedGhParam>,
861 condvar: Option<String>,
862 outputs: BTreeMap<String, Vec<GhOutput>>,
863 permissions: BTreeMap<GhPermission, GhPermissionValue>,
864 gh_to_rust: Vec<GhToRust>,
865 rust_to_gh: Vec<RustToGh>,
866 );
867
868 fn on_emit_side_effect_step(&mut self);
869
870 fn backend(&mut self) -> FlowBackend;
871 fn platform(&mut self) -> FlowPlatform;
872 fn arch(&mut self) -> FlowArch;
873
874 fn persistent_dir_path_var(&mut self) -> Option<String>;
878}
879
880pub fn new_node_ctx(backend: &mut dyn NodeCtxBackend) -> NodeCtx<'_> {
881 NodeCtx {
882 backend: Rc::new(RefCell::new(backend)),
883 }
884}
885
886#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
888pub enum FlowBackend {
889 Local,
891 Ado,
893 Github,
895}
896
897#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
899pub enum FlowPlatformKind {
900 Windows,
901 Unix,
902}
903
904#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
906pub enum FlowPlatformLinuxDistro {
907 Fedora,
909 Ubuntu,
911 Unknown,
913}
914
915#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
917#[non_exhaustive]
918pub enum FlowPlatform {
919 Windows,
921 Linux(FlowPlatformLinuxDistro),
923 MacOs,
925}
926
927impl FlowPlatform {
928 pub fn kind(&self) -> FlowPlatformKind {
929 match self {
930 Self::Windows => FlowPlatformKind::Windows,
931 Self::Linux(_) | Self::MacOs => FlowPlatformKind::Unix,
932 }
933 }
934
935 fn as_str(&self) -> &'static str {
936 match self {
937 Self::Windows => "windows",
938 Self::Linux(_) => "linux",
939 Self::MacOs => "macos",
940 }
941 }
942
943 pub fn exe_suffix(&self) -> &'static str {
945 if self == &Self::Windows { ".exe" } else { "" }
946 }
947
948 pub fn binary(&self, name: &str) -> String {
950 format!("{}{}", name, self.exe_suffix())
951 }
952}
953
954impl std::fmt::Display for FlowPlatform {
955 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
956 f.pad(self.as_str())
957 }
958}
959
960#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
962#[non_exhaustive]
963pub enum FlowArch {
964 X86_64,
965 Aarch64,
966}
967
968impl FlowArch {
969 fn as_str(&self) -> &'static str {
970 match self {
971 Self::X86_64 => "x86_64",
972 Self::Aarch64 => "aarch64",
973 }
974 }
975}
976
977impl std::fmt::Display for FlowArch {
978 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
979 f.pad(self.as_str())
980 }
981}
982
983pub struct StepCtx<'a> {
985 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
986}
987
988impl StepCtx<'_> {
989 pub fn backend(&self) -> FlowBackend {
992 self.backend.borrow_mut().backend()
993 }
994
995 pub fn platform(&self) -> FlowPlatform {
998 self.backend.borrow_mut().platform()
999 }
1000}
1001
1002const NO_ADO_INLINE_SCRIPT: Option<
1003 for<'a> fn(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>,
1004> = None;
1005
1006pub struct NodeCtx<'a> {
1008 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
1009}
1010
1011impl<'ctx> NodeCtx<'ctx> {
1012 pub fn emit_rust_step<F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<SideEffect>
1018 where
1019 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1020 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1021 {
1022 self.emit_rust_step_inner(label.as_ref(), false, code)
1023 }
1024
1025 pub fn emit_minor_rust_step<F, G>(
1035 &mut self,
1036 label: impl AsRef<str>,
1037 code: F,
1038 ) -> ReadVar<SideEffect>
1039 where
1040 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1041 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) + 'static,
1042 {
1043 self.emit_rust_step_inner(label.as_ref(), true, |ctx| {
1044 let f = code(ctx);
1045 |rt| {
1046 f(rt);
1047 Ok(())
1048 }
1049 })
1050 }
1051
1052 #[must_use]
1073 #[track_caller]
1074 pub fn emit_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1075 where
1076 T: Serialize + DeserializeOwned + 'static,
1077 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1078 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1079 {
1080 self.emit_rust_stepv_inner(label.as_ref(), false, code)
1081 }
1082
1083 #[must_use]
1107 #[track_caller]
1108 pub fn emit_minor_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1109 where
1110 T: Serialize + DeserializeOwned + 'static,
1111 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1112 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> T + 'static,
1113 {
1114 self.emit_rust_stepv_inner(label.as_ref(), true, |ctx| {
1115 let f = code(ctx);
1116 |rt| Ok(f(rt))
1117 })
1118 }
1119
1120 fn emit_rust_step_inner<F, G>(
1121 &mut self,
1122 label: &str,
1123 can_merge: bool,
1124 code: F,
1125 ) -> ReadVar<SideEffect>
1126 where
1127 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1128 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1129 {
1130 let (read, write) = self.new_prefixed_var("auto_se");
1131
1132 let ctx = &mut StepCtx {
1133 backend: self.backend.clone(),
1134 };
1135 write.claim(ctx);
1136
1137 let code = code(ctx);
1138 self.backend
1139 .borrow_mut()
1140 .on_emit_rust_step(label.as_ref(), can_merge, Box::new(code));
1141 read
1142 }
1143
1144 #[must_use]
1145 #[track_caller]
1146 fn emit_rust_stepv_inner<T, F, G>(
1147 &mut self,
1148 label: impl AsRef<str>,
1149 can_merge: bool,
1150 code: F,
1151 ) -> ReadVar<T>
1152 where
1153 T: Serialize + DeserializeOwned + 'static,
1154 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1155 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1156 {
1157 let (read, write) = self.new_var();
1158
1159 let ctx = &mut StepCtx {
1160 backend: self.backend.clone(),
1161 };
1162 let write = write.claim(ctx);
1163
1164 let code = code(ctx);
1165 self.backend.borrow_mut().on_emit_rust_step(
1166 label.as_ref(),
1167 can_merge,
1168 Box::new(|rt| {
1169 let val = code(rt)?;
1170 rt.write(write, &val);
1171 Ok(())
1172 }),
1173 );
1174 read
1175 }
1176
1177 #[track_caller]
1179 #[must_use]
1180 pub fn get_ado_variable(&mut self, ado_var: AdoRuntimeVar) -> ReadVar<String> {
1181 let (var, write_var) = self.new_var();
1182 self.emit_ado_step(format!("🌼 read {}", ado_var.as_raw_var_name()), |ctx| {
1183 let write_var = write_var.claim(ctx);
1184 |rt| {
1185 rt.set_var(write_var, ado_var);
1186 "".into()
1187 }
1188 });
1189 var
1190 }
1191
1192 pub fn emit_ado_step<F, G>(&mut self, display_name: impl AsRef<str>, yaml_snippet: F)
1194 where
1195 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1196 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1197 {
1198 self.emit_ado_step_inner(display_name, None, |ctx| {
1199 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1200 })
1201 }
1202
1203 pub fn emit_ado_step_with_condition<F, G>(
1206 &mut self,
1207 display_name: impl AsRef<str>,
1208 cond: ReadVar<bool>,
1209 yaml_snippet: F,
1210 ) where
1211 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1212 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1213 {
1214 self.emit_ado_step_inner(display_name, Some(cond), |ctx| {
1215 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1216 })
1217 }
1218
1219 pub fn emit_ado_step_with_condition_optional<F, G>(
1222 &mut self,
1223 display_name: impl AsRef<str>,
1224 cond: Option<ReadVar<bool>>,
1225 yaml_snippet: F,
1226 ) where
1227 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1228 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1229 {
1230 self.emit_ado_step_inner(display_name, cond, |ctx| {
1231 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1232 })
1233 }
1234
1235 pub fn emit_ado_step_with_inline_script<F, G, H>(
1264 &mut self,
1265 display_name: impl AsRef<str>,
1266 yaml_snippet: F,
1267 ) where
1268 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, H),
1269 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1270 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1271 {
1272 self.emit_ado_step_inner(display_name, None, |ctx| {
1273 let (f, g) = yaml_snippet(ctx);
1274 (f, Some(g))
1275 })
1276 }
1277
1278 fn emit_ado_step_inner<F, G, H>(
1279 &mut self,
1280 display_name: impl AsRef<str>,
1281 cond: Option<ReadVar<bool>>,
1282 yaml_snippet: F,
1283 ) where
1284 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, Option<H>),
1285 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1286 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1287 {
1288 let condvar = match cond.map(|c| c.backing_var) {
1289 Some(ReadVarBacking::Inline(cond)) => {
1291 if !cond {
1292 return;
1293 } else {
1294 None
1295 }
1296 }
1297 Some(ReadVarBacking::RuntimeVar {
1298 var,
1299 is_side_effect,
1300 }) => {
1301 assert!(!is_side_effect);
1302 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1303 Some(var)
1304 }
1305 None => None,
1306 };
1307
1308 let (yaml_snippet, inline_script) = yaml_snippet(&mut StepCtx {
1309 backend: self.backend.clone(),
1310 });
1311 self.backend.borrow_mut().on_emit_ado_step(
1312 display_name.as_ref(),
1313 Box::new(yaml_snippet),
1314 if let Some(inline_script) = inline_script {
1315 Some(Box::new(inline_script))
1316 } else {
1317 None
1318 },
1319 condvar,
1320 );
1321 }
1322
1323 #[track_caller]
1325 #[must_use]
1326 pub fn get_gh_context_var(&mut self) -> GhContextVarReader<'ctx, Root> {
1327 GhContextVarReader {
1328 ctx: NodeCtx {
1329 backend: self.backend.clone(),
1330 },
1331 _state: std::marker::PhantomData,
1332 }
1333 }
1334
1335 pub fn emit_gh_step(
1337 &mut self,
1338 display_name: impl AsRef<str>,
1339 uses: impl AsRef<str>,
1340 ) -> GhStepBuilder {
1341 GhStepBuilder::new(display_name, uses)
1342 }
1343
1344 fn emit_gh_step_inner(
1345 &mut self,
1346 display_name: impl AsRef<str>,
1347 cond: Option<ReadVar<bool>>,
1348 uses: impl AsRef<str>,
1349 with: Option<BTreeMap<String, GhParam>>,
1350 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1351 run_after: Vec<ReadVar<SideEffect>>,
1352 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1353 ) {
1354 let condvar = match cond.map(|c| c.backing_var) {
1355 Some(ReadVarBacking::Inline(cond)) => {
1357 if !cond {
1358 return;
1359 } else {
1360 None
1361 }
1362 }
1363 Some(ReadVarBacking::RuntimeVar {
1364 var,
1365 is_side_effect,
1366 }) => {
1367 assert!(!is_side_effect);
1368 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1369 Some(var)
1370 }
1371 None => None,
1372 };
1373
1374 let with = with
1375 .unwrap_or_default()
1376 .into_iter()
1377 .map(|(k, v)| {
1378 (
1379 k.clone(),
1380 v.claim(&mut StepCtx {
1381 backend: self.backend.clone(),
1382 }),
1383 )
1384 })
1385 .collect();
1386
1387 for var in run_after {
1388 var.claim(&mut StepCtx {
1389 backend: self.backend.clone(),
1390 });
1391 }
1392
1393 let outputvars = outputs
1394 .into_iter()
1395 .map(|(name, vars)| {
1396 (
1397 name,
1398 vars.into_iter()
1399 .map(|var| {
1400 let var = var.claim(&mut StepCtx {
1401 backend: self.backend.clone(),
1402 });
1403 GhOutput {
1404 backing_var: var.backing_var,
1405 is_secret: false,
1406 is_object: false,
1407 }
1408 })
1409 .collect(),
1410 )
1411 })
1412 .collect();
1413
1414 self.backend.borrow_mut().on_emit_gh_step(
1415 display_name.as_ref(),
1416 uses.as_ref(),
1417 with,
1418 condvar,
1419 outputvars,
1420 permissions,
1421 Vec::new(),
1422 Vec::new(),
1423 );
1424 }
1425
1426 pub fn emit_side_effect_step(
1434 &mut self,
1435 use_side_effects: impl IntoIterator<Item = ReadVar<SideEffect>>,
1436 resolve_side_effects: impl IntoIterator<Item = WriteVar<SideEffect>>,
1437 ) {
1438 let mut backend = self.backend.borrow_mut();
1439 for var in use_side_effects.into_iter() {
1440 if let ReadVarBacking::RuntimeVar {
1441 var,
1442 is_side_effect: _,
1443 } = &var.backing_var
1444 {
1445 backend.on_claimed_runtime_var(var, true);
1446 }
1447 }
1448
1449 for var in resolve_side_effects.into_iter() {
1450 backend.on_claimed_runtime_var(&var.backing_var, false);
1451 }
1452
1453 backend.on_emit_side_effect_step();
1454 }
1455
1456 pub fn backend(&self) -> FlowBackend {
1459 self.backend.borrow_mut().backend()
1460 }
1461
1462 pub fn platform(&self) -> FlowPlatform {
1465 self.backend.borrow_mut().platform()
1466 }
1467
1468 pub fn arch(&self) -> FlowArch {
1470 self.backend.borrow_mut().arch()
1471 }
1472
1473 pub fn req<R>(&mut self, req: R)
1475 where
1476 R: IntoRequest + 'static,
1477 {
1478 let mut backend = self.backend.borrow_mut();
1479 backend.on_request(
1480 NodeHandle::from_type::<R::Node>(),
1481 serde_json::to_vec(&req.into_request())
1482 .map(Into::into)
1483 .map_err(Into::into),
1484 );
1485 }
1486
1487 #[track_caller]
1490 #[must_use]
1491 pub fn reqv<T, R>(&mut self, f: impl FnOnce(WriteVar<T>) -> R) -> ReadVar<T>
1492 where
1493 T: Serialize + DeserializeOwned,
1494 R: IntoRequest + 'static,
1495 {
1496 let (read, write) = self.new_var();
1497 self.req::<R>(f(write));
1498 read
1499 }
1500
1501 pub fn requests<N>(&mut self, reqs: impl IntoIterator<Item = N::Request>)
1503 where
1504 N: FlowNodeBase + 'static,
1505 {
1506 let mut backend = self.backend.borrow_mut();
1507 for req in reqs.into_iter() {
1508 backend.on_request(
1509 NodeHandle::from_type::<N>(),
1510 serde_json::to_vec(&req).map(Into::into).map_err(Into::into),
1511 );
1512 }
1513 }
1514
1515 #[track_caller]
1518 #[must_use]
1519 pub fn new_var<T>(&self) -> (ReadVar<T>, WriteVar<T>)
1520 where
1521 T: Serialize + DeserializeOwned,
1522 {
1523 self.new_prefixed_var("")
1524 }
1525
1526 #[track_caller]
1527 #[must_use]
1528 fn new_prefixed_var<T>(&self, prefix: &'static str) -> (ReadVar<T>, WriteVar<T>)
1529 where
1530 T: Serialize + DeserializeOwned,
1531 {
1532 let caller = std::panic::Location::caller()
1534 .to_string()
1535 .replace('\\', "/");
1536
1537 let caller = caller
1553 .split_once("flowey/")
1554 .expect("due to a known limitation with flowey, all flowey code must have an ancestor dir called 'flowey/' somewhere in its full path")
1555 .1;
1556
1557 let colon = if prefix.is_empty() { "" } else { ":" };
1558 let ordinal = self.backend.borrow_mut().on_new_var();
1559 let backing_var = format!("{prefix}{colon}{ordinal}:{caller}");
1560
1561 (
1562 ReadVar {
1563 backing_var: ReadVarBacking::RuntimeVar {
1564 var: backing_var.clone(),
1565 is_side_effect: false,
1566 },
1567 _kind: std::marker::PhantomData,
1568 },
1569 WriteVar {
1570 backing_var,
1571 is_side_effect: false,
1572 _kind: std::marker::PhantomData,
1573 },
1574 )
1575 }
1576
1577 #[track_caller]
1588 #[must_use]
1589 pub fn new_post_job_side_effect(&self) -> (ReadVar<SideEffect>, WriteVar<SideEffect>) {
1590 self.new_prefixed_var("post_job")
1591 }
1592
1593 #[track_caller]
1606 #[must_use]
1607 pub fn persistent_dir(&mut self) -> Option<ReadVar<PathBuf>> {
1608 let path: ReadVar<PathBuf> = ReadVar {
1609 backing_var: ReadVarBacking::RuntimeVar {
1610 var: self.backend.borrow_mut().persistent_dir_path_var()?,
1611 is_side_effect: false,
1612 },
1613 _kind: std::marker::PhantomData,
1614 };
1615
1616 let folder_name = self
1617 .backend
1618 .borrow_mut()
1619 .current_node()
1620 .modpath()
1621 .replace("::", "__");
1622
1623 Some(
1624 self.emit_rust_stepv("🌼 Create persistent store dir", |ctx| {
1625 let path = path.claim(ctx);
1626 |rt| {
1627 let dir = rt.read(path).join(folder_name);
1628 fs_err::create_dir_all(&dir)?;
1629 Ok(dir)
1630 }
1631 }),
1632 )
1633 }
1634
1635 pub fn supports_persistent_dir(&mut self) -> bool {
1637 self.backend
1638 .borrow_mut()
1639 .persistent_dir_path_var()
1640 .is_some()
1641 }
1642}
1643
1644pub trait RuntimeVarDb {
1647 fn get_var(&mut self, var_name: &str) -> (Vec<u8>, bool) {
1648 self.try_get_var(var_name)
1649 .unwrap_or_else(|| panic!("db is missing var {}", var_name))
1650 }
1651
1652 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)>;
1653 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>);
1654}
1655
1656impl RuntimeVarDb for Box<dyn RuntimeVarDb> {
1657 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)> {
1658 (**self).try_get_var(var_name)
1659 }
1660
1661 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>) {
1662 (**self).set_var(var_name, is_secret, value)
1663 }
1664}
1665
1666pub mod steps {
1667 pub mod ado {
1668 use crate::node::ClaimedReadVar;
1669 use crate::node::ClaimedWriteVar;
1670 use crate::node::ReadVarBacking;
1671 use serde::Deserialize;
1672 use serde::Serialize;
1673 use std::borrow::Cow;
1674
1675 #[derive(Debug, Clone, Serialize, Deserialize)]
1681 pub struct AdoResourcesRepositoryId {
1682 pub(crate) repo_id: String,
1683 }
1684
1685 impl AdoResourcesRepositoryId {
1686 pub fn new_self() -> Self {
1692 Self {
1693 repo_id: "self".into(),
1694 }
1695 }
1696
1697 pub fn dangerous_get_raw_id(&self) -> &str {
1703 &self.repo_id
1704 }
1705
1706 pub fn dangerous_new(repo_id: &str) -> Self {
1712 Self {
1713 repo_id: repo_id.into(),
1714 }
1715 }
1716 }
1717
1718 #[derive(Clone, Debug, Serialize, Deserialize)]
1723 pub struct AdoRuntimeVar {
1724 is_secret: bool,
1725 ado_var: Cow<'static, str>,
1726 }
1727
1728 #[allow(non_upper_case_globals)]
1729 impl AdoRuntimeVar {
1730 pub const BUILD__SOURCE_BRANCH: AdoRuntimeVar =
1736 AdoRuntimeVar::new("build.SourceBranch");
1737
1738 pub const BUILD__BUILD_NUMBER: AdoRuntimeVar = AdoRuntimeVar::new("build.BuildNumber");
1740
1741 pub const SYSTEM__ACCESS_TOKEN: AdoRuntimeVar =
1743 AdoRuntimeVar::new_secret("System.AccessToken");
1744
1745 pub const SYSTEM__JOB_ATTEMPT: AdoRuntimeVar =
1747 AdoRuntimeVar::new_secret("System.JobAttempt");
1748 }
1749
1750 impl AdoRuntimeVar {
1751 const fn new(s: &'static str) -> Self {
1752 Self {
1753 is_secret: false,
1754 ado_var: Cow::Borrowed(s),
1755 }
1756 }
1757
1758 const fn new_secret(s: &'static str) -> Self {
1759 Self {
1760 is_secret: true,
1761 ado_var: Cow::Borrowed(s),
1762 }
1763 }
1764
1765 pub fn is_secret(&self) -> bool {
1767 self.is_secret
1768 }
1769
1770 pub fn as_raw_var_name(&self) -> String {
1772 self.ado_var.as_ref().into()
1773 }
1774
1775 pub fn dangerous_from_global(ado_var_name: impl AsRef<str>, is_secret: bool) -> Self {
1783 Self {
1784 is_secret,
1785 ado_var: ado_var_name.as_ref().to_owned().into(),
1786 }
1787 }
1788 }
1789
1790 pub fn new_ado_step_services(
1791 fresh_ado_var: &mut dyn FnMut() -> String,
1792 ) -> AdoStepServices<'_> {
1793 AdoStepServices {
1794 fresh_ado_var,
1795 ado_to_rust: Vec::new(),
1796 rust_to_ado: Vec::new(),
1797 }
1798 }
1799
1800 pub struct CompletedAdoStepServices {
1801 pub ado_to_rust: Vec<(String, String, bool)>,
1802 pub rust_to_ado: Vec<(String, String)>,
1803 }
1804
1805 impl CompletedAdoStepServices {
1806 pub fn from_ado_step_services(access: AdoStepServices<'_>) -> Self {
1807 let AdoStepServices {
1808 fresh_ado_var: _,
1809 ado_to_rust,
1810 rust_to_ado,
1811 } = access;
1812
1813 Self {
1814 ado_to_rust,
1815 rust_to_ado,
1816 }
1817 }
1818 }
1819
1820 pub struct AdoStepServices<'a> {
1821 fresh_ado_var: &'a mut dyn FnMut() -> String,
1822 ado_to_rust: Vec<(String, String, bool)>,
1823 rust_to_ado: Vec<(String, String)>,
1824 }
1825
1826 impl AdoStepServices<'_> {
1827 pub fn resolve_repository_id(&self, repo_id: AdoResourcesRepositoryId) -> String {
1830 repo_id.repo_id
1831 }
1832
1833 pub fn set_var(&mut self, var: ClaimedWriteVar<String>, from_ado_var: AdoRuntimeVar) {
1839 self.ado_to_rust.push((
1840 from_ado_var.ado_var.into(),
1841 var.backing_var,
1842 from_ado_var.is_secret,
1843 ))
1844 }
1845
1846 pub fn get_var(&mut self, var: ClaimedReadVar<String>) -> AdoRuntimeVar {
1848 let backing_var = if let ReadVarBacking::RuntimeVar {
1849 var,
1850 is_side_effect,
1851 } = &var.backing_var
1852 {
1853 assert!(!is_side_effect);
1854 var
1855 } else {
1856 todo!("support inline ado read vars")
1857 };
1858
1859 let new_ado_var_name = (self.fresh_ado_var)();
1860
1861 self.rust_to_ado
1862 .push((backing_var.clone(), new_ado_var_name.clone()));
1863 AdoRuntimeVar::dangerous_from_global(new_ado_var_name, false)
1864 }
1865 }
1866 }
1867
1868 pub mod github {
1869 use crate::node::ClaimVar;
1870 use crate::node::NodeCtx;
1871 use crate::node::ReadVar;
1872 use crate::node::ReadVarBacking;
1873 use crate::node::SideEffect;
1874 use crate::node::StepCtx;
1875 use crate::node::VarClaimed;
1876 use crate::node::VarNotClaimed;
1877 use crate::node::WriteVar;
1878 use std::collections::BTreeMap;
1879
1880 pub struct GhStepBuilder {
1881 display_name: String,
1882 cond: Option<ReadVar<bool>>,
1883 uses: String,
1884 with: Option<BTreeMap<String, GhParam>>,
1885 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1886 run_after: Vec<ReadVar<SideEffect>>,
1887 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1888 }
1889
1890 impl GhStepBuilder {
1891 pub fn new(display_name: impl AsRef<str>, uses: impl AsRef<str>) -> Self {
1906 Self {
1907 display_name: display_name.as_ref().into(),
1908 cond: None,
1909 uses: uses.as_ref().into(),
1910 with: None,
1911 outputs: BTreeMap::new(),
1912 run_after: Vec::new(),
1913 permissions: BTreeMap::new(),
1914 }
1915 }
1916
1917 pub fn condition(mut self, cond: ReadVar<bool>) -> Self {
1924 self.cond = Some(cond);
1925 self
1926 }
1927
1928 pub fn with(mut self, k: impl AsRef<str>, v: impl Into<GhParam>) -> Self {
1954 self.with.get_or_insert_with(BTreeMap::new);
1955 if let Some(with) = &mut self.with {
1956 with.insert(k.as_ref().to_string(), v.into());
1957 }
1958 self
1959 }
1960
1961 pub fn output(mut self, k: impl AsRef<str>, v: WriteVar<String>) -> Self {
1970 self.outputs
1971 .entry(k.as_ref().to_string())
1972 .or_default()
1973 .push(v);
1974 self
1975 }
1976
1977 pub fn run_after(mut self, side_effect: ReadVar<SideEffect>) -> Self {
1979 self.run_after.push(side_effect);
1980 self
1981 }
1982
1983 pub fn requires_permission(
1988 mut self,
1989 perm: GhPermission,
1990 value: GhPermissionValue,
1991 ) -> Self {
1992 self.permissions.insert(perm, value);
1993 self
1994 }
1995
1996 #[track_caller]
1998 pub fn finish(self, ctx: &mut NodeCtx<'_>) -> ReadVar<SideEffect> {
1999 let (side_effect, claim_side_effect) = ctx.new_prefixed_var("auto_se");
2000 ctx.backend
2001 .borrow_mut()
2002 .on_claimed_runtime_var(&claim_side_effect.backing_var, false);
2003
2004 ctx.emit_gh_step_inner(
2005 self.display_name,
2006 self.cond,
2007 self.uses,
2008 self.with,
2009 self.outputs,
2010 self.run_after,
2011 self.permissions,
2012 );
2013
2014 side_effect
2015 }
2016 }
2017
2018 #[derive(Clone, Debug)]
2019 pub enum GhParam<C = VarNotClaimed> {
2020 Static(String),
2021 FloweyVar(ReadVar<String, C>),
2022 }
2023
2024 impl From<String> for GhParam {
2025 fn from(param: String) -> GhParam {
2026 GhParam::Static(param)
2027 }
2028 }
2029
2030 impl From<&str> for GhParam {
2031 fn from(param: &str) -> GhParam {
2032 GhParam::Static(param.to_string())
2033 }
2034 }
2035
2036 impl From<ReadVar<String>> for GhParam {
2037 fn from(param: ReadVar<String>) -> GhParam {
2038 GhParam::FloweyVar(param)
2039 }
2040 }
2041
2042 pub type ClaimedGhParam = GhParam<VarClaimed>;
2043
2044 impl ClaimVar for GhParam {
2045 type Claimed = ClaimedGhParam;
2046
2047 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedGhParam {
2048 match self {
2049 GhParam::Static(s) => ClaimedGhParam::Static(s),
2050 GhParam::FloweyVar(var) => match &var.backing_var {
2051 ReadVarBacking::RuntimeVar { is_side_effect, .. } => {
2052 assert!(!is_side_effect);
2053 ClaimedGhParam::FloweyVar(var.claim(ctx))
2054 }
2055 ReadVarBacking::Inline(var) => ClaimedGhParam::Static(var.clone()),
2056 },
2057 }
2058 }
2059 }
2060
2061 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2066 pub enum GhPermissionValue {
2067 Read,
2068 Write,
2069 None,
2070 }
2071
2072 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2078 pub enum GhPermission {
2079 Actions,
2080 Attestations,
2081 Checks,
2082 Contents,
2083 Deployments,
2084 Discussions,
2085 IdToken,
2086 Issues,
2087 Packages,
2088 Pages,
2089 PullRequests,
2090 RepositoryProjects,
2091 SecurityEvents,
2092 Statuses,
2093 }
2094 }
2095
2096 pub mod rust {
2097 use crate::node::ClaimedWriteVar;
2098 use crate::node::FlowArch;
2099 use crate::node::FlowBackend;
2100 use crate::node::FlowPlatform;
2101 use crate::node::ReadVarValue;
2102 use crate::node::RuntimeVarDb;
2103 use serde::Serialize;
2104 use serde::de::DeserializeOwned;
2105
2106 pub fn new_rust_runtime_services(
2107 runtime_var_db: &mut dyn RuntimeVarDb,
2108 backend: FlowBackend,
2109 platform: FlowPlatform,
2110 arch: FlowArch,
2111 ) -> RustRuntimeServices<'_> {
2112 RustRuntimeServices {
2113 runtime_var_db,
2114 backend,
2115 platform,
2116 arch,
2117 has_read_secret: false,
2118 }
2119 }
2120
2121 pub struct RustRuntimeServices<'a> {
2122 runtime_var_db: &'a mut dyn RuntimeVarDb,
2123 backend: FlowBackend,
2124 platform: FlowPlatform,
2125 arch: FlowArch,
2126 has_read_secret: bool,
2127 }
2128
2129 impl RustRuntimeServices<'_> {
2130 pub fn backend(&self) -> FlowBackend {
2133 self.backend
2134 }
2135
2136 pub fn platform(&self) -> FlowPlatform {
2139 self.platform
2140 }
2141
2142 pub fn arch(&self) -> FlowArch {
2144 self.arch
2145 }
2146
2147 pub fn write<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2155 where
2156 T: Serialize + DeserializeOwned,
2157 {
2158 self.write_maybe_secret(var, val, self.has_read_secret)
2159 }
2160
2161 pub fn write_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2167 where
2168 T: Serialize + DeserializeOwned,
2169 {
2170 self.write_maybe_secret(var, val, true)
2171 }
2172
2173 pub fn write_not_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2180 where
2181 T: Serialize + DeserializeOwned,
2182 {
2183 self.write_maybe_secret(var, val, false)
2184 }
2185
2186 fn write_maybe_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T, is_secret: bool)
2187 where
2188 T: Serialize + DeserializeOwned,
2189 {
2190 let val = if var.is_side_effect {
2191 b"null".to_vec()
2192 } else {
2193 serde_json::to_vec(val).expect("improve this error path")
2194 };
2195 self.runtime_var_db
2196 .set_var(&var.backing_var, is_secret, val);
2197 }
2198
2199 pub fn write_all<T>(
2200 &mut self,
2201 vars: impl IntoIterator<Item = ClaimedWriteVar<T>>,
2202 val: &T,
2203 ) where
2204 T: Serialize + DeserializeOwned,
2205 {
2206 for var in vars {
2207 self.write(var, val)
2208 }
2209 }
2210
2211 pub fn read<T: ReadVarValue>(&mut self, var: T) -> T::Value {
2212 var.read_value(self)
2213 }
2214
2215 pub(crate) fn get_var(&mut self, var: &str, is_side_effect: bool) -> Vec<u8> {
2216 let (v, is_secret) = self.runtime_var_db.get_var(var);
2217 self.has_read_secret |= is_secret && !is_side_effect;
2218 v
2219 }
2220
2221 pub fn dangerous_gh_set_global_env_var(
2228 &mut self,
2229 var: String,
2230 gh_env_var: String,
2231 ) -> anyhow::Result<()> {
2232 if !matches!(self.backend, FlowBackend::Github) {
2233 return Err(anyhow::anyhow!(
2234 "dangerous_set_gh_env_var can only be used on GitHub Actions"
2235 ));
2236 }
2237
2238 let gh_env_file_path = std::env::var("GITHUB_ENV")?;
2239 let mut gh_env_file = fs_err::OpenOptions::new()
2240 .append(true)
2241 .open(gh_env_file_path)?;
2242 let gh_env_var_assignment = format!(
2243 r#"{}<<EOF
2244{}
2245EOF
2246"#,
2247 gh_env_var, var
2248 );
2249 std::io::Write::write_all(&mut gh_env_file, gh_env_var_assignment.as_bytes())?;
2250
2251 Ok(())
2252 }
2253 }
2254 }
2255}
2256
2257pub trait FlowNodeBase {
2262 type Request: Serialize + DeserializeOwned;
2263
2264 fn imports(&mut self, ctx: &mut ImportCtx<'_>);
2265 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2266
2267 fn i_know_what_im_doing_with_this_manual_impl(&mut self);
2273}
2274
2275pub mod erased {
2276 use crate::node::FlowNodeBase;
2277 use crate::node::NodeCtx;
2278 use crate::node::user_facing::*;
2279
2280 pub struct ErasedNode<N: FlowNodeBase>(pub N);
2281
2282 impl<N: FlowNodeBase> ErasedNode<N> {
2283 pub fn from_node(node: N) -> Self {
2284 Self(node)
2285 }
2286 }
2287
2288 impl<N> FlowNodeBase for ErasedNode<N>
2289 where
2290 N: FlowNodeBase,
2291 {
2292 type Request = Box<[u8]>;
2294
2295 fn imports(&mut self, ctx: &mut ImportCtx<'_>) {
2296 self.0.imports(ctx)
2297 }
2298
2299 fn emit(&mut self, requests: Vec<Box<[u8]>>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
2300 let mut converted_requests = Vec::new();
2301 for req in requests {
2302 converted_requests.push(serde_json::from_slice(&req)?)
2303 }
2304
2305 self.0.emit(converted_requests, ctx)
2306 }
2307
2308 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2309 }
2310}
2311
2312#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2314pub struct NodeHandle(std::any::TypeId);
2315
2316impl Ord for NodeHandle {
2317 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2318 self.modpath().cmp(other.modpath())
2319 }
2320}
2321
2322impl PartialOrd for NodeHandle {
2323 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2324 Some(self.cmp(other))
2325 }
2326}
2327
2328impl std::fmt::Debug for NodeHandle {
2329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2330 std::fmt::Debug::fmt(&self.try_modpath(), f)
2331 }
2332}
2333
2334impl NodeHandle {
2335 pub fn from_type<N: FlowNodeBase + 'static>() -> NodeHandle {
2336 NodeHandle(std::any::TypeId::of::<N>())
2337 }
2338
2339 pub fn from_modpath(modpath: &str) -> NodeHandle {
2340 node_luts::erased_node_by_modpath().get(modpath).unwrap().0
2341 }
2342
2343 pub fn try_from_modpath(modpath: &str) -> Option<NodeHandle> {
2344 node_luts::erased_node_by_modpath()
2345 .get(modpath)
2346 .map(|(s, _)| *s)
2347 }
2348
2349 pub fn new_erased_node(&self) -> Box<dyn FlowNodeBase<Request = Box<[u8]>>> {
2350 let ctor = node_luts::erased_node_by_typeid().get(self).unwrap();
2351 ctor()
2352 }
2353
2354 pub fn modpath(&self) -> &'static str {
2355 node_luts::modpath_by_node_typeid().get(self).unwrap()
2356 }
2357
2358 pub fn try_modpath(&self) -> Option<&'static str> {
2359 node_luts::modpath_by_node_typeid().get(self).cloned()
2360 }
2361
2362 pub fn dummy() -> NodeHandle {
2365 NodeHandle(std::any::TypeId::of::<()>())
2366 }
2367}
2368
2369pub fn list_all_registered_nodes() -> impl Iterator<Item = NodeHandle> {
2370 node_luts::modpath_by_node_typeid().keys().cloned()
2371}
2372
2373mod node_luts {
2388 use super::FlowNodeBase;
2389 use super::NodeHandle;
2390 use std::collections::HashMap;
2391 use std::sync::OnceLock;
2392
2393 pub(super) fn modpath_by_node_typeid() -> &'static HashMap<NodeHandle, &'static str> {
2394 static TYPEID_TO_MODPATH: OnceLock<HashMap<NodeHandle, &'static str>> = OnceLock::new();
2395
2396 let lookup = TYPEID_TO_MODPATH.get_or_init(|| {
2397 let mut lookup = HashMap::new();
2398 for crate::node::private::FlowNodeMeta {
2399 module_path,
2400 ctor: _,
2401 get_typeid,
2402 } in crate::node::private::FLOW_NODES
2403 {
2404 let existing = lookup.insert(
2405 NodeHandle(get_typeid()),
2406 module_path
2407 .strip_suffix("::_only_one_call_to_flowey_node_per_module")
2408 .unwrap(),
2409 );
2410 assert!(existing.is_none())
2413 }
2414
2415 lookup
2416 });
2417
2418 lookup
2419 }
2420
2421 pub(super) fn erased_node_by_typeid()
2422 -> &'static HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>> {
2423 static LOOKUP: OnceLock<
2424 HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>>,
2425 > = OnceLock::new();
2426
2427 let lookup = LOOKUP.get_or_init(|| {
2428 let mut lookup = HashMap::new();
2429 for crate::node::private::FlowNodeMeta {
2430 module_path: _,
2431 ctor,
2432 get_typeid,
2433 } in crate::node::private::FLOW_NODES
2434 {
2435 let existing = lookup.insert(NodeHandle(get_typeid()), *ctor);
2436 assert!(existing.is_none())
2439 }
2440
2441 lookup
2442 });
2443
2444 lookup
2445 }
2446
2447 pub(super) fn erased_node_by_modpath() -> &'static HashMap<
2448 &'static str,
2449 (
2450 NodeHandle,
2451 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2452 ),
2453 > {
2454 static MODPATH_LOOKUP: OnceLock<
2455 HashMap<
2456 &'static str,
2457 (
2458 NodeHandle,
2459 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2460 ),
2461 >,
2462 > = OnceLock::new();
2463
2464 let lookup = MODPATH_LOOKUP.get_or_init(|| {
2465 let mut lookup = HashMap::new();
2466 for crate::node::private::FlowNodeMeta { module_path, ctor, get_typeid } in crate::node::private::FLOW_NODES {
2467 let existing = lookup.insert(module_path.strip_suffix("::_only_one_call_to_flowey_node_per_module").unwrap(), (NodeHandle(get_typeid()), *ctor));
2468 if existing.is_some() {
2469 panic!("conflicting node registrations at {module_path}! please ensure there is a single node per module!")
2470 }
2471 }
2472 lookup
2473 });
2474
2475 lookup
2476 }
2477}
2478
2479#[doc(hidden)]
2480pub mod private {
2481 pub use linkme;
2482
2483 pub struct FlowNodeMeta {
2484 pub module_path: &'static str,
2485 pub ctor: fn() -> Box<dyn super::FlowNodeBase<Request = Box<[u8]>>>,
2486 pub get_typeid: fn() -> std::any::TypeId,
2488 }
2489
2490 #[linkme::distributed_slice]
2491 pub static FLOW_NODES: [FlowNodeMeta] = [..];
2492
2493 #[expect(unsafe_code)]
2495 #[linkme::distributed_slice(FLOW_NODES)]
2496 static DUMMY_FLOW_NODE: FlowNodeMeta = FlowNodeMeta {
2497 module_path: "<dummy>::_only_one_call_to_flowey_node_per_module",
2498 ctor: || unreachable!(),
2499 get_typeid: std::any::TypeId::of::<()>,
2500 };
2501}
2502
2503#[doc(hidden)]
2504#[macro_export]
2505macro_rules! new_flow_node_base {
2506 (struct Node) => {
2507 #[non_exhaustive]
2509 pub struct Node;
2510
2511 mod _only_one_call_to_flowey_node_per_module {
2512 const _: () = {
2513 use $crate::node::private::linkme;
2514
2515 fn new_erased() -> Box<dyn $crate::node::FlowNodeBase<Request = Box<[u8]>>> {
2516 Box::new($crate::node::erased::ErasedNode(super::Node))
2517 }
2518
2519 #[linkme::distributed_slice($crate::node::private::FLOW_NODES)]
2520 #[linkme(crate = linkme)]
2521 static FLOW_NODE: $crate::node::private::FlowNodeMeta =
2522 $crate::node::private::FlowNodeMeta {
2523 module_path: module_path!(),
2524 ctor: new_erased,
2525 get_typeid: std::any::TypeId::of::<super::Node>,
2526 };
2527 };
2528 }
2529 };
2530}
2531
2532pub trait FlowNode {
2534 type Request: Serialize + DeserializeOwned;
2536
2537 fn imports(ctx: &mut ImportCtx<'_>);
2549
2550 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2553}
2554
2555#[macro_export]
2556macro_rules! new_flow_node {
2557 (struct Node) => {
2558 $crate::new_flow_node_base!(struct Node);
2559
2560 impl $crate::node::FlowNodeBase for Node
2561 where
2562 Node: FlowNode,
2563 {
2564 type Request = <Node as FlowNode>::Request;
2565
2566 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2567 <Node as FlowNode>::imports(dep)
2568 }
2569
2570 fn emit(
2571 &mut self,
2572 requests: Vec<Self::Request>,
2573 ctx: &mut $crate::node::NodeCtx<'_>,
2574 ) -> anyhow::Result<()> {
2575 <Node as FlowNode>::emit(requests, ctx)
2576 }
2577
2578 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2579 }
2580 };
2581}
2582
2583pub trait SimpleFlowNode {
2604 type Request: Serialize + DeserializeOwned;
2605
2606 fn imports(ctx: &mut ImportCtx<'_>);
2618
2619 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2621}
2622
2623#[macro_export]
2624macro_rules! new_simple_flow_node {
2625 (struct Node) => {
2626 $crate::new_flow_node_base!(struct Node);
2627
2628 impl $crate::node::FlowNodeBase for Node
2629 where
2630 Node: $crate::node::SimpleFlowNode,
2631 {
2632 type Request = <Node as $crate::node::SimpleFlowNode>::Request;
2633
2634 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2635 <Node as $crate::node::SimpleFlowNode>::imports(dep)
2636 }
2637
2638 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut $crate::node::NodeCtx<'_>) -> anyhow::Result<()> {
2639 for req in requests {
2640 <Node as $crate::node::SimpleFlowNode>::process_request(req, ctx)?
2641 }
2642
2643 Ok(())
2644 }
2645
2646 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2647 }
2648 };
2649}
2650
2651pub trait IntoRequest {
2659 type Node: FlowNodeBase;
2660 fn into_request(self) -> <Self::Node as FlowNodeBase>::Request;
2661
2662 #[doc(hidden)]
2665 #[expect(nonstandard_style)]
2666 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self);
2667}
2668
2669#[doc(hidden)]
2670#[macro_export]
2671macro_rules! __flowey_request_inner {
2672 (@emit_struct [$req:ident]
2676 $(#[$a:meta])*
2677 $variant:ident($($tt:tt)*),
2678 $($rest:tt)*
2679 ) => {
2680 $(#[$a])*
2681 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2682 pub struct $variant($($tt)*);
2683
2684 impl IntoRequest for $variant {
2685 type Node = Node;
2686 fn into_request(self) -> $req {
2687 $req::$variant(self)
2688 }
2689 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2690 }
2691
2692 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2693 };
2694 (@emit_struct [$req:ident]
2695 $(#[$a:meta])*
2696 $variant:ident { $($tt:tt)* },
2697 $($rest:tt)*
2698 ) => {
2699 $(#[$a])*
2700 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2701 pub struct $variant {
2702 $($tt)*
2703 }
2704
2705 impl IntoRequest for $variant {
2706 type Node = Node;
2707 fn into_request(self) -> $req {
2708 $req::$variant(self)
2709 }
2710 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2711 }
2712
2713 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2714 };
2715 (@emit_struct [$req:ident]
2716 $(#[$a:meta])*
2717 $variant:ident,
2718 $($rest:tt)*
2719 ) => {
2720 $(#[$a])*
2721 #[derive(Serialize, Deserialize)]
2722 pub struct $variant;
2723
2724 impl IntoRequest for $variant {
2725 type Node = Node;
2726 fn into_request(self) -> $req {
2727 $req::$variant(self)
2728 }
2729 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2730 }
2731
2732 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2733 };
2734 (@emit_struct [$req:ident]
2735 ) => {};
2736
2737 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2741 $(#[$a:meta])*
2742 $variant:ident($($tt:tt)*),
2743 $($rest:tt)*
2744 ) => {
2745 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2746 };
2747 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2748 $(#[$a:meta])*
2749 $variant:ident { $($tt:tt)* },
2750 $($rest:tt)*
2751 ) => {
2752 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2753 };
2754 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2755 $(#[$a:meta])*
2756 $variant:ident,
2757 $($rest:tt)*
2758 ) => {
2759 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2760 };
2761 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2762 ) => {
2763 #[derive(Serialize, Deserialize)]
2764 pub enum $req {$(
2765 $(#[$prev_a])*
2766 $prev(self::req::$prev),
2767 )*}
2768
2769 impl IntoRequest for $req {
2770 type Node = Node;
2771 fn into_request(self) -> $req {
2772 self
2773 }
2774 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2775 }
2776 };
2777}
2778
2779#[macro_export]
2827macro_rules! flowey_request {
2828 (
2829 $(#[$root_a:meta])*
2830 pub enum_struct $req:ident {
2831 $($tt:tt)*
2832 }
2833 ) => {
2834 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*),] $($tt)*);
2835 pub mod req {
2836 use super::*;
2837 $crate::__flowey_request_inner!(@emit_struct [$req] $($tt)*);
2838 }
2839 };
2840
2841 (
2842 $(#[$a:meta])*
2843 pub enum $req:ident {
2844 $($tt:tt)*
2845 }
2846 ) => {
2847 $(#[$a])*
2848 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2849 pub enum $req {
2850 $($tt)*
2851 }
2852
2853 impl $crate::node::IntoRequest for $req {
2854 type Node = Node;
2855 fn into_request(self) -> $req {
2856 self
2857 }
2858 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2859 }
2860 };
2861
2862 (
2863 $(#[$a:meta])*
2864 pub struct $req:ident {
2865 $($tt:tt)*
2866 }
2867 ) => {
2868 $(#[$a])*
2869 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2870 pub struct $req {
2871 $($tt)*
2872 }
2873
2874 impl $crate::node::IntoRequest for $req {
2875 type Node = Node;
2876 fn into_request(self) -> $req {
2877 self
2878 }
2879 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2880 }
2881 };
2882
2883 (
2884 $(#[$a:meta])*
2885 pub struct $req:ident($($tt:tt)*);
2886 ) => {
2887 $(#[$a])*
2888 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2889 pub struct $req($($tt)*);
2890
2891 impl $crate::node::IntoRequest for $req {
2892 type Node = Node;
2893 fn into_request(self) -> $req {
2894 self
2895 }
2896 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2897 }
2898 };
2899}