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 #[macro_export]
111 macro_rules! match_arch {
112 ($host_arch:expr, $match_arch:pat, $expr:expr) => {
113 if matches!($host_arch, $match_arch) {
114 $expr
115 } else {
116 anyhow::bail!("Linux distro not supported on host arch {}", $host_arch);
117 }
118 };
119 }
120}
121
122pub trait VarEqBacking {
146 fn eq(&self, other: &Self) -> bool;
148}
149
150impl<T> VarEqBacking for WriteVar<T>
151where
152 T: Serialize + DeserializeOwned,
153{
154 fn eq(&self, other: &Self) -> bool {
155 self.backing_var == other.backing_var
156 }
157}
158
159impl<T> VarEqBacking for ReadVar<T>
160where
161 T: Serialize + DeserializeOwned + PartialEq + Eq + Clone,
162{
163 fn eq(&self, other: &Self) -> bool {
164 self.backing_var == other.backing_var
165 }
166}
167
168impl<T, U> VarEqBacking for (T, U)
170where
171 T: VarEqBacking,
172 U: VarEqBacking,
173{
174 fn eq(&self, other: &Self) -> bool {
175 (self.0.eq(&other.0)) && (self.1.eq(&other.1))
176 }
177}
178
179pub type SideEffect = ();
186
187#[derive(Clone, Debug, Serialize, Deserialize)]
190pub enum VarNotClaimed {}
191
192#[derive(Clone, Debug, Serialize, Deserialize)]
195pub enum VarClaimed {}
196
197#[derive(Debug, Serialize, Deserialize)]
217pub struct WriteVar<T: Serialize + DeserializeOwned, C = VarNotClaimed> {
218 backing_var: String,
219 is_side_effect: bool,
222
223 #[serde(skip)]
224 _kind: core::marker::PhantomData<(T, C)>,
225}
226
227pub type ClaimedWriteVar<T> = WriteVar<T, VarClaimed>;
230
231impl<T: Serialize + DeserializeOwned> WriteVar<T, VarNotClaimed> {
232 fn into_claimed(self) -> WriteVar<T, VarClaimed> {
234 let Self {
235 backing_var,
236 is_side_effect,
237 _kind,
238 } = self;
239
240 WriteVar {
241 backing_var,
242 is_side_effect,
243 _kind: std::marker::PhantomData,
244 }
245 }
246
247 #[track_caller]
249 pub fn write_static(self, ctx: &mut NodeCtx<'_>, val: T)
250 where
251 T: 'static,
252 {
253 let val = ReadVar::from_static(val);
254 val.write_into(ctx, self, |v| v);
255 }
256
257 pub(crate) fn into_json(self) -> WriteVar<serde_json::Value> {
258 WriteVar {
259 backing_var: self.backing_var,
260 is_side_effect: self.is_side_effect,
261 _kind: std::marker::PhantomData,
262 }
263 }
264}
265
266impl WriteVar<SideEffect, VarNotClaimed> {
267 pub fn discard_result<T: Serialize + DeserializeOwned>(self) -> WriteVar<T> {
272 WriteVar {
273 backing_var: self.backing_var,
274 is_side_effect: true,
275 _kind: std::marker::PhantomData,
276 }
277 }
278}
279
280pub trait ClaimVar {
288 type Claimed;
290 fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed;
292}
293
294pub trait ReadVarValue {
300 type Value;
302 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value;
304}
305
306impl<T: Serialize + DeserializeOwned> ClaimVar for ReadVar<T> {
307 type Claimed = ClaimedReadVar<T>;
308
309 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedReadVar<T> {
310 if let ReadVarBacking::RuntimeVar {
311 var,
312 is_side_effect: _,
313 } = &self.backing_var
314 {
315 ctx.backend.borrow_mut().on_claimed_runtime_var(var, true);
316 }
317 self.into_claimed()
318 }
319}
320
321impl<T: Serialize + DeserializeOwned> ClaimVar for WriteVar<T> {
322 type Claimed = ClaimedWriteVar<T>;
323
324 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedWriteVar<T> {
325 ctx.backend
326 .borrow_mut()
327 .on_claimed_runtime_var(&self.backing_var, false);
328 self.into_claimed()
329 }
330}
331
332impl<T: Serialize + DeserializeOwned> ReadVarValue for ClaimedReadVar<T> {
333 type Value = T;
334
335 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
336 match self.backing_var {
337 ReadVarBacking::RuntimeVar {
338 var,
339 is_side_effect,
340 } => {
341 let data = rt.get_var(&var, is_side_effect);
343 if is_side_effect {
344 serde_json::from_slice(b"null").expect("should be deserializing into ()")
348 } else {
349 serde_json::from_slice(&data).expect("improve this error path")
351 }
352 }
353 ReadVarBacking::Inline(val) => val,
354 }
355 }
356}
357
358impl<T: ClaimVar> ClaimVar for Vec<T> {
359 type Claimed = Vec<T::Claimed>;
360
361 fn claim(self, ctx: &mut StepCtx<'_>) -> Vec<T::Claimed> {
362 self.into_iter().map(|v| v.claim(ctx)).collect()
363 }
364}
365
366impl<T: ReadVarValue> ReadVarValue for Vec<T> {
367 type Value = Vec<T::Value>;
368
369 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
370 self.into_iter().map(|v| v.read_value(rt)).collect()
371 }
372}
373
374impl<T: ClaimVar> ClaimVar for Option<T> {
375 type Claimed = Option<T::Claimed>;
376
377 fn claim(self, ctx: &mut StepCtx<'_>) -> Option<T::Claimed> {
378 self.map(|x| x.claim(ctx))
379 }
380}
381
382impl<T: ReadVarValue> ReadVarValue for Option<T> {
383 type Value = Option<T::Value>;
384
385 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
386 self.map(|x| x.read_value(rt))
387 }
388}
389
390impl<U: Ord, T: ClaimVar> ClaimVar for BTreeMap<U, T> {
391 type Claimed = BTreeMap<U, T::Claimed>;
392
393 fn claim(self, ctx: &mut StepCtx<'_>) -> BTreeMap<U, T::Claimed> {
394 self.into_iter().map(|(k, v)| (k, v.claim(ctx))).collect()
395 }
396}
397
398impl<U: Ord, T: ReadVarValue> ReadVarValue for BTreeMap<U, T> {
399 type Value = BTreeMap<U, T::Value>;
400
401 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
402 self.into_iter()
403 .map(|(k, v)| (k, v.read_value(rt)))
404 .collect()
405 }
406}
407
408macro_rules! impl_tuple_claim {
409 ($($T:tt)*) => {
410 impl<$($T,)*> $crate::node::ClaimVar for ($($T,)*)
411 where
412 $($T: $crate::node::ClaimVar,)*
413 {
414 type Claimed = ($($T::Claimed,)*);
415
416 #[expect(non_snake_case)]
417 fn claim(self, ctx: &mut $crate::node::StepCtx<'_>) -> Self::Claimed {
418 let ($($T,)*) = self;
419 ($($T.claim(ctx),)*)
420 }
421 }
422
423 impl<$($T,)*> $crate::node::ReadVarValue for ($($T,)*)
424 where
425 $($T: $crate::node::ReadVarValue,)*
426 {
427 type Value = ($($T::Value,)*);
428
429 #[expect(non_snake_case)]
430 fn read_value(self, rt: &mut $crate::node::RustRuntimeServices<'_>) -> Self::Value {
431 let ($($T,)*) = self;
432 ($($T.read_value(rt),)*)
433 }
434 }
435 };
436}
437
438impl_tuple_claim!(A B C D E F G H I J);
439impl_tuple_claim!(A B C D E F G H I);
440impl_tuple_claim!(A B C D E F G H);
441impl_tuple_claim!(A B C D E F G);
442impl_tuple_claim!(A B C D E F);
443impl_tuple_claim!(A B C D E);
444impl_tuple_claim!(A B C D);
445impl_tuple_claim!(A B C);
446impl_tuple_claim!(A B);
447impl_tuple_claim!(A);
448
449impl ClaimVar for () {
450 type Claimed = ();
451
452 fn claim(self, _ctx: &mut StepCtx<'_>) -> Self::Claimed {}
453}
454
455impl ReadVarValue for () {
456 type Value = ();
457
458 fn read_value(self, _rt: &mut RustRuntimeServices<'_>) -> Self::Value {}
459}
460
461#[derive(Serialize, Deserialize, Clone)]
466pub struct GhUserSecretVar(pub(crate) String);
467
468#[derive(Debug, Serialize, Deserialize)]
487pub struct ReadVar<T, C = VarNotClaimed> {
488 backing_var: ReadVarBacking<T>,
489 #[serde(skip)]
490 _kind: std::marker::PhantomData<C>,
491}
492
493pub type ClaimedReadVar<T> = ReadVar<T, VarClaimed>;
496
497impl<T: Serialize + DeserializeOwned, C> Clone for ReadVar<T, C> {
499 fn clone(&self) -> Self {
500 ReadVar {
501 backing_var: self.backing_var.clone(),
502 _kind: std::marker::PhantomData,
503 }
504 }
505}
506
507#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
508enum ReadVarBacking<T> {
509 RuntimeVar {
510 var: String,
511 is_side_effect: bool,
518 },
519 Inline(T),
520}
521
522impl<T: Serialize + DeserializeOwned> Clone for ReadVarBacking<T> {
524 fn clone(&self) -> Self {
525 match self {
526 Self::RuntimeVar {
527 var,
528 is_side_effect,
529 } => Self::RuntimeVar {
530 var: var.clone(),
531 is_side_effect: *is_side_effect,
532 },
533 Self::Inline(v) => {
534 Self::Inline(serde_json::from_value(serde_json::to_value(v).unwrap()).unwrap())
535 }
536 }
537 }
538}
539
540impl<T: Serialize + DeserializeOwned> ReadVar<T> {
541 fn into_claimed(self) -> ReadVar<T, VarClaimed> {
543 let Self { backing_var, _kind } = self;
544
545 ReadVar {
546 backing_var,
547 _kind: std::marker::PhantomData,
548 }
549 }
550
551 #[must_use]
560 pub fn into_side_effect(self) -> ReadVar<SideEffect> {
561 ReadVar {
562 backing_var: match self.backing_var {
563 ReadVarBacking::RuntimeVar {
564 var,
565 is_side_effect: _,
566 } => ReadVarBacking::RuntimeVar {
567 var,
568 is_side_effect: true,
569 },
570 ReadVarBacking::Inline(_) => ReadVarBacking::Inline(()),
571 },
572 _kind: std::marker::PhantomData,
573 }
574 }
575
576 #[track_caller]
579 #[must_use]
580 pub fn map<F, U>(&self, ctx: &mut NodeCtx<'_>, f: F) -> ReadVar<U>
581 where
582 T: 'static,
583 U: Serialize + DeserializeOwned + 'static,
584 F: FnOnce(T) -> U + 'static,
585 {
586 let (read_from, write_into) = ctx.new_var();
587 self.write_into(ctx, write_into, f);
588 read_from
589 }
590
591 #[track_caller]
594 pub fn write_into<F, U>(&self, ctx: &mut NodeCtx<'_>, write_into: WriteVar<U>, f: F)
595 where
596 T: 'static,
597 U: Serialize + DeserializeOwned + 'static,
598 F: FnOnce(T) -> U + 'static,
599 {
600 let this = self.clone();
601 ctx.emit_minor_rust_step("🌼 write_into Var", move |ctx| {
602 let this = this.claim(ctx);
603 let write_into = write_into.claim(ctx);
604 move |rt| {
605 let this = rt.read(this);
606 rt.write(write_into, &f(this));
607 }
608 });
609 }
610
611 #[track_caller]
614 #[must_use]
615 pub fn zip<U>(&self, ctx: &mut NodeCtx<'_>, other: ReadVar<U>) -> ReadVar<(T, U)>
616 where
617 T: 'static,
618 U: Serialize + DeserializeOwned + 'static,
619 {
620 let (read_from, write_into) = ctx.new_var();
621 let this = self.clone();
622 ctx.emit_minor_rust_step("🌼 Zip Vars", move |ctx| {
623 let this = this.claim(ctx);
624 let other = other.claim(ctx);
625 let write_into = write_into.claim(ctx);
626 move |rt| {
627 let this = rt.read(this);
628 let other = rt.read(other);
629 rt.write(write_into, &(this, other));
630 }
631 });
632 read_from
633 }
634
635 #[track_caller]
640 #[must_use]
641 pub fn from_static(val: T) -> ReadVar<T>
642 where
643 T: 'static,
644 {
645 ReadVar {
646 backing_var: ReadVarBacking::Inline(val),
647 _kind: std::marker::PhantomData,
648 }
649 }
650
651 pub fn get_static(&self) -> Option<T> {
660 match self.clone().backing_var {
661 ReadVarBacking::Inline(v) => Some(v),
662 _ => None,
663 }
664 }
665
666 #[track_caller]
668 #[must_use]
669 pub fn transpose_vec(ctx: &mut NodeCtx<'_>, vec: Vec<ReadVar<T>>) -> ReadVar<Vec<T>>
670 where
671 T: 'static,
672 {
673 let (read_from, write_into) = ctx.new_var();
674 ctx.emit_minor_rust_step("🌼 Transpose Vec<ReadVar<T>>", move |ctx| {
675 let vec = vec.claim(ctx);
676 let write_into = write_into.claim(ctx);
677 move |rt| {
678 let mut v = Vec::new();
679 for var in vec {
680 v.push(rt.read(var));
681 }
682 rt.write(write_into, &v);
683 }
684 });
685 read_from
686 }
687
688 #[must_use]
704 pub fn depending_on<U>(&self, ctx: &mut NodeCtx<'_>, other: &ReadVar<U>) -> Self
705 where
706 T: 'static,
707 U: Serialize + DeserializeOwned + 'static,
708 {
709 ctx.emit_minor_rust_stepv("🌼 Add dependency", |ctx| {
712 let this = self.clone().claim(ctx);
713 other.clone().claim(ctx);
714 move |rt| rt.read(this)
715 })
716 }
717
718 pub fn claim_unused(self, ctx: &mut NodeCtx<'_>) {
721 match self.backing_var {
722 ReadVarBacking::RuntimeVar {
723 var,
724 is_side_effect: _,
725 } => ctx.backend.borrow_mut().on_unused_read_var(&var),
726 ReadVarBacking::Inline(_) => {}
727 }
728 }
729
730 pub(crate) fn into_json(self) -> ReadVar<serde_json::Value> {
731 match self.backing_var {
732 ReadVarBacking::RuntimeVar {
733 var,
734 is_side_effect,
735 } => ReadVar {
736 backing_var: ReadVarBacking::RuntimeVar {
737 var,
738 is_side_effect,
739 },
740 _kind: std::marker::PhantomData,
741 },
742 ReadVarBacking::Inline(v) => ReadVar {
743 backing_var: ReadVarBacking::Inline(serde_json::to_value(v).unwrap()),
744 _kind: std::marker::PhantomData,
745 },
746 }
747 }
748}
749
750#[must_use]
756pub fn thin_air_read_runtime_var<T>(backing_var: String) -> ReadVar<T>
757where
758 T: Serialize + DeserializeOwned,
759{
760 ReadVar {
761 backing_var: ReadVarBacking::RuntimeVar {
762 var: backing_var,
763 is_side_effect: false,
764 },
765 _kind: std::marker::PhantomData,
766 }
767}
768
769#[must_use]
775pub fn thin_air_write_runtime_var<T>(backing_var: String) -> WriteVar<T>
776where
777 T: Serialize + DeserializeOwned,
778{
779 WriteVar {
780 backing_var,
781 is_side_effect: false,
782 _kind: std::marker::PhantomData,
783 }
784}
785
786pub fn read_var_internals<T: Serialize + DeserializeOwned, C>(
792 var: &ReadVar<T, C>,
793) -> (Option<String>, bool) {
794 match var.backing_var {
795 ReadVarBacking::RuntimeVar {
796 var: ref s,
797 is_side_effect,
798 } => (Some(s.clone()), is_side_effect),
799 ReadVarBacking::Inline(_) => (None, false),
800 }
801}
802
803pub trait ImportCtxBackend {
804 fn on_possible_dep(&mut self, node_handle: NodeHandle);
805}
806
807pub struct ImportCtx<'a> {
809 backend: &'a mut dyn ImportCtxBackend,
810}
811
812impl ImportCtx<'_> {
813 pub fn import<N: FlowNodeBase + 'static>(&mut self) {
815 self.backend.on_possible_dep(NodeHandle::from_type::<N>())
816 }
817}
818
819pub fn new_import_ctx(backend: &mut dyn ImportCtxBackend) -> ImportCtx<'_> {
820 ImportCtx { backend }
821}
822
823#[derive(Debug)]
824pub enum CtxAnchor {
825 PostJob,
826}
827
828pub trait NodeCtxBackend {
829 fn current_node(&self) -> NodeHandle;
831
832 fn on_new_var(&mut self) -> String;
837
838 fn on_claimed_runtime_var(&mut self, var: &str, is_read: bool);
840
841 fn on_unused_read_var(&mut self, var: &str);
843
844 fn on_request(&mut self, node_handle: NodeHandle, req: anyhow::Result<Box<[u8]>>);
852
853 fn on_emit_rust_step(
854 &mut self,
855 label: &str,
856 can_merge: bool,
857 code: Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
858 );
859
860 fn on_emit_ado_step(
861 &mut self,
862 label: &str,
863 yaml_snippet: Box<dyn for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String>,
864 inline_script: Option<
865 Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
866 >,
867 condvar: Option<String>,
868 );
869
870 fn on_emit_gh_step(
871 &mut self,
872 label: &str,
873 uses: &str,
874 with: BTreeMap<String, ClaimedGhParam>,
875 condvar: Option<String>,
876 outputs: BTreeMap<String, Vec<GhOutput>>,
877 permissions: BTreeMap<GhPermission, GhPermissionValue>,
878 gh_to_rust: Vec<GhToRust>,
879 rust_to_gh: Vec<RustToGh>,
880 );
881
882 fn on_emit_side_effect_step(&mut self);
883
884 fn backend(&mut self) -> FlowBackend;
885 fn platform(&mut self) -> FlowPlatform;
886 fn arch(&mut self) -> FlowArch;
887
888 fn persistent_dir_path_var(&mut self) -> Option<String>;
892}
893
894pub fn new_node_ctx(backend: &mut dyn NodeCtxBackend) -> NodeCtx<'_> {
895 NodeCtx {
896 backend: Rc::new(RefCell::new(backend)),
897 }
898}
899
900#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
902pub enum FlowBackend {
903 Local,
905 Ado,
907 Github,
909}
910
911#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
913pub enum FlowPlatformKind {
914 Windows,
915 Unix,
916}
917
918#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
920pub enum FlowPlatformLinuxDistro {
921 Fedora,
923 Ubuntu,
925 Arch,
927 Unknown,
929}
930
931#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
933#[non_exhaustive]
934pub enum FlowPlatform {
935 Windows,
937 Linux(FlowPlatformLinuxDistro),
939 MacOs,
941}
942
943impl FlowPlatform {
944 pub fn kind(&self) -> FlowPlatformKind {
945 match self {
946 Self::Windows => FlowPlatformKind::Windows,
947 Self::Linux(_) | Self::MacOs => FlowPlatformKind::Unix,
948 }
949 }
950
951 fn as_str(&self) -> &'static str {
952 match self {
953 Self::Windows => "windows",
954 Self::Linux(_) => "linux",
955 Self::MacOs => "macos",
956 }
957 }
958
959 pub fn exe_suffix(&self) -> &'static str {
961 if self == &Self::Windows { ".exe" } else { "" }
962 }
963
964 pub fn binary(&self, name: &str) -> String {
966 format!("{}{}", name, self.exe_suffix())
967 }
968}
969
970impl std::fmt::Display for FlowPlatform {
971 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
972 f.pad(self.as_str())
973 }
974}
975
976#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
978#[non_exhaustive]
979pub enum FlowArch {
980 X86_64,
981 Aarch64,
982}
983
984impl FlowArch {
985 fn as_str(&self) -> &'static str {
986 match self {
987 Self::X86_64 => "x86_64",
988 Self::Aarch64 => "aarch64",
989 }
990 }
991}
992
993impl std::fmt::Display for FlowArch {
994 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
995 f.pad(self.as_str())
996 }
997}
998
999pub struct StepCtx<'a> {
1001 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
1002}
1003
1004impl StepCtx<'_> {
1005 pub fn backend(&self) -> FlowBackend {
1008 self.backend.borrow_mut().backend()
1009 }
1010
1011 pub fn platform(&self) -> FlowPlatform {
1014 self.backend.borrow_mut().platform()
1015 }
1016}
1017
1018const NO_ADO_INLINE_SCRIPT: Option<
1019 for<'a> fn(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>,
1020> = None;
1021
1022pub struct NodeCtx<'a> {
1024 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
1025}
1026
1027impl<'ctx> NodeCtx<'ctx> {
1028 pub fn emit_rust_step<F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<SideEffect>
1034 where
1035 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1036 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1037 {
1038 self.emit_rust_step_inner(label.as_ref(), false, code)
1039 }
1040
1041 pub fn emit_minor_rust_step<F, G>(
1051 &mut self,
1052 label: impl AsRef<str>,
1053 code: F,
1054 ) -> ReadVar<SideEffect>
1055 where
1056 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1057 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) + 'static,
1058 {
1059 self.emit_rust_step_inner(label.as_ref(), true, |ctx| {
1060 let f = code(ctx);
1061 |rt| {
1062 f(rt);
1063 Ok(())
1064 }
1065 })
1066 }
1067
1068 #[must_use]
1089 #[track_caller]
1090 pub fn emit_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1091 where
1092 T: Serialize + DeserializeOwned + 'static,
1093 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1094 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1095 {
1096 self.emit_rust_stepv_inner(label.as_ref(), false, code)
1097 }
1098
1099 #[must_use]
1123 #[track_caller]
1124 pub fn emit_minor_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1125 where
1126 T: Serialize + DeserializeOwned + 'static,
1127 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1128 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> T + 'static,
1129 {
1130 self.emit_rust_stepv_inner(label.as_ref(), true, |ctx| {
1131 let f = code(ctx);
1132 |rt| Ok(f(rt))
1133 })
1134 }
1135
1136 fn emit_rust_step_inner<F, G>(
1137 &mut self,
1138 label: &str,
1139 can_merge: bool,
1140 code: F,
1141 ) -> ReadVar<SideEffect>
1142 where
1143 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1144 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1145 {
1146 let (read, write) = self.new_prefixed_var("auto_se");
1147
1148 let ctx = &mut StepCtx {
1149 backend: self.backend.clone(),
1150 };
1151 write.claim(ctx);
1152
1153 let code = code(ctx);
1154 self.backend
1155 .borrow_mut()
1156 .on_emit_rust_step(label.as_ref(), can_merge, Box::new(code));
1157 read
1158 }
1159
1160 #[must_use]
1161 #[track_caller]
1162 fn emit_rust_stepv_inner<T, F, G>(
1163 &mut self,
1164 label: impl AsRef<str>,
1165 can_merge: bool,
1166 code: F,
1167 ) -> ReadVar<T>
1168 where
1169 T: Serialize + DeserializeOwned + 'static,
1170 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1171 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1172 {
1173 let (read, write) = self.new_var();
1174
1175 let ctx = &mut StepCtx {
1176 backend: self.backend.clone(),
1177 };
1178 let write = write.claim(ctx);
1179
1180 let code = code(ctx);
1181 self.backend.borrow_mut().on_emit_rust_step(
1182 label.as_ref(),
1183 can_merge,
1184 Box::new(|rt| {
1185 let val = code(rt)?;
1186 rt.write(write, &val);
1187 Ok(())
1188 }),
1189 );
1190 read
1191 }
1192
1193 #[track_caller]
1195 #[must_use]
1196 pub fn get_ado_variable(&mut self, ado_var: AdoRuntimeVar) -> ReadVar<String> {
1197 let (var, write_var) = self.new_var();
1198 self.emit_ado_step(format!("🌼 read {}", ado_var.as_raw_var_name()), |ctx| {
1199 let write_var = write_var.claim(ctx);
1200 |rt| {
1201 rt.set_var(write_var, ado_var);
1202 "".into()
1203 }
1204 });
1205 var
1206 }
1207
1208 pub fn emit_ado_step<F, G>(&mut self, display_name: impl AsRef<str>, 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, None, |ctx| {
1215 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1216 })
1217 }
1218
1219 pub fn emit_ado_step_with_condition<F, G>(
1222 &mut self,
1223 display_name: impl AsRef<str>,
1224 cond: 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, Some(cond), |ctx| {
1231 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1232 })
1233 }
1234
1235 pub fn emit_ado_step_with_condition_optional<F, G>(
1238 &mut self,
1239 display_name: impl AsRef<str>,
1240 cond: Option<ReadVar<bool>>,
1241 yaml_snippet: F,
1242 ) where
1243 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1244 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1245 {
1246 self.emit_ado_step_inner(display_name, cond, |ctx| {
1247 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1248 })
1249 }
1250
1251 pub fn emit_ado_step_with_inline_script<F, G, H>(
1280 &mut self,
1281 display_name: impl AsRef<str>,
1282 yaml_snippet: F,
1283 ) where
1284 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, H),
1285 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1286 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1287 {
1288 self.emit_ado_step_inner(display_name, None, |ctx| {
1289 let (f, g) = yaml_snippet(ctx);
1290 (f, Some(g))
1291 })
1292 }
1293
1294 fn emit_ado_step_inner<F, G, H>(
1295 &mut self,
1296 display_name: impl AsRef<str>,
1297 cond: Option<ReadVar<bool>>,
1298 yaml_snippet: F,
1299 ) where
1300 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, Option<H>),
1301 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1302 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1303 {
1304 let condvar = match cond.map(|c| c.backing_var) {
1305 Some(ReadVarBacking::Inline(cond)) => {
1307 if !cond {
1308 return;
1309 } else {
1310 None
1311 }
1312 }
1313 Some(ReadVarBacking::RuntimeVar {
1314 var,
1315 is_side_effect,
1316 }) => {
1317 assert!(!is_side_effect);
1318 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1319 Some(var)
1320 }
1321 None => None,
1322 };
1323
1324 let (yaml_snippet, inline_script) = yaml_snippet(&mut StepCtx {
1325 backend: self.backend.clone(),
1326 });
1327 self.backend.borrow_mut().on_emit_ado_step(
1328 display_name.as_ref(),
1329 Box::new(yaml_snippet),
1330 if let Some(inline_script) = inline_script {
1331 Some(Box::new(inline_script))
1332 } else {
1333 None
1334 },
1335 condvar,
1336 );
1337 }
1338
1339 #[track_caller]
1341 #[must_use]
1342 pub fn get_gh_context_var(&mut self) -> GhContextVarReader<'ctx, Root> {
1343 GhContextVarReader {
1344 ctx: NodeCtx {
1345 backend: self.backend.clone(),
1346 },
1347 _state: std::marker::PhantomData,
1348 }
1349 }
1350
1351 pub fn emit_gh_step(
1353 &mut self,
1354 display_name: impl AsRef<str>,
1355 uses: impl AsRef<str>,
1356 ) -> GhStepBuilder {
1357 GhStepBuilder::new(display_name, uses)
1358 }
1359
1360 fn emit_gh_step_inner(
1361 &mut self,
1362 display_name: impl AsRef<str>,
1363 cond: Option<ReadVar<bool>>,
1364 uses: impl AsRef<str>,
1365 with: Option<BTreeMap<String, GhParam>>,
1366 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1367 run_after: Vec<ReadVar<SideEffect>>,
1368 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1369 ) {
1370 let condvar = match cond.map(|c| c.backing_var) {
1371 Some(ReadVarBacking::Inline(cond)) => {
1373 if !cond {
1374 return;
1375 } else {
1376 None
1377 }
1378 }
1379 Some(ReadVarBacking::RuntimeVar {
1380 var,
1381 is_side_effect,
1382 }) => {
1383 assert!(!is_side_effect);
1384 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1385 Some(var)
1386 }
1387 None => None,
1388 };
1389
1390 let with = with
1391 .unwrap_or_default()
1392 .into_iter()
1393 .map(|(k, v)| {
1394 (
1395 k.clone(),
1396 v.claim(&mut StepCtx {
1397 backend: self.backend.clone(),
1398 }),
1399 )
1400 })
1401 .collect();
1402
1403 for var in run_after {
1404 var.claim(&mut StepCtx {
1405 backend: self.backend.clone(),
1406 });
1407 }
1408
1409 let outputvars = outputs
1410 .into_iter()
1411 .map(|(name, vars)| {
1412 (
1413 name,
1414 vars.into_iter()
1415 .map(|var| {
1416 let var = var.claim(&mut StepCtx {
1417 backend: self.backend.clone(),
1418 });
1419 GhOutput {
1420 backing_var: var.backing_var,
1421 is_secret: false,
1422 is_object: false,
1423 }
1424 })
1425 .collect(),
1426 )
1427 })
1428 .collect();
1429
1430 self.backend.borrow_mut().on_emit_gh_step(
1431 display_name.as_ref(),
1432 uses.as_ref(),
1433 with,
1434 condvar,
1435 outputvars,
1436 permissions,
1437 Vec::new(),
1438 Vec::new(),
1439 );
1440 }
1441
1442 pub fn emit_side_effect_step(
1450 &mut self,
1451 use_side_effects: impl IntoIterator<Item = ReadVar<SideEffect>>,
1452 resolve_side_effects: impl IntoIterator<Item = WriteVar<SideEffect>>,
1453 ) {
1454 let mut backend = self.backend.borrow_mut();
1455 for var in use_side_effects.into_iter() {
1456 if let ReadVarBacking::RuntimeVar {
1457 var,
1458 is_side_effect: _,
1459 } = &var.backing_var
1460 {
1461 backend.on_claimed_runtime_var(var, true);
1462 }
1463 }
1464
1465 for var in resolve_side_effects.into_iter() {
1466 backend.on_claimed_runtime_var(&var.backing_var, false);
1467 }
1468
1469 backend.on_emit_side_effect_step();
1470 }
1471
1472 pub fn backend(&self) -> FlowBackend {
1475 self.backend.borrow_mut().backend()
1476 }
1477
1478 pub fn platform(&self) -> FlowPlatform {
1481 self.backend.borrow_mut().platform()
1482 }
1483
1484 pub fn arch(&self) -> FlowArch {
1486 self.backend.borrow_mut().arch()
1487 }
1488
1489 pub fn req<R>(&mut self, req: R)
1491 where
1492 R: IntoRequest + 'static,
1493 {
1494 let mut backend = self.backend.borrow_mut();
1495 backend.on_request(
1496 NodeHandle::from_type::<R::Node>(),
1497 serde_json::to_vec(&req.into_request())
1498 .map(Into::into)
1499 .map_err(Into::into),
1500 );
1501 }
1502
1503 #[track_caller]
1506 #[must_use]
1507 pub fn reqv<T, R>(&mut self, f: impl FnOnce(WriteVar<T>) -> R) -> ReadVar<T>
1508 where
1509 T: Serialize + DeserializeOwned,
1510 R: IntoRequest + 'static,
1511 {
1512 let (read, write) = self.new_var();
1513 self.req::<R>(f(write));
1514 read
1515 }
1516
1517 pub fn requests<N>(&mut self, reqs: impl IntoIterator<Item = N::Request>)
1519 where
1520 N: FlowNodeBase + 'static,
1521 {
1522 let mut backend = self.backend.borrow_mut();
1523 for req in reqs.into_iter() {
1524 backend.on_request(
1525 NodeHandle::from_type::<N>(),
1526 serde_json::to_vec(&req).map(Into::into).map_err(Into::into),
1527 );
1528 }
1529 }
1530
1531 #[track_caller]
1534 #[must_use]
1535 pub fn new_var<T>(&self) -> (ReadVar<T>, WriteVar<T>)
1536 where
1537 T: Serialize + DeserializeOwned,
1538 {
1539 self.new_prefixed_var("")
1540 }
1541
1542 #[track_caller]
1543 #[must_use]
1544 fn new_prefixed_var<T>(&self, prefix: &'static str) -> (ReadVar<T>, WriteVar<T>)
1545 where
1546 T: Serialize + DeserializeOwned,
1547 {
1548 let caller = std::panic::Location::caller()
1550 .to_string()
1551 .replace('\\', "/");
1552
1553 let caller = caller
1569 .split_once("flowey/")
1570 .expect("due to a known limitation with flowey, all flowey code must have an ancestor dir called 'flowey/' somewhere in its full path")
1571 .1;
1572
1573 let colon = if prefix.is_empty() { "" } else { ":" };
1574 let ordinal = self.backend.borrow_mut().on_new_var();
1575 let backing_var = format!("{prefix}{colon}{ordinal}:{caller}");
1576
1577 (
1578 ReadVar {
1579 backing_var: ReadVarBacking::RuntimeVar {
1580 var: backing_var.clone(),
1581 is_side_effect: false,
1582 },
1583 _kind: std::marker::PhantomData,
1584 },
1585 WriteVar {
1586 backing_var,
1587 is_side_effect: false,
1588 _kind: std::marker::PhantomData,
1589 },
1590 )
1591 }
1592
1593 #[track_caller]
1604 #[must_use]
1605 pub fn new_post_job_side_effect(&self) -> (ReadVar<SideEffect>, WriteVar<SideEffect>) {
1606 self.new_prefixed_var("post_job")
1607 }
1608
1609 #[track_caller]
1622 #[must_use]
1623 pub fn persistent_dir(&mut self) -> Option<ReadVar<PathBuf>> {
1624 let path: ReadVar<PathBuf> = ReadVar {
1625 backing_var: ReadVarBacking::RuntimeVar {
1626 var: self.backend.borrow_mut().persistent_dir_path_var()?,
1627 is_side_effect: false,
1628 },
1629 _kind: std::marker::PhantomData,
1630 };
1631
1632 let folder_name = self
1633 .backend
1634 .borrow_mut()
1635 .current_node()
1636 .modpath()
1637 .replace("::", "__");
1638
1639 Some(
1640 self.emit_rust_stepv("🌼 Create persistent store dir", |ctx| {
1641 let path = path.claim(ctx);
1642 |rt| {
1643 let dir = rt.read(path).join(folder_name);
1644 fs_err::create_dir_all(&dir)?;
1645 Ok(dir)
1646 }
1647 }),
1648 )
1649 }
1650
1651 pub fn supports_persistent_dir(&mut self) -> bool {
1653 self.backend
1654 .borrow_mut()
1655 .persistent_dir_path_var()
1656 .is_some()
1657 }
1658}
1659
1660pub trait RuntimeVarDb {
1663 fn get_var(&mut self, var_name: &str) -> (Vec<u8>, bool) {
1664 self.try_get_var(var_name)
1665 .unwrap_or_else(|| panic!("db is missing var {}", var_name))
1666 }
1667
1668 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)>;
1669 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>);
1670}
1671
1672impl RuntimeVarDb for Box<dyn RuntimeVarDb> {
1673 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)> {
1674 (**self).try_get_var(var_name)
1675 }
1676
1677 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>) {
1678 (**self).set_var(var_name, is_secret, value)
1679 }
1680}
1681
1682pub mod steps {
1683 pub mod ado {
1684 use crate::node::ClaimedReadVar;
1685 use crate::node::ClaimedWriteVar;
1686 use crate::node::ReadVarBacking;
1687 use serde::Deserialize;
1688 use serde::Serialize;
1689 use std::borrow::Cow;
1690
1691 #[derive(Debug, Clone, Serialize, Deserialize)]
1697 pub struct AdoResourcesRepositoryId {
1698 pub(crate) repo_id: String,
1699 }
1700
1701 impl AdoResourcesRepositoryId {
1702 pub fn new_self() -> Self {
1708 Self {
1709 repo_id: "self".into(),
1710 }
1711 }
1712
1713 pub fn dangerous_get_raw_id(&self) -> &str {
1719 &self.repo_id
1720 }
1721
1722 pub fn dangerous_new(repo_id: &str) -> Self {
1728 Self {
1729 repo_id: repo_id.into(),
1730 }
1731 }
1732 }
1733
1734 #[derive(Clone, Debug, Serialize, Deserialize)]
1739 pub struct AdoRuntimeVar {
1740 is_secret: bool,
1741 ado_var: Cow<'static, str>,
1742 }
1743
1744 #[allow(non_upper_case_globals)]
1745 impl AdoRuntimeVar {
1746 pub const BUILD__SOURCE_BRANCH: AdoRuntimeVar =
1752 AdoRuntimeVar::new("build.SourceBranch");
1753
1754 pub const BUILD__BUILD_NUMBER: AdoRuntimeVar = AdoRuntimeVar::new("build.BuildNumber");
1756
1757 pub const SYSTEM__ACCESS_TOKEN: AdoRuntimeVar =
1759 AdoRuntimeVar::new_secret("System.AccessToken");
1760
1761 pub const SYSTEM__JOB_ATTEMPT: AdoRuntimeVar =
1763 AdoRuntimeVar::new_secret("System.JobAttempt");
1764 }
1765
1766 impl AdoRuntimeVar {
1767 const fn new(s: &'static str) -> Self {
1768 Self {
1769 is_secret: false,
1770 ado_var: Cow::Borrowed(s),
1771 }
1772 }
1773
1774 const fn new_secret(s: &'static str) -> Self {
1775 Self {
1776 is_secret: true,
1777 ado_var: Cow::Borrowed(s),
1778 }
1779 }
1780
1781 pub fn is_secret(&self) -> bool {
1783 self.is_secret
1784 }
1785
1786 pub fn as_raw_var_name(&self) -> String {
1788 self.ado_var.as_ref().into()
1789 }
1790
1791 pub fn dangerous_from_global(ado_var_name: impl AsRef<str>, is_secret: bool) -> Self {
1799 Self {
1800 is_secret,
1801 ado_var: ado_var_name.as_ref().to_owned().into(),
1802 }
1803 }
1804 }
1805
1806 pub fn new_ado_step_services(
1807 fresh_ado_var: &mut dyn FnMut() -> String,
1808 ) -> AdoStepServices<'_> {
1809 AdoStepServices {
1810 fresh_ado_var,
1811 ado_to_rust: Vec::new(),
1812 rust_to_ado: Vec::new(),
1813 }
1814 }
1815
1816 pub struct CompletedAdoStepServices {
1817 pub ado_to_rust: Vec<(String, String, bool)>,
1818 pub rust_to_ado: Vec<(String, String)>,
1819 }
1820
1821 impl CompletedAdoStepServices {
1822 pub fn from_ado_step_services(access: AdoStepServices<'_>) -> Self {
1823 let AdoStepServices {
1824 fresh_ado_var: _,
1825 ado_to_rust,
1826 rust_to_ado,
1827 } = access;
1828
1829 Self {
1830 ado_to_rust,
1831 rust_to_ado,
1832 }
1833 }
1834 }
1835
1836 pub struct AdoStepServices<'a> {
1837 fresh_ado_var: &'a mut dyn FnMut() -> String,
1838 ado_to_rust: Vec<(String, String, bool)>,
1839 rust_to_ado: Vec<(String, String)>,
1840 }
1841
1842 impl AdoStepServices<'_> {
1843 pub fn resolve_repository_id(&self, repo_id: AdoResourcesRepositoryId) -> String {
1846 repo_id.repo_id
1847 }
1848
1849 pub fn set_var(&mut self, var: ClaimedWriteVar<String>, from_ado_var: AdoRuntimeVar) {
1855 self.ado_to_rust.push((
1856 from_ado_var.ado_var.into(),
1857 var.backing_var,
1858 from_ado_var.is_secret,
1859 ))
1860 }
1861
1862 pub fn get_var(&mut self, var: ClaimedReadVar<String>) -> AdoRuntimeVar {
1864 let backing_var = if let ReadVarBacking::RuntimeVar {
1865 var,
1866 is_side_effect,
1867 } = &var.backing_var
1868 {
1869 assert!(!is_side_effect);
1870 var
1871 } else {
1872 todo!("support inline ado read vars")
1873 };
1874
1875 let new_ado_var_name = (self.fresh_ado_var)();
1876
1877 self.rust_to_ado
1878 .push((backing_var.clone(), new_ado_var_name.clone()));
1879 AdoRuntimeVar::dangerous_from_global(new_ado_var_name, false)
1880 }
1881 }
1882 }
1883
1884 pub mod github {
1885 use crate::node::ClaimVar;
1886 use crate::node::NodeCtx;
1887 use crate::node::ReadVar;
1888 use crate::node::ReadVarBacking;
1889 use crate::node::SideEffect;
1890 use crate::node::StepCtx;
1891 use crate::node::VarClaimed;
1892 use crate::node::VarNotClaimed;
1893 use crate::node::WriteVar;
1894 use std::collections::BTreeMap;
1895
1896 pub struct GhStepBuilder {
1897 display_name: String,
1898 cond: Option<ReadVar<bool>>,
1899 uses: String,
1900 with: Option<BTreeMap<String, GhParam>>,
1901 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1902 run_after: Vec<ReadVar<SideEffect>>,
1903 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1904 }
1905
1906 impl GhStepBuilder {
1907 pub fn new(display_name: impl AsRef<str>, uses: impl AsRef<str>) -> Self {
1922 Self {
1923 display_name: display_name.as_ref().into(),
1924 cond: None,
1925 uses: uses.as_ref().into(),
1926 with: None,
1927 outputs: BTreeMap::new(),
1928 run_after: Vec::new(),
1929 permissions: BTreeMap::new(),
1930 }
1931 }
1932
1933 pub fn condition(mut self, cond: ReadVar<bool>) -> Self {
1940 self.cond = Some(cond);
1941 self
1942 }
1943
1944 pub fn with(mut self, k: impl AsRef<str>, v: impl Into<GhParam>) -> Self {
1970 self.with.get_or_insert_with(BTreeMap::new);
1971 if let Some(with) = &mut self.with {
1972 with.insert(k.as_ref().to_string(), v.into());
1973 }
1974 self
1975 }
1976
1977 pub fn output(mut self, k: impl AsRef<str>, v: WriteVar<String>) -> Self {
1986 self.outputs
1987 .entry(k.as_ref().to_string())
1988 .or_default()
1989 .push(v);
1990 self
1991 }
1992
1993 pub fn run_after(mut self, side_effect: ReadVar<SideEffect>) -> Self {
1995 self.run_after.push(side_effect);
1996 self
1997 }
1998
1999 pub fn requires_permission(
2004 mut self,
2005 perm: GhPermission,
2006 value: GhPermissionValue,
2007 ) -> Self {
2008 self.permissions.insert(perm, value);
2009 self
2010 }
2011
2012 #[track_caller]
2014 pub fn finish(self, ctx: &mut NodeCtx<'_>) -> ReadVar<SideEffect> {
2015 let (side_effect, claim_side_effect) = ctx.new_prefixed_var("auto_se");
2016 ctx.backend
2017 .borrow_mut()
2018 .on_claimed_runtime_var(&claim_side_effect.backing_var, false);
2019
2020 ctx.emit_gh_step_inner(
2021 self.display_name,
2022 self.cond,
2023 self.uses,
2024 self.with,
2025 self.outputs,
2026 self.run_after,
2027 self.permissions,
2028 );
2029
2030 side_effect
2031 }
2032 }
2033
2034 #[derive(Clone, Debug)]
2035 pub enum GhParam<C = VarNotClaimed> {
2036 Static(String),
2037 FloweyVar(ReadVar<String, C>),
2038 }
2039
2040 impl From<String> for GhParam {
2041 fn from(param: String) -> GhParam {
2042 GhParam::Static(param)
2043 }
2044 }
2045
2046 impl From<&str> for GhParam {
2047 fn from(param: &str) -> GhParam {
2048 GhParam::Static(param.to_string())
2049 }
2050 }
2051
2052 impl From<ReadVar<String>> for GhParam {
2053 fn from(param: ReadVar<String>) -> GhParam {
2054 GhParam::FloweyVar(param)
2055 }
2056 }
2057
2058 pub type ClaimedGhParam = GhParam<VarClaimed>;
2059
2060 impl ClaimVar for GhParam {
2061 type Claimed = ClaimedGhParam;
2062
2063 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedGhParam {
2064 match self {
2065 GhParam::Static(s) => ClaimedGhParam::Static(s),
2066 GhParam::FloweyVar(var) => match &var.backing_var {
2067 ReadVarBacking::RuntimeVar { is_side_effect, .. } => {
2068 assert!(!is_side_effect);
2069 ClaimedGhParam::FloweyVar(var.claim(ctx))
2070 }
2071 ReadVarBacking::Inline(var) => ClaimedGhParam::Static(var.clone()),
2072 },
2073 }
2074 }
2075 }
2076
2077 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2082 pub enum GhPermissionValue {
2083 Read,
2084 Write,
2085 None,
2086 }
2087
2088 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2094 pub enum GhPermission {
2095 Actions,
2096 Attestations,
2097 Checks,
2098 Contents,
2099 Deployments,
2100 Discussions,
2101 IdToken,
2102 Issues,
2103 Packages,
2104 Pages,
2105 PullRequests,
2106 RepositoryProjects,
2107 SecurityEvents,
2108 Statuses,
2109 }
2110 }
2111
2112 pub mod rust {
2113 use crate::node::ClaimedWriteVar;
2114 use crate::node::FlowArch;
2115 use crate::node::FlowBackend;
2116 use crate::node::FlowPlatform;
2117 use crate::node::ReadVarValue;
2118 use crate::node::RuntimeVarDb;
2119 use serde::Serialize;
2120 use serde::de::DeserializeOwned;
2121
2122 pub fn new_rust_runtime_services(
2123 runtime_var_db: &mut dyn RuntimeVarDb,
2124 backend: FlowBackend,
2125 platform: FlowPlatform,
2126 arch: FlowArch,
2127 ) -> RustRuntimeServices<'_> {
2128 RustRuntimeServices {
2129 runtime_var_db,
2130 backend,
2131 platform,
2132 arch,
2133 has_read_secret: false,
2134 }
2135 }
2136
2137 pub struct RustRuntimeServices<'a> {
2138 runtime_var_db: &'a mut dyn RuntimeVarDb,
2139 backend: FlowBackend,
2140 platform: FlowPlatform,
2141 arch: FlowArch,
2142 has_read_secret: bool,
2143 }
2144
2145 impl RustRuntimeServices<'_> {
2146 pub fn backend(&self) -> FlowBackend {
2149 self.backend
2150 }
2151
2152 pub fn platform(&self) -> FlowPlatform {
2155 self.platform
2156 }
2157
2158 pub fn arch(&self) -> FlowArch {
2160 self.arch
2161 }
2162
2163 pub fn write<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2171 where
2172 T: Serialize + DeserializeOwned,
2173 {
2174 self.write_maybe_secret(var, val, self.has_read_secret)
2175 }
2176
2177 pub fn write_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2183 where
2184 T: Serialize + DeserializeOwned,
2185 {
2186 self.write_maybe_secret(var, val, true)
2187 }
2188
2189 pub fn write_not_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2196 where
2197 T: Serialize + DeserializeOwned,
2198 {
2199 self.write_maybe_secret(var, val, false)
2200 }
2201
2202 fn write_maybe_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T, is_secret: bool)
2203 where
2204 T: Serialize + DeserializeOwned,
2205 {
2206 let val = if var.is_side_effect {
2207 b"null".to_vec()
2208 } else {
2209 serde_json::to_vec(val).expect("improve this error path")
2210 };
2211 self.runtime_var_db
2212 .set_var(&var.backing_var, is_secret, val);
2213 }
2214
2215 pub fn write_all<T>(
2216 &mut self,
2217 vars: impl IntoIterator<Item = ClaimedWriteVar<T>>,
2218 val: &T,
2219 ) where
2220 T: Serialize + DeserializeOwned,
2221 {
2222 for var in vars {
2223 self.write(var, val)
2224 }
2225 }
2226
2227 pub fn read<T: ReadVarValue>(&mut self, var: T) -> T::Value {
2228 var.read_value(self)
2229 }
2230
2231 pub(crate) fn get_var(&mut self, var: &str, is_side_effect: bool) -> Vec<u8> {
2232 let (v, is_secret) = self.runtime_var_db.get_var(var);
2233 self.has_read_secret |= is_secret && !is_side_effect;
2234 v
2235 }
2236
2237 pub fn dangerous_gh_set_global_env_var(
2244 &mut self,
2245 var: String,
2246 gh_env_var: String,
2247 ) -> anyhow::Result<()> {
2248 if !matches!(self.backend, FlowBackend::Github) {
2249 return Err(anyhow::anyhow!(
2250 "dangerous_set_gh_env_var can only be used on GitHub Actions"
2251 ));
2252 }
2253
2254 let gh_env_file_path = std::env::var("GITHUB_ENV")?;
2255 let mut gh_env_file = fs_err::OpenOptions::new()
2256 .append(true)
2257 .open(gh_env_file_path)?;
2258 let gh_env_var_assignment = format!(
2259 r#"{}<<EOF
2260{}
2261EOF
2262"#,
2263 gh_env_var, var
2264 );
2265 std::io::Write::write_all(&mut gh_env_file, gh_env_var_assignment.as_bytes())?;
2266
2267 Ok(())
2268 }
2269 }
2270 }
2271}
2272
2273pub trait FlowNodeBase {
2278 type Request: Serialize + DeserializeOwned;
2279
2280 fn imports(&mut self, ctx: &mut ImportCtx<'_>);
2281 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2282
2283 fn i_know_what_im_doing_with_this_manual_impl(&mut self);
2289}
2290
2291pub mod erased {
2292 use crate::node::FlowNodeBase;
2293 use crate::node::NodeCtx;
2294 use crate::node::user_facing::*;
2295
2296 pub struct ErasedNode<N: FlowNodeBase>(pub N);
2297
2298 impl<N: FlowNodeBase> ErasedNode<N> {
2299 pub fn from_node(node: N) -> Self {
2300 Self(node)
2301 }
2302 }
2303
2304 impl<N> FlowNodeBase for ErasedNode<N>
2305 where
2306 N: FlowNodeBase,
2307 {
2308 type Request = Box<[u8]>;
2310
2311 fn imports(&mut self, ctx: &mut ImportCtx<'_>) {
2312 self.0.imports(ctx)
2313 }
2314
2315 fn emit(&mut self, requests: Vec<Box<[u8]>>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
2316 let mut converted_requests = Vec::new();
2317 for req in requests {
2318 converted_requests.push(serde_json::from_slice(&req)?)
2319 }
2320
2321 self.0.emit(converted_requests, ctx)
2322 }
2323
2324 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2325 }
2326}
2327
2328#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2330pub struct NodeHandle(std::any::TypeId);
2331
2332impl Ord for NodeHandle {
2333 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2334 self.modpath().cmp(other.modpath())
2335 }
2336}
2337
2338impl PartialOrd for NodeHandle {
2339 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2340 Some(self.cmp(other))
2341 }
2342}
2343
2344impl std::fmt::Debug for NodeHandle {
2345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2346 std::fmt::Debug::fmt(&self.try_modpath(), f)
2347 }
2348}
2349
2350impl NodeHandle {
2351 pub fn from_type<N: FlowNodeBase + 'static>() -> NodeHandle {
2352 NodeHandle(std::any::TypeId::of::<N>())
2353 }
2354
2355 pub fn from_modpath(modpath: &str) -> NodeHandle {
2356 node_luts::erased_node_by_modpath().get(modpath).unwrap().0
2357 }
2358
2359 pub fn try_from_modpath(modpath: &str) -> Option<NodeHandle> {
2360 node_luts::erased_node_by_modpath()
2361 .get(modpath)
2362 .map(|(s, _)| *s)
2363 }
2364
2365 pub fn new_erased_node(&self) -> Box<dyn FlowNodeBase<Request = Box<[u8]>>> {
2366 let ctor = node_luts::erased_node_by_typeid().get(self).unwrap();
2367 ctor()
2368 }
2369
2370 pub fn modpath(&self) -> &'static str {
2371 node_luts::modpath_by_node_typeid().get(self).unwrap()
2372 }
2373
2374 pub fn try_modpath(&self) -> Option<&'static str> {
2375 node_luts::modpath_by_node_typeid().get(self).cloned()
2376 }
2377
2378 pub fn dummy() -> NodeHandle {
2381 NodeHandle(std::any::TypeId::of::<()>())
2382 }
2383}
2384
2385pub fn list_all_registered_nodes() -> impl Iterator<Item = NodeHandle> {
2386 node_luts::modpath_by_node_typeid().keys().cloned()
2387}
2388
2389mod node_luts {
2404 use super::FlowNodeBase;
2405 use super::NodeHandle;
2406 use std::collections::HashMap;
2407 use std::sync::OnceLock;
2408
2409 pub(super) fn modpath_by_node_typeid() -> &'static HashMap<NodeHandle, &'static str> {
2410 static TYPEID_TO_MODPATH: OnceLock<HashMap<NodeHandle, &'static str>> = OnceLock::new();
2411
2412 TYPEID_TO_MODPATH.get_or_init(|| {
2413 let mut lookup = HashMap::new();
2414 for crate::node::private::FlowNodeMeta {
2415 module_path,
2416 ctor: _,
2417 get_typeid,
2418 } in crate::node::private::FLOW_NODES
2419 {
2420 let existing = lookup.insert(
2421 NodeHandle(get_typeid()),
2422 module_path
2423 .strip_suffix("::_only_one_call_to_flowey_node_per_module")
2424 .unwrap(),
2425 );
2426 assert!(existing.is_none())
2429 }
2430
2431 lookup
2432 })
2433 }
2434
2435 pub(super) fn erased_node_by_typeid()
2436 -> &'static HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>> {
2437 static LOOKUP: OnceLock<
2438 HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>>,
2439 > = OnceLock::new();
2440
2441 LOOKUP.get_or_init(|| {
2442 let mut lookup = HashMap::new();
2443 for crate::node::private::FlowNodeMeta {
2444 module_path: _,
2445 ctor,
2446 get_typeid,
2447 } in crate::node::private::FLOW_NODES
2448 {
2449 let existing = lookup.insert(NodeHandle(get_typeid()), *ctor);
2450 assert!(existing.is_none())
2453 }
2454
2455 lookup
2456 })
2457 }
2458
2459 pub(super) fn erased_node_by_modpath() -> &'static HashMap<
2460 &'static str,
2461 (
2462 NodeHandle,
2463 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2464 ),
2465 > {
2466 static MODPATH_LOOKUP: OnceLock<
2467 HashMap<
2468 &'static str,
2469 (
2470 NodeHandle,
2471 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2472 ),
2473 >,
2474 > = OnceLock::new();
2475
2476 MODPATH_LOOKUP.get_or_init(|| {
2477 let mut lookup = HashMap::new();
2478 for crate::node::private::FlowNodeMeta { module_path, ctor, get_typeid } in crate::node::private::FLOW_NODES {
2479 let existing = lookup.insert(module_path.strip_suffix("::_only_one_call_to_flowey_node_per_module").unwrap(), (NodeHandle(get_typeid()), *ctor));
2480 if existing.is_some() {
2481 panic!("conflicting node registrations at {module_path}! please ensure there is a single node per module!")
2482 }
2483 }
2484 lookup
2485 })
2486 }
2487}
2488
2489#[doc(hidden)]
2490pub mod private {
2491 pub use linkme;
2492
2493 pub struct FlowNodeMeta {
2494 pub module_path: &'static str,
2495 pub ctor: fn() -> Box<dyn super::FlowNodeBase<Request = Box<[u8]>>>,
2496 pub get_typeid: fn() -> std::any::TypeId,
2498 }
2499
2500 #[linkme::distributed_slice]
2501 pub static FLOW_NODES: [FlowNodeMeta] = [..];
2502
2503 #[expect(unsafe_code)]
2505 #[linkme::distributed_slice(FLOW_NODES)]
2506 static DUMMY_FLOW_NODE: FlowNodeMeta = FlowNodeMeta {
2507 module_path: "<dummy>::_only_one_call_to_flowey_node_per_module",
2508 ctor: || unreachable!(),
2509 get_typeid: std::any::TypeId::of::<()>,
2510 };
2511}
2512
2513#[doc(hidden)]
2514#[macro_export]
2515macro_rules! new_flow_node_base {
2516 (struct Node) => {
2517 #[non_exhaustive]
2519 pub struct Node;
2520
2521 mod _only_one_call_to_flowey_node_per_module {
2522 const _: () = {
2523 use $crate::node::private::linkme;
2524
2525 fn new_erased() -> Box<dyn $crate::node::FlowNodeBase<Request = Box<[u8]>>> {
2526 Box::new($crate::node::erased::ErasedNode(super::Node))
2527 }
2528
2529 #[linkme::distributed_slice($crate::node::private::FLOW_NODES)]
2530 #[linkme(crate = linkme)]
2531 static FLOW_NODE: $crate::node::private::FlowNodeMeta =
2532 $crate::node::private::FlowNodeMeta {
2533 module_path: module_path!(),
2534 ctor: new_erased,
2535 get_typeid: std::any::TypeId::of::<super::Node>,
2536 };
2537 };
2538 }
2539 };
2540}
2541
2542pub trait FlowNode {
2544 type Request: Serialize + DeserializeOwned;
2546
2547 fn imports(ctx: &mut ImportCtx<'_>);
2559
2560 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2563}
2564
2565#[macro_export]
2566macro_rules! new_flow_node {
2567 (struct Node) => {
2568 $crate::new_flow_node_base!(struct Node);
2569
2570 impl $crate::node::FlowNodeBase for Node
2571 where
2572 Node: FlowNode,
2573 {
2574 type Request = <Node as FlowNode>::Request;
2575
2576 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2577 <Node as FlowNode>::imports(dep)
2578 }
2579
2580 fn emit(
2581 &mut self,
2582 requests: Vec<Self::Request>,
2583 ctx: &mut $crate::node::NodeCtx<'_>,
2584 ) -> anyhow::Result<()> {
2585 <Node as FlowNode>::emit(requests, ctx)
2586 }
2587
2588 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2589 }
2590 };
2591}
2592
2593pub trait SimpleFlowNode {
2614 type Request: Serialize + DeserializeOwned;
2615
2616 fn imports(ctx: &mut ImportCtx<'_>);
2628
2629 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2631}
2632
2633#[macro_export]
2634macro_rules! new_simple_flow_node {
2635 (struct Node) => {
2636 $crate::new_flow_node_base!(struct Node);
2637
2638 impl $crate::node::FlowNodeBase for Node
2639 where
2640 Node: $crate::node::SimpleFlowNode,
2641 {
2642 type Request = <Node as $crate::node::SimpleFlowNode>::Request;
2643
2644 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2645 <Node as $crate::node::SimpleFlowNode>::imports(dep)
2646 }
2647
2648 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut $crate::node::NodeCtx<'_>) -> anyhow::Result<()> {
2649 for req in requests {
2650 <Node as $crate::node::SimpleFlowNode>::process_request(req, ctx)?
2651 }
2652
2653 Ok(())
2654 }
2655
2656 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2657 }
2658 };
2659}
2660
2661pub trait IntoRequest {
2669 type Node: FlowNodeBase;
2670 fn into_request(self) -> <Self::Node as FlowNodeBase>::Request;
2671
2672 #[doc(hidden)]
2675 #[expect(nonstandard_style)]
2676 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self);
2677}
2678
2679#[doc(hidden)]
2680#[macro_export]
2681macro_rules! __flowey_request_inner {
2682 (@emit_struct [$req:ident]
2686 $(#[$a:meta])*
2687 $variant:ident($($tt:tt)*),
2688 $($rest:tt)*
2689 ) => {
2690 $(#[$a])*
2691 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2692 pub struct $variant($($tt)*);
2693
2694 impl IntoRequest for $variant {
2695 type Node = Node;
2696 fn into_request(self) -> $req {
2697 $req::$variant(self)
2698 }
2699 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2700 }
2701
2702 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2703 };
2704 (@emit_struct [$req:ident]
2705 $(#[$a:meta])*
2706 $variant:ident { $($tt:tt)* },
2707 $($rest:tt)*
2708 ) => {
2709 $(#[$a])*
2710 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2711 pub struct $variant {
2712 $($tt)*
2713 }
2714
2715 impl IntoRequest for $variant {
2716 type Node = Node;
2717 fn into_request(self) -> $req {
2718 $req::$variant(self)
2719 }
2720 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2721 }
2722
2723 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2724 };
2725 (@emit_struct [$req:ident]
2726 $(#[$a:meta])*
2727 $variant:ident,
2728 $($rest:tt)*
2729 ) => {
2730 $(#[$a])*
2731 #[derive(Serialize, Deserialize)]
2732 pub struct $variant;
2733
2734 impl IntoRequest for $variant {
2735 type Node = Node;
2736 fn into_request(self) -> $req {
2737 $req::$variant(self)
2738 }
2739 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2740 }
2741
2742 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2743 };
2744 (@emit_struct [$req:ident]
2745 ) => {};
2746
2747 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2751 $(#[$a:meta])*
2752 $variant:ident($($tt:tt)*),
2753 $($rest:tt)*
2754 ) => {
2755 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2756 };
2757 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2758 $(#[$a:meta])*
2759 $variant:ident { $($tt:tt)* },
2760 $($rest:tt)*
2761 ) => {
2762 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2763 };
2764 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2765 $(#[$a:meta])*
2766 $variant:ident,
2767 $($rest:tt)*
2768 ) => {
2769 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2770 };
2771 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2772 ) => {
2773 #[derive(Serialize, Deserialize)]
2774 pub enum $req {$(
2775 $(#[$prev_a])*
2776 $prev(self::req::$prev),
2777 )*}
2778
2779 impl IntoRequest for $req {
2780 type Node = Node;
2781 fn into_request(self) -> $req {
2782 self
2783 }
2784 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2785 }
2786 };
2787}
2788
2789#[macro_export]
2837macro_rules! flowey_request {
2838 (
2839 $(#[$root_a:meta])*
2840 pub enum_struct $req:ident {
2841 $($tt:tt)*
2842 }
2843 ) => {
2844 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*),] $($tt)*);
2845 pub mod req {
2846 use super::*;
2847 $crate::__flowey_request_inner!(@emit_struct [$req] $($tt)*);
2848 }
2849 };
2850
2851 (
2852 $(#[$a:meta])*
2853 pub enum $req:ident {
2854 $($tt:tt)*
2855 }
2856 ) => {
2857 $(#[$a])*
2858 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2859 pub enum $req {
2860 $($tt)*
2861 }
2862
2863 impl $crate::node::IntoRequest for $req {
2864 type Node = Node;
2865 fn into_request(self) -> $req {
2866 self
2867 }
2868 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2869 }
2870 };
2871
2872 (
2873 $(#[$a:meta])*
2874 pub struct $req:ident {
2875 $($tt:tt)*
2876 }
2877 ) => {
2878 $(#[$a])*
2879 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2880 pub struct $req {
2881 $($tt)*
2882 }
2883
2884 impl $crate::node::IntoRequest for $req {
2885 type Node = Node;
2886 fn into_request(self) -> $req {
2887 self
2888 }
2889 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2890 }
2891 };
2892
2893 (
2894 $(#[$a:meta])*
2895 pub struct $req:ident($($tt:tt)*);
2896 ) => {
2897 $(#[$a])*
2898 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2899 pub struct $req($($tt)*);
2900
2901 impl $crate::node::IntoRequest for $req {
2902 type Node = Node;
2903 fn into_request(self) -> $req {
2904 self
2905 }
2906 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2907 }
2908 };
2909}