1use crate::{
2 State, command::Command, config::RunnableConfig, error::JunctureError, node::Node,
3 runtime::Runtime,
4};
5use std::{marker::PhantomData, sync::Arc};
6
7pub trait IntoNode<S: State> {
101 fn into_node(self, name: &str) -> Arc<dyn Node<S>>;
103}
104
105#[derive(Debug)]
107pub struct NodeFnUpdate<F>(pub F);
108
109#[derive(Debug)]
111pub struct NodeFnUpdateWithConfig<F>(pub F);
112
113#[derive(Debug)]
115pub struct NodeFnCommand<F>(pub F);
116
117#[derive(Debug)]
119pub struct NodeFnCommandWithConfig<F>(pub F);
120
121pub struct NodeFnUpdateWithRuntime<F, C>
145where
146 C: Clone + Send + Sync + 'static,
147{
148 pub func: F,
150 pub runtime: Runtime<C>,
152 _phantom: PhantomData<fn() -> C>,
153}
154
155impl<F, C> std::fmt::Debug for NodeFnUpdateWithRuntime<F, C>
156where
157 F: std::fmt::Debug,
158 C: Clone + Send + Sync + 'static + std::fmt::Debug,
159{
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("NodeFnUpdateWithRuntime")
162 .field("func", &self.func)
163 .field("runtime", &self.runtime)
164 .field("_phantom", &self._phantom)
165 .finish()
166 }
167}
168
169impl<F, C> NodeFnUpdateWithRuntime<F, C>
170where
171 C: Clone + Send + Sync + 'static,
172{
173 #[must_use]
180 pub const fn new(func: F, runtime: Runtime<C>) -> Self {
181 Self {
182 func,
183 runtime,
184 _phantom: PhantomData,
185 }
186 }
187}
188
189pub struct NodeFnUpdateWithConfigAndRuntime<F, C>
194where
195 C: Clone + Send + Sync + 'static,
196{
197 pub func: F,
199 pub runtime: Runtime<C>,
201 _phantom: PhantomData<fn() -> C>,
202}
203
204impl<F, C> std::fmt::Debug for NodeFnUpdateWithConfigAndRuntime<F, C>
205where
206 F: std::fmt::Debug,
207 C: Clone + Send + Sync + 'static + std::fmt::Debug,
208{
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 f.debug_struct("NodeFnUpdateWithConfigAndRuntime")
211 .field("func", &self.func)
212 .field("runtime", &self.runtime)
213 .field("_phantom", &self._phantom)
214 .finish()
215 }
216}
217
218impl<F, C> NodeFnUpdateWithConfigAndRuntime<F, C>
219where
220 C: Clone + Send + Sync + 'static,
221{
222 #[must_use]
229 pub const fn new(func: F, runtime: Runtime<C>) -> Self {
230 Self {
231 func,
232 runtime,
233 _phantom: PhantomData,
234 }
235 }
236}
237
238pub struct NodeFnCommandWithRuntime<F, C>
243where
244 C: Clone + Send + Sync + 'static,
245{
246 pub func: F,
248 pub runtime: Runtime<C>,
250 _phantom: PhantomData<fn() -> C>,
251}
252
253impl<F, C> std::fmt::Debug for NodeFnCommandWithRuntime<F, C>
254where
255 F: std::fmt::Debug,
256 C: Clone + Send + Sync + 'static + std::fmt::Debug,
257{
258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 f.debug_struct("NodeFnCommandWithRuntime")
260 .field("func", &self.func)
261 .field("runtime", &self.runtime)
262 .field("_phantom", &self._phantom)
263 .finish()
264 }
265}
266
267impl<F, C> NodeFnCommandWithRuntime<F, C>
268where
269 C: Clone + Send + Sync + 'static,
270{
271 #[must_use]
278 pub const fn new(func: F, runtime: Runtime<C>) -> Self {
279 Self {
280 func,
281 runtime,
282 _phantom: PhantomData,
283 }
284 }
285}
286
287pub struct NodeFnCommandWithConfigAndRuntime<F, C>
292where
293 C: Clone + Send + Sync + 'static,
294{
295 pub func: F,
297 pub runtime: Runtime<C>,
299 _phantom: PhantomData<fn() -> C>,
300}
301
302impl<F, C> std::fmt::Debug for NodeFnCommandWithConfigAndRuntime<F, C>
303where
304 F: std::fmt::Debug,
305 C: Clone + Send + Sync + 'static + std::fmt::Debug,
306{
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 f.debug_struct("NodeFnCommandWithConfigAndRuntime")
309 .field("func", &self.func)
310 .field("runtime", &self.runtime)
311 .field("_phantom", &self._phantom)
312 .finish()
313 }
314}
315
316impl<F, C> NodeFnCommandWithConfigAndRuntime<F, C>
317where
318 C: Clone + Send + Sync + 'static,
319{
320 #[must_use]
327 pub const fn new(func: F, runtime: Runtime<C>) -> Self {
328 Self {
329 func,
330 runtime,
331 _phantom: PhantomData,
332 }
333 }
334}
335
336impl<S, F, Fut> IntoNode<S> for NodeFnUpdate<F>
339where
340 S: State,
341 F: Fn(&S) -> Fut + Send + Sync + 'static,
342 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
343{
344 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
345 Arc::new(FnNodeUpdateOnly {
346 name: name.to_string(),
347 func: self.0,
348 _phantom: PhantomData,
349 })
350 }
351}
352
353impl<S, F, Fut> IntoNode<S> for NodeFnUpdateWithConfig<F>
354where
355 S: State,
356 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
357 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
358{
359 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
360 Arc::new(FnNodeUpdateWithConfig {
361 name: name.to_string(),
362 func: self.0,
363 _phantom: PhantomData,
364 })
365 }
366}
367
368impl<S, F, Fut> IntoNode<S> for NodeFnCommand<F>
369where
370 S: State,
371 F: Fn(&S) -> Fut + Send + Sync + 'static,
372 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
373{
374 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
375 Arc::new(FnNodeCommandOnly {
376 name: name.to_string(),
377 func: self.0,
378 _phantom: PhantomData,
379 })
380 }
381}
382
383impl<S, F, Fut> IntoNode<S> for NodeFnCommandWithConfig<F>
384where
385 S: State,
386 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
387 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
388{
389 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
390 Arc::new(FnNodeCommandWithConfig {
391 name: name.to_string(),
392 func: self.0,
393 _phantom: PhantomData,
394 })
395 }
396}
397
398impl<S, F, Fut, C> IntoNode<S> for NodeFnUpdateWithRuntime<F, C>
400where
401 S: State,
402 C: Clone + Send + Sync + 'static,
403 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
404 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
405{
406 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
407 Arc::new(FnNodeUpdateWithRuntime {
408 name: name.to_string(),
409 func: self.func,
410 runtime: self.runtime,
411 _phantom: PhantomData,
412 })
413 }
414}
415
416impl<S, F, Fut, C> IntoNode<S> for NodeFnUpdateWithConfigAndRuntime<F, C>
418where
419 S: State,
420 C: Clone + Send + Sync + 'static,
421 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
422 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
423{
424 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
425 Arc::new(FnNodeUpdateWithConfigAndRuntime {
426 name: name.to_string(),
427 func: self.func,
428 runtime: self.runtime,
429 _phantom: PhantomData,
430 })
431 }
432}
433
434impl<S, F, Fut, C> IntoNode<S> for NodeFnCommandWithRuntime<F, C>
436where
437 S: State,
438 C: Clone + Send + Sync + 'static,
439 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
440 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
441{
442 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
443 Arc::new(FnNodeCommandWithRuntime {
444 name: name.to_string(),
445 func: self.func,
446 runtime: self.runtime,
447 _phantom: PhantomData,
448 })
449 }
450}
451
452impl<S, F, Fut, C> IntoNode<S> for NodeFnCommandWithConfigAndRuntime<F, C>
454where
455 S: State,
456 C: Clone + Send + Sync + 'static,
457 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
458 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
459{
460 fn into_node(self, name: &str) -> Arc<dyn Node<S>> {
461 Arc::new(FnNodeCommandWithConfigAndRuntime {
462 name: name.to_string(),
463 func: self.func,
464 runtime: self.runtime,
465 _phantom: PhantomData,
466 })
467 }
468}
469
470#[allow(
471 dead_code,
472 reason = "fields used via Node trait, not directly accessed"
473)]
474struct FnNodeUpdateOnly<S, F, Fut>
475where
476 S: State,
477 F: Fn(&S) -> Fut + Send + Sync + 'static,
478 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
479{
480 name: String,
481 func: F,
482 _phantom: PhantomData<fn(&S) -> Fut>,
483}
484
485impl<S, F, Fut> Node<S> for FnNodeUpdateOnly<S, F, Fut>
486where
487 S: State,
488 F: Fn(&S) -> Fut + Send + Sync + 'static,
489 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
490{
491 fn call(
492 &self,
493 state: &S,
494 _config: &RunnableConfig,
495 ) -> std::pin::Pin<
496 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
497 > {
498 let state_clone = state.clone();
499 let func = &self.func;
500 Box::pin(async move {
501 let update = func(&state_clone).await?;
502 Ok(Command::update(update))
503 })
504 }
505
506 fn call_arc(
507 &self,
508 state: std::sync::Arc<S>,
509 _config: &RunnableConfig,
510 ) -> std::pin::Pin<
511 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
512 > {
513 let state_arc = std::sync::Arc::clone(&state);
514 let func = &self.func;
515 Box::pin(async move {
516 let update = func(&state_arc).await?;
517 Ok(Command::update(update))
518 })
519 }
520
521 fn name(&self) -> &str {
522 &self.name
523 }
524}
525
526#[allow(
527 dead_code,
528 reason = "fields used via Node trait, not directly accessed"
529)]
530struct FnNodeUpdateWithConfig<S, F, Fut>
531where
532 S: State,
533 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
534 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
535{
536 name: String,
537 func: F,
538 _phantom: PhantomData<fn(&S, RunnableConfig) -> Fut>,
539}
540
541impl<S, F, Fut> Node<S> for FnNodeUpdateWithConfig<S, F, Fut>
542where
543 S: State,
544 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
545 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
546{
547 fn call(
548 &self,
549 state: &S,
550 config: &RunnableConfig,
551 ) -> std::pin::Pin<
552 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
553 > {
554 let config = config.clone();
555 let state_clone = state.clone();
556 let func = &self.func;
557 Box::pin(async move {
558 let update = func(&state_clone, config).await?;
559 Ok(Command::update(update))
560 })
561 }
562
563 fn call_arc(
564 &self,
565 state: std::sync::Arc<S>,
566 config: &RunnableConfig,
567 ) -> std::pin::Pin<
568 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
569 > {
570 let config = config.clone();
571 let state_arc = std::sync::Arc::clone(&state);
572 let func = &self.func;
573 Box::pin(async move {
574 let update = func(&state_arc, config).await?;
575 Ok(Command::update(update))
576 })
577 }
578
579 fn name(&self) -> &str {
580 &self.name
581 }
582}
583
584#[allow(
585 dead_code,
586 reason = "fields used via Node trait, not directly accessed"
587)]
588struct FnNodeCommandOnly<S, F, Fut>
589where
590 S: State,
591 F: Fn(&S) -> Fut + Send + Sync + 'static,
592 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
593{
594 name: String,
595 func: F,
596 _phantom: PhantomData<fn(&S) -> Fut>,
597}
598
599impl<S, F, Fut> Node<S> for FnNodeCommandOnly<S, F, Fut>
600where
601 S: State,
602 F: Fn(&S) -> Fut + Send + Sync + 'static,
603 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
604{
605 fn call(
606 &self,
607 state: &S,
608 _config: &RunnableConfig,
609 ) -> std::pin::Pin<
610 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
611 > {
612 let state_clone = state.clone();
613 let func = &self.func;
614 Box::pin(async move { func(&state_clone).await })
615 }
616
617 fn call_arc(
618 &self,
619 state: std::sync::Arc<S>,
620 _config: &RunnableConfig,
621 ) -> std::pin::Pin<
622 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
623 > {
624 let state_arc = std::sync::Arc::clone(&state);
625 let func = &self.func;
626 Box::pin(async move { func(&state_arc).await })
627 }
628
629 fn name(&self) -> &str {
630 &self.name
631 }
632}
633
634#[allow(
635 dead_code,
636 reason = "fields used via Node trait, not directly accessed"
637)]
638struct FnNodeCommandWithConfig<S, F, Fut>
639where
640 S: State,
641 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
642 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
643{
644 name: String,
645 func: F,
646 _phantom: PhantomData<fn(&S, RunnableConfig) -> Fut>,
647}
648
649impl<S, F, Fut> Node<S> for FnNodeCommandWithConfig<S, F, Fut>
650where
651 S: State,
652 F: Fn(&S, RunnableConfig) -> Fut + Send + Sync + 'static,
653 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
654{
655 fn call(
656 &self,
657 state: &S,
658 config: &RunnableConfig,
659 ) -> std::pin::Pin<
660 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
661 > {
662 let config = config.clone();
663 let state_clone = state.clone();
664 let func = &self.func;
665 Box::pin(async move { func(&state_clone, config).await })
666 }
667
668 fn call_arc(
669 &self,
670 state: std::sync::Arc<S>,
671 config: &RunnableConfig,
672 ) -> std::pin::Pin<
673 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
674 > {
675 let config = config.clone();
676 let state_arc = std::sync::Arc::clone(&state);
677 let func = &self.func;
678 Box::pin(async move { func(&state_arc, config).await })
679 }
680
681 fn name(&self) -> &str {
682 &self.name
683 }
684}
685
686#[allow(
688 dead_code,
689 reason = "fields used via Node trait, not directly accessed"
690)]
691struct FnNodeUpdateWithRuntime<S, F, Fut, C>
692where
693 S: State,
694 C: Clone + Send + Sync + 'static,
695 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
696 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
697{
698 name: String,
699 func: F,
700 runtime: Runtime<C>,
701 #[allow(
702 clippy::type_complexity,
703 reason = "PhantomData needs to capture all generic parameters including complex Future type"
704 )]
705 _phantom: PhantomData<fn(&S, Runtime<C>) -> Fut>,
706}
707
708impl<S, F, Fut, C> Node<S> for FnNodeUpdateWithRuntime<S, F, Fut, C>
709where
710 S: State,
711 C: Clone + Send + Sync + 'static,
712 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
713 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
714{
715 fn call(
716 &self,
717 state: &S,
718 _config: &RunnableConfig,
719 ) -> std::pin::Pin<
720 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
721 > {
722 let runtime = self.runtime.clone();
723 let state_clone = state.clone();
724 let func = &self.func;
725 Box::pin(async move {
726 let update = func(&state_clone, runtime).await?;
727 Ok(Command::update(update))
728 })
729 }
730
731 fn call_arc(
732 &self,
733 state: std::sync::Arc<S>,
734 _config: &RunnableConfig,
735 ) -> std::pin::Pin<
736 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
737 > {
738 let runtime = self.runtime.clone();
739 let state_arc = std::sync::Arc::clone(&state);
740 let func = &self.func;
741 Box::pin(async move {
742 let update = func(&state_arc, runtime).await?;
743 Ok(Command::update(update))
744 })
745 }
746
747 fn name(&self) -> &str {
748 &self.name
749 }
750}
751
752#[allow(
754 dead_code,
755 reason = "fields used via Node trait, not directly accessed"
756)]
757struct FnNodeUpdateWithConfigAndRuntime<S, F, Fut, C>
758where
759 S: State,
760 C: Clone + Send + Sync + 'static,
761 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
762 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
763{
764 name: String,
765 func: F,
766 runtime: Runtime<C>,
767 #[allow(
768 clippy::type_complexity,
769 reason = "PhantomData needs to capture all generic parameters including complex Future type"
770 )]
771 _phantom: PhantomData<fn(&S, RunnableConfig, Runtime<C>) -> Fut>,
772}
773
774impl<S, F, Fut, C> Node<S> for FnNodeUpdateWithConfigAndRuntime<S, F, Fut, C>
775where
776 S: State,
777 C: Clone + Send + Sync + 'static,
778 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
779 Fut: std::future::Future<Output = Result<S::Update, JunctureError>> + Send + 'static,
780{
781 fn call(
782 &self,
783 state: &S,
784 config: &RunnableConfig,
785 ) -> std::pin::Pin<
786 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
787 > {
788 let config = config.clone();
789 let runtime = self.runtime.clone();
790 let state = state.clone();
791 Box::pin(async move {
792 let update = (self.func)(&state, config, runtime).await?;
793 Ok(Command::update(update))
794 })
795 }
796
797 fn call_arc(
798 &self,
799 state: std::sync::Arc<S>,
800 config: &RunnableConfig,
801 ) -> std::pin::Pin<
802 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
803 > {
804 let config = config.clone();
805 let runtime = self.runtime.clone();
806 let state_arc = std::sync::Arc::clone(&state);
807 Box::pin(async move {
808 let update = (self.func)(&state_arc, config, runtime).await?;
809 Ok(Command::update(update))
810 })
811 }
812
813 fn name(&self) -> &str {
814 &self.name
815 }
816}
817
818#[allow(
820 dead_code,
821 reason = "fields used via Node trait, not directly accessed"
822)]
823struct FnNodeCommandWithRuntime<S, F, Fut, C>
824where
825 S: State,
826 C: Clone + Send + Sync + 'static,
827 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
828 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
829{
830 name: String,
831 func: F,
832 runtime: Runtime<C>,
833 #[allow(
834 clippy::type_complexity,
835 reason = "PhantomData needs to capture all generic parameters including complex Future type"
836 )]
837 _phantom: PhantomData<fn(&S, Runtime<C>) -> Fut>,
838}
839
840impl<S, F, Fut, C> Node<S> for FnNodeCommandWithRuntime<S, F, Fut, C>
841where
842 S: State,
843 C: Clone + Send + Sync + 'static,
844 F: Fn(&S, Runtime<C>) -> Fut + Send + Sync + 'static,
845 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
846{
847 fn call(
848 &self,
849 state: &S,
850 _config: &RunnableConfig,
851 ) -> std::pin::Pin<
852 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
853 > {
854 let runtime = self.runtime.clone();
855 let state = state.clone();
856 Box::pin(async move { (self.func)(&state, runtime).await })
857 }
858
859 fn call_arc(
860 &self,
861 state: std::sync::Arc<S>,
862 _config: &RunnableConfig,
863 ) -> std::pin::Pin<
864 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
865 > {
866 let runtime = self.runtime.clone();
867 let state_arc = std::sync::Arc::clone(&state);
868 Box::pin(async move { (self.func)(&state_arc, runtime).await })
869 }
870
871 fn name(&self) -> &str {
872 &self.name
873 }
874}
875
876#[allow(
878 dead_code,
879 reason = "fields used via Node trait, not directly accessed"
880)]
881struct FnNodeCommandWithConfigAndRuntime<S, F, Fut, C>
882where
883 S: State,
884 C: Clone + Send + Sync + 'static,
885 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
886 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
887{
888 name: String,
889 func: F,
890 runtime: Runtime<C>,
891 #[allow(
892 clippy::type_complexity,
893 reason = "PhantomData needs to capture all generic parameters including complex Future type"
894 )]
895 _phantom: PhantomData<fn(&S, RunnableConfig, Runtime<C>) -> Fut>,
896}
897
898impl<S, F, Fut, C> Node<S> for FnNodeCommandWithConfigAndRuntime<S, F, Fut, C>
899where
900 S: State,
901 C: Clone + Send + Sync + 'static,
902 F: Fn(&S, RunnableConfig, Runtime<C>) -> Fut + Send + Sync + 'static,
903 Fut: std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + 'static,
904{
905 fn call(
906 &self,
907 state: &S,
908 config: &RunnableConfig,
909 ) -> std::pin::Pin<
910 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
911 > {
912 let config = config.clone();
913 let runtime = self.runtime.clone();
914 let state = state.clone();
915 Box::pin(async move { (self.func)(&state, config, runtime).await })
916 }
917
918 fn call_arc(
919 &self,
920 state: std::sync::Arc<S>,
921 config: &RunnableConfig,
922 ) -> std::pin::Pin<
923 Box<dyn std::future::Future<Output = Result<Command<S>, JunctureError>> + Send + '_>,
924 > {
925 let config = config.clone();
926 let runtime = self.runtime.clone();
927 let state_arc = std::sync::Arc::clone(&state);
928 Box::pin(async move { (self.func)(&state_arc, config, runtime).await })
929 }
930
931 fn name(&self) -> &str {
932 &self.name
933 }
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939 use crate::FieldsChanged;
940 use crate::state::FieldVersions;
941
942 type BoxResult<T> = std::pin::Pin<
943 Box<dyn std::future::Future<Output = Result<T, crate::JunctureError>> + Send>,
944 >;
945
946 #[derive(Debug, Clone, Default, PartialEq)]
948 struct TestState {
949 value: i32,
950 }
951
952 #[derive(Debug, Clone, Default, PartialEq)]
953 struct TestStateUpdate {
954 value: Option<i32>,
955 }
956
957 impl State for TestState {
958 type Update = TestStateUpdate;
959 type FieldVersions = FieldVersions;
960
961 fn apply(&mut self, update: Self::Update) -> FieldsChanged {
962 if update.value.is_some() {
963 self.value = update.value.unwrap();
964 FieldsChanged(1u64) } else {
966 FieldsChanged(0)
967 }
968 }
969
970 fn reset_ephemeral(&mut self) {
971 }
973 }
974
975 #[derive(Debug, Clone, Default)]
977 struct TestContext {
978 user_id: String,
979 }
980
981 #[expect(dead_code, reason = "inlined in tests due to lifetime constraints")]
984 #[allow(
985 clippy::unused_async,
986 reason = "kept as reference for async node pattern"
987 )]
988 async fn form_e_update_node(
989 state: &TestState,
990 runtime: Runtime<TestContext>,
991 ) -> Result<TestStateUpdate, JunctureError> {
992 assert_eq!(runtime.context.user_id, "test-user");
993 Ok(TestStateUpdate {
994 value: Some(state.value + 10),
995 })
996 }
997
998 #[expect(dead_code, reason = "inlined in tests due to lifetime constraints")]
999 #[allow(
1000 clippy::unused_async,
1001 reason = "kept as reference for async node pattern"
1002 )]
1003 async fn form_f_update_node(
1004 state: &TestState,
1005 config: RunnableConfig,
1006 _runtime: Runtime<TestContext>,
1007 ) -> Result<TestStateUpdate, JunctureError> {
1008 assert_eq!(config.recursion_limit, 0);
1009 Ok(TestStateUpdate {
1010 value: Some(state.value + 20),
1011 })
1012 }
1013
1014 #[expect(dead_code, reason = "inlined in tests due to lifetime constraints")]
1015 #[allow(
1016 clippy::unused_async,
1017 reason = "kept as reference for async node pattern"
1018 )]
1019 async fn form_e_command_node(
1020 state: &TestState,
1021 runtime: Runtime<TestContext>,
1022 ) -> Result<Command<TestState>, JunctureError> {
1023 assert_eq!(runtime.context.user_id, "test-user-3");
1024 Ok(Command::update(TestStateUpdate {
1025 value: Some(state.value + 30),
1026 }))
1027 }
1028
1029 #[expect(dead_code, reason = "inlined in tests due to lifetime constraints")]
1030 #[allow(
1031 clippy::unused_async,
1032 reason = "kept as reference for async node pattern"
1033 )]
1034 async fn form_f_command_node(
1035 state: &TestState,
1036 config: RunnableConfig,
1037 _runtime: Runtime<TestContext>,
1038 ) -> Result<Command<TestState>, JunctureError> {
1039 assert_eq!(config.recursion_limit, 0);
1040 Ok(Command::update(TestStateUpdate {
1041 value: Some(state.value + 40),
1042 }))
1043 }
1044
1045 #[expect(dead_code, reason = "inlined in tests due to lifetime constraints")]
1046 #[allow(
1047 clippy::unused_async,
1048 reason = "kept as reference for async node pattern"
1049 )]
1050 async fn shared_runtime_node(
1051 state: &TestState,
1052 _runtime: Runtime<TestContext>,
1053 ) -> Result<TestStateUpdate, JunctureError> {
1054 Ok(TestStateUpdate {
1055 value: Some(state.value + 1),
1056 })
1057 }
1058
1059 #[tokio::test]
1061 async fn test_form_e_update_with_runtime() {
1062 let runtime = Runtime::with_context(TestContext {
1063 user_id: "test-user".to_string(),
1064 });
1065
1066 let wrapper = NodeFnUpdateWithRuntime::new(
1067 |state: &TestState, rt: Runtime<TestContext>| -> BoxResult<_> {
1068 let value = state.value;
1069 Box::pin(async move {
1070 assert_eq!(rt.context.user_id, "test-user");
1071 Ok(TestStateUpdate {
1072 value: Some(value + 10),
1073 })
1074 })
1075 },
1076 runtime,
1077 );
1078 let node = wrapper.into_node("test_node");
1079
1080 let result = node
1081 .call(&TestState { value: 5 }, &RunnableConfig::default())
1082 .await
1083 .unwrap();
1084
1085 assert_eq!(result.update.unwrap().value, Some(15));
1086 assert_eq!(node.name(), "test_node");
1087 }
1088
1089 #[tokio::test]
1091 async fn test_form_f_update_with_config_and_runtime() {
1092 let runtime = Runtime::with_context(TestContext {
1093 user_id: "test-user-2".to_string(),
1094 });
1095
1096 let wrapper = NodeFnUpdateWithConfigAndRuntime::new(
1097 |state: &TestState, cfg: RunnableConfig, rt: Runtime<TestContext>| -> BoxResult<_> {
1098 let value = state.value;
1099 Box::pin(async move {
1100 assert_eq!(rt.context.user_id, "test-user-2");
1101 assert_eq!(cfg.recursion_limit, 0);
1102 Ok(TestStateUpdate {
1103 value: Some(value + 20),
1104 })
1105 })
1106 },
1107 runtime,
1108 );
1109 let node = wrapper.into_node("test_node");
1110
1111 let result = node
1112 .call(&TestState { value: 5 }, &RunnableConfig::default())
1113 .await
1114 .unwrap();
1115
1116 assert_eq!(result.update.unwrap().value, Some(25));
1117 }
1118
1119 #[tokio::test]
1121 async fn test_form_e_command_with_runtime() {
1122 let runtime = Runtime::with_context(TestContext {
1123 user_id: "test-user-3".to_string(),
1124 });
1125
1126 let wrapper = NodeFnCommandWithRuntime::new(
1127 |state: &TestState, rt: Runtime<TestContext>| -> BoxResult<_> {
1128 let value = state.value;
1129 Box::pin(async move {
1130 assert_eq!(rt.context.user_id, "test-user-3");
1131 Ok(crate::Command::update(TestStateUpdate {
1132 value: Some(value + 30),
1133 }))
1134 })
1135 },
1136 runtime,
1137 );
1138 let node = wrapper.into_node("test_node");
1139
1140 let result = node
1141 .call(&TestState { value: 5 }, &RunnableConfig::default())
1142 .await
1143 .unwrap();
1144
1145 assert_eq!(result.update.unwrap().value, Some(35));
1146 }
1147
1148 #[tokio::test]
1150 async fn test_form_f_command_with_config_and_runtime() {
1151 let runtime = Runtime::with_context(TestContext {
1152 user_id: "test-user-4".to_string(),
1153 });
1154
1155 let wrapper = NodeFnCommandWithConfigAndRuntime::new(
1156 |state: &TestState, cfg: RunnableConfig, rt: Runtime<TestContext>| -> BoxResult<_> {
1157 let value = state.value;
1158 Box::pin(async move {
1159 assert_eq!(rt.context.user_id, "test-user-4");
1160 assert_eq!(cfg.recursion_limit, 0);
1161 Ok(crate::Command::update(TestStateUpdate {
1162 value: Some(value + 40),
1163 }))
1164 })
1165 },
1166 runtime,
1167 );
1168 let node = wrapper.into_node("test_node");
1169
1170 let result = node
1171 .call(&TestState { value: 5 }, &RunnableConfig::default())
1172 .await
1173 .unwrap();
1174
1175 assert_eq!(result.update.unwrap().value, Some(45));
1176 }
1177
1178 #[tokio::test]
1180 async fn test_runtime_clone_multiple_invocations() {
1181 let runtime = Runtime::with_context(TestContext {
1182 user_id: "shared-user".to_string(),
1183 });
1184
1185 let wrapper = NodeFnUpdateWithRuntime::new(
1186 |state: &TestState, rt: Runtime<TestContext>| -> BoxResult<_> {
1187 let value = state.value;
1188 Box::pin(async move {
1189 let _ = rt;
1190 Ok(TestStateUpdate {
1191 value: Some(value + 1),
1192 })
1193 })
1194 },
1195 runtime,
1196 );
1197 let node = wrapper.into_node("test_node");
1198
1199 let result1 = node
1201 .call(&TestState { value: 0 }, &RunnableConfig::default())
1202 .await
1203 .unwrap();
1204 assert_eq!(result1.update.unwrap().value, Some(1));
1205
1206 let result2 = node
1208 .call(&TestState { value: 10 }, &RunnableConfig::default())
1209 .await
1210 .unwrap();
1211 assert_eq!(result2.update.unwrap().value, Some(11));
1212 }
1213}
1214
1215