orcs_component/child.rs
1//! Child trait for component-managed entities.
2//!
3//! Components may internally manage child entities. All children
4//! must implement this trait to ensure proper signal handling.
5//!
6//! # Why Child Trait?
7//!
8//! Without this constraint, Components could hold arbitrary entities
9//! that don't respond to signals. This would break the "Human as
10//! Superpower" principle - Veto signals must reach everything.
11//!
12//! # Child Hierarchy
13//!
14//! ```text
15//! ┌─────────────────────────────────────────────────────────────┐
16//! │ Child (base trait) │
17//! │ - Identifiable + SignalReceiver + Statusable │
18//! │ - Passive: managed by Component │
19//! └─────────────────────────────────────────────────────────────┘
20//! │
21//! ▼
22//! ┌─────────────────────────────────────────────────────────────┐
23//! │ RunnableChild (extends Child) │
24//! │ - Active: can execute work via run() │
25//! │ - Used for SubAgents, Workers, Skills │
26//! └─────────────────────────────────────────────────────────────┘
27//! ```
28//!
29//! # Component-Child Relationship
30//!
31//! ```text
32//! ┌─────────────────────────────────────────────┐
33//! │ Component (e.g., LlmComponent) │
34//! │ │
35//! │ on_signal(Signal) { │
36//! │ // Forward to all children │
37//! │ for child in &mut self.children { │
38//! │ child.on_signal(&signal); │
39//! │ } │
40//! │ } │
41//! │ │
42//! │ children: Vec<Box<dyn Child>> │
43//! │ ┌────────┐ ┌────────┐ ┌────────┐ │
44//! │ │ Agent1 │ │ Agent2 │ │ Worker │ │
45//! │ │impl │ │impl │ │impl │ │
46//! │ │ Child │ │ Child │ │ Child │ │
47//! │ └────────┘ └────────┘ └────────┘ │
48//! └─────────────────────────────────────────────┘
49//! ```
50//!
51//! # Domain-Specific Implementations
52//!
53//! Concrete child types are defined in domain crates:
54//!
55//! - `orcs-llm`: `Agent impl Child`
56//! - `orcs-skill`: `Skill impl Child`
57//! - etc.
58//!
59//! # Example
60//!
61//! ```
62//! use orcs_component::{Child, Identifiable, SignalReceiver, Statusable, Status};
63//! use orcs_event::{Signal, SignalResponse};
64//!
65//! // Domain-specific child implementation
66//! struct MyAgent {
67//! id: String,
68//! status: Status,
69//! }
70//!
71//! impl Identifiable for MyAgent {
72//! fn id(&self) -> &str {
73//! &self.id
74//! }
75//! }
76//!
77//! impl SignalReceiver for MyAgent {
78//! fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
79//! if signal.is_veto() {
80//! self.abort();
81//! SignalResponse::Abort
82//! } else {
83//! SignalResponse::Ignored
84//! }
85//! }
86//!
87//! fn abort(&mut self) {
88//! self.status = Status::Aborted;
89//! }
90//! }
91//!
92//! impl Statusable for MyAgent {
93//! fn status(&self) -> Status {
94//! self.status
95//! }
96//! }
97//!
98//! // Mark as Child - now safe to hold in Component
99//! impl Child for MyAgent {}
100//! ```
101
102use crate::{Identifiable, SignalReceiver, Statusable};
103use async_trait::async_trait;
104use serde::{Deserialize, Serialize};
105use serde_json::Value;
106use thiserror::Error;
107
108// ============================================
109// Internal Error Type (thiserror-based)
110// ============================================
111
112/// Child execution errors.
113///
114/// Used internally for type-safe error handling.
115/// Convert to [`ChildResultDto`] for serialization.
116#[derive(Debug, Clone, Error)]
117pub enum ChildError {
118 /// Execution failed with a reason.
119 #[error("execution failed: {reason}")]
120 ExecutionFailed { reason: String },
121
122 /// Input validation failed.
123 #[error("invalid input: {0}")]
124 InvalidInput(String),
125
126 /// Timeout during execution.
127 #[error("timeout after {elapsed_ms}ms")]
128 Timeout { elapsed_ms: u64 },
129
130 /// Internal error.
131 #[error("internal: {0}")]
132 Internal(String),
133}
134
135impl ChildError {
136 /// Returns the error kind as a string identifier.
137 #[must_use]
138 pub fn kind(&self) -> &'static str {
139 match self {
140 Self::ExecutionFailed { .. } => "execution_failed",
141 Self::InvalidInput(_) => "invalid_input",
142 Self::Timeout { .. } => "timeout",
143 Self::Internal(_) => "internal",
144 }
145 }
146}
147
148/// A child entity managed by a Component.
149///
150/// All entities held inside a Component must implement this trait.
151/// This ensures they can:
152///
153/// - Be identified ([`Identifiable`])
154/// - Respond to signals ([`SignalReceiver`])
155/// - Report status ([`Statusable`])
156///
157/// # Object Safety
158///
159/// This trait is object-safe, allowing `Box<dyn Child>`.
160///
161/// # Signal Propagation
162///
163/// When a Component receives a signal, it MUST forward it to all children:
164///
165/// ```ignore
166/// fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
167/// // Handle self first
168/// // ...
169///
170/// // Forward to ALL children
171/// for child in &mut self.children {
172/// child.on_signal(signal);
173/// }
174/// }
175/// ```
176///
177/// # Type Parameter Alternative
178///
179/// For stronger typing, Components can use:
180///
181/// ```ignore
182/// struct MyComponent<C: Child> {
183/// children: Vec<C>,
184/// }
185/// ```
186pub trait Child: Identifiable + SignalReceiver + Statusable + Send + Sync {
187 /// Inject a [`ChildContext`](crate::ChildContext) so the child can use `orcs.*` functions at
188 /// runtime (RPC, exec, spawn, file tools, etc.).
189 ///
190 /// The default implementation is a no-op — override when the child
191 /// actually needs context (e.g. `LuaChild`).
192 fn set_context(&mut self, _ctx: Box<dyn super::ChildContext>) {}
193}
194
195// ============================================
196// Internal Result Type
197// ============================================
198
199/// Result of a Child's work execution.
200///
201/// Represents the outcome of [`RunnableChild::run()`].
202/// For serialization, convert to [`ChildResultDto`] using `.into()`.
203///
204/// # Variants
205///
206/// - `Ok`: Work completed successfully with optional output data
207/// - `Err`: Work failed with a typed error
208/// - `Aborted`: Work was interrupted by a Signal (Veto/Cancel)
209///
210/// # Example
211///
212/// ```
213/// use orcs_component::{ChildResult, ChildError};
214/// use serde_json::json;
215///
216/// let success = ChildResult::Ok(json!({"processed": true}));
217/// assert!(success.is_ok());
218///
219/// let failure = ChildResult::Err(ChildError::Timeout { elapsed_ms: 5000 });
220/// assert!(failure.is_err());
221///
222/// let aborted = ChildResult::Aborted;
223/// assert!(aborted.is_aborted());
224/// ```
225#[derive(Debug, Clone)]
226pub enum ChildResult {
227 /// Work completed successfully.
228 Ok(Value),
229 /// Work failed with a typed error.
230 Err(ChildError),
231 /// Work was aborted by a Signal.
232 Aborted,
233}
234
235impl ChildResult {
236 /// Returns `true` if the result is `Ok`.
237 #[must_use]
238 pub fn is_ok(&self) -> bool {
239 matches!(self, Self::Ok(_))
240 }
241
242 /// Returns `true` if the result is `Err`.
243 #[must_use]
244 pub fn is_err(&self) -> bool {
245 matches!(self, Self::Err(_))
246 }
247
248 /// Returns `true` if the result is `Aborted`.
249 #[must_use]
250 pub fn is_aborted(&self) -> bool {
251 matches!(self, Self::Aborted)
252 }
253
254 /// Returns the success value if `Ok`, otherwise `None`.
255 #[must_use]
256 pub fn ok(self) -> Option<Value> {
257 match self {
258 Self::Ok(v) => Some(v),
259 _ => None,
260 }
261 }
262
263 /// Returns the error if `Err`, otherwise `None`.
264 #[must_use]
265 pub fn err(self) -> Option<ChildError> {
266 match self {
267 Self::Err(e) => Some(e),
268 _ => None,
269 }
270 }
271}
272
273impl Default for ChildResult {
274 fn default() -> Self {
275 Self::Ok(Value::Null)
276 }
277}
278
279impl From<Value> for ChildResult {
280 fn from(value: Value) -> Self {
281 Self::Ok(value)
282 }
283}
284
285impl From<ChildError> for ChildResult {
286 fn from(err: ChildError) -> Self {
287 Self::Err(err)
288 }
289}
290
291// ============================================
292// External DTO Type (Serializable)
293// ============================================
294
295/// Serializable representation of [`ChildResult`].
296///
297/// Use this type for IPC, persistence, or JSON serialization.
298///
299/// # Example
300///
301/// ```
302/// use orcs_component::{ChildResult, ChildResultDto, ChildError};
303/// use serde_json::json;
304///
305/// let result = ChildResult::Err(ChildError::Timeout { elapsed_ms: 5000 });
306/// let dto: ChildResultDto = result.into();
307///
308/// let json = serde_json::to_string(&dto).expect("ChildResultDto should serialize");
309/// assert!(json.contains("timeout"));
310/// ```
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312pub enum ChildResultDto {
313 /// Work completed successfully.
314 Ok(Value),
315 /// Work failed with error details.
316 Err {
317 /// Error kind identifier (e.g., "timeout", "invalid_input").
318 kind: String,
319 /// Human-readable error message.
320 message: String,
321 },
322 /// Work was aborted by a Signal.
323 Aborted,
324}
325
326impl ChildResultDto {
327 /// Returns `true` if the result is `Ok`.
328 #[must_use]
329 pub fn is_ok(&self) -> bool {
330 matches!(self, Self::Ok(_))
331 }
332
333 /// Returns `true` if the result is `Err`.
334 #[must_use]
335 pub fn is_err(&self) -> bool {
336 matches!(self, Self::Err { .. })
337 }
338
339 /// Returns `true` if the result is `Aborted`.
340 #[must_use]
341 pub fn is_aborted(&self) -> bool {
342 matches!(self, Self::Aborted)
343 }
344}
345
346impl From<ChildResult> for ChildResultDto {
347 fn from(result: ChildResult) -> Self {
348 match result {
349 ChildResult::Ok(v) => Self::Ok(v),
350 ChildResult::Err(e) => Self::Err {
351 kind: e.kind().to_string(),
352 message: e.to_string(),
353 },
354 ChildResult::Aborted => Self::Aborted,
355 }
356 }
357}
358
359impl Default for ChildResultDto {
360 fn default() -> Self {
361 Self::Ok(Value::Null)
362 }
363}
364
365/// A Child that can actively execute work.
366///
367/// Extends [`Child`] with the ability to run tasks.
368/// Used for SubAgents, Workers, and Skills that need to
369/// perform actual work rather than just respond to signals.
370///
371/// # Architecture
372///
373/// ```text
374/// Component (Manager)
375/// │
376/// │ spawn_child()
377/// ▼
378/// RunnableChild (Worker)
379/// │
380/// │ run(input)
381/// ▼
382/// ChildResult
383/// ```
384///
385/// # Signal Handling
386///
387/// During `run()`, the child should periodically check for
388/// signals and return `ChildResult::Aborted` if a Veto/Cancel
389/// is received.
390///
391/// # Example
392///
393/// ```
394/// use orcs_component::{Child, RunnableChild, ChildResult, Identifiable, SignalReceiver, Statusable, Status};
395/// use orcs_event::{Signal, SignalResponse};
396/// use serde_json::{json, Value};
397///
398/// struct Worker {
399/// id: String,
400/// status: Status,
401/// }
402///
403/// impl Identifiable for Worker {
404/// fn id(&self) -> &str { &self.id }
405/// }
406///
407/// impl SignalReceiver for Worker {
408/// fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
409/// if signal.is_veto() {
410/// self.status = Status::Aborted;
411/// SignalResponse::Abort
412/// } else {
413/// SignalResponse::Handled
414/// }
415/// }
416/// fn abort(&mut self) { self.status = Status::Aborted; }
417/// }
418///
419/// impl Statusable for Worker {
420/// fn status(&self) -> Status { self.status }
421/// }
422///
423/// impl Child for Worker {}
424///
425/// impl RunnableChild for Worker {
426/// fn run(&mut self, input: Value) -> ChildResult {
427/// self.status = Status::Running;
428/// // Do work...
429/// let result = json!({"input": input, "processed": true});
430/// self.status = Status::Idle;
431/// ChildResult::Ok(result)
432/// }
433/// }
434/// ```
435pub trait RunnableChild: Child {
436 /// Execute work with the given input (synchronous).
437 ///
438 /// # Arguments
439 ///
440 /// * `input` - Input data for the work (JSON value)
441 ///
442 /// # Returns
443 ///
444 /// - `ChildResult::Ok(value)` on success
445 /// - `ChildResult::Err(error)` on failure
446 /// - `ChildResult::Aborted` if interrupted by signal
447 ///
448 /// # Contract
449 ///
450 /// - Should update status to `Running` at start
451 /// - Should update status to `Idle` or `Completed` on success
452 /// - Should periodically check for signals during long operations
453 fn run(&mut self, input: Value) -> ChildResult;
454}
455
456/// Async version of [`RunnableChild`].
457///
458/// Use this trait for children that perform async I/O operations
459/// such as LLM API calls, network requests, or file I/O.
460///
461/// # Example
462///
463/// ```ignore
464/// use orcs_component::{AsyncRunnableChild, ChildResult, Child};
465/// use async_trait::async_trait;
466/// use serde_json::Value;
467///
468/// struct LlmWorker {
469/// // ...
470/// }
471///
472/// #[async_trait]
473/// impl AsyncRunnableChild for LlmWorker {
474/// async fn run(&mut self, input: Value) -> ChildResult {
475/// // Async LLM call
476/// let response = self.client.complete(&input).await?;
477/// ChildResult::Ok(response)
478/// }
479/// }
480/// ```
481///
482/// # When to Use
483///
484/// | Trait | Use Case |
485/// |-------|----------|
486/// | `RunnableChild` | CPU-bound, quick tasks |
487/// | `AsyncRunnableChild` | I/O-bound, network, LLM calls |
488#[async_trait]
489pub trait AsyncRunnableChild: Child {
490 /// Execute work with the given input (asynchronous).
491 ///
492 /// # Arguments
493 ///
494 /// * `input` - Input data for the work (JSON value)
495 ///
496 /// # Returns
497 ///
498 /// - `ChildResult::Ok(value)` on success
499 /// - `ChildResult::Err(error)` on failure
500 /// - `ChildResult::Aborted` if interrupted by signal
501 ///
502 /// # Contract
503 ///
504 /// - Should update status to `Running` at start
505 /// - Should update status to `Idle` or `Completed` on success
506 /// - Should check for cancellation using `tokio::select!` or similar
507 async fn run(&mut self, input: Value) -> ChildResult;
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use crate::Status;
514 use orcs_event::{Signal, SignalResponse};
515
516 struct TestChild {
517 id: String,
518 status: Status,
519 }
520
521 impl Identifiable for TestChild {
522 fn id(&self) -> &str {
523 &self.id
524 }
525 }
526
527 impl SignalReceiver for TestChild {
528 fn on_signal(&mut self, _signal: &Signal) -> SignalResponse {
529 SignalResponse::Ignored
530 }
531
532 fn abort(&mut self) {
533 self.status = Status::Aborted;
534 }
535 }
536
537 impl Statusable for TestChild {
538 fn status(&self) -> Status {
539 self.status
540 }
541 }
542
543 impl Child for TestChild {}
544
545 #[test]
546 fn child_object_safety() {
547 let child: Box<dyn Child> = Box::new(TestChild {
548 id: "test".into(),
549 status: Status::Idle,
550 });
551
552 assert_eq!(child.id(), "test");
553 assert_eq!(child.status(), Status::Idle);
554 }
555
556 #[test]
557 fn child_collection() {
558 let children: Vec<Box<dyn Child>> = vec![
559 Box::new(TestChild {
560 id: "child-1".into(),
561 status: Status::Running,
562 }),
563 Box::new(TestChild {
564 id: "child-2".into(),
565 status: Status::Idle,
566 }),
567 ];
568
569 assert_eq!(children.len(), 2);
570 assert_eq!(children[0].id(), "child-1");
571 assert_eq!(children[1].id(), "child-2");
572 }
573
574 // --- ChildError tests ---
575
576 #[test]
577 fn child_error_kind() {
578 assert_eq!(
579 ChildError::ExecutionFailed { reason: "x".into() }.kind(),
580 "execution_failed"
581 );
582 assert_eq!(ChildError::InvalidInput("x".into()).kind(), "invalid_input");
583 assert_eq!(ChildError::Timeout { elapsed_ms: 100 }.kind(), "timeout");
584 assert_eq!(ChildError::Internal("x".into()).kind(), "internal");
585 }
586
587 #[test]
588 fn child_error_display() {
589 let err = ChildError::Timeout { elapsed_ms: 5000 };
590 assert_eq!(err.to_string(), "timeout after 5000ms");
591 }
592
593 // --- ChildResult tests ---
594
595 #[test]
596 fn child_result_ok() {
597 let result = ChildResult::Ok(serde_json::json!({"done": true}));
598 assert!(result.is_ok());
599 assert!(!result.is_err());
600 assert!(!result.is_aborted());
601 }
602
603 #[test]
604 fn child_result_err() {
605 let result = ChildResult::Err(ChildError::ExecutionFailed {
606 reason: "failed".into(),
607 });
608 assert!(!result.is_ok());
609 assert!(result.is_err());
610 assert!(!result.is_aborted());
611 }
612
613 #[test]
614 fn child_result_aborted() {
615 let result = ChildResult::Aborted;
616 assert!(!result.is_ok());
617 assert!(!result.is_err());
618 assert!(result.is_aborted());
619 }
620
621 #[test]
622 fn child_result_ok_extract() {
623 let result = ChildResult::Ok(serde_json::json!(42));
624 let value = result.ok();
625 assert_eq!(value, Some(serde_json::json!(42)));
626 }
627
628 #[test]
629 fn child_result_err_extract() {
630 let result = ChildResult::Err(ChildError::InvalidInput("bad input".into()));
631 let err = result.err();
632 assert!(err.is_some());
633 assert_eq!(
634 err.expect("ChildResult::Err should yield Some(ChildError)")
635 .kind(),
636 "invalid_input"
637 );
638 }
639
640 #[test]
641 fn child_result_default() {
642 let result = ChildResult::default();
643 assert!(result.is_ok());
644 assert_eq!(result.ok(), Some(Value::Null));
645 }
646
647 #[test]
648 fn child_result_from_value() {
649 let result: ChildResult = serde_json::json!({"key": "value"}).into();
650 assert!(result.is_ok());
651 }
652
653 #[test]
654 fn child_result_from_error() {
655 let result: ChildResult = ChildError::Internal("oops".into()).into();
656 assert!(result.is_err());
657 }
658
659 // --- ChildResultDto tests ---
660
661 #[test]
662 fn child_result_dto_from_ok() {
663 let result = ChildResult::Ok(serde_json::json!({"done": true}));
664 let dto: ChildResultDto = result.into();
665
666 assert!(dto.is_ok());
667 assert!(!dto.is_err());
668 assert!(!dto.is_aborted());
669 }
670
671 #[test]
672 fn child_result_dto_from_err() {
673 let result = ChildResult::Err(ChildError::Timeout { elapsed_ms: 3000 });
674 let dto: ChildResultDto = result.into();
675
676 assert!(dto.is_err());
677 if let ChildResultDto::Err { kind, message } = dto {
678 assert_eq!(kind, "timeout");
679 assert!(message.contains("3000"));
680 } else {
681 panic!("expected Err variant");
682 }
683 }
684
685 #[test]
686 fn child_result_dto_from_aborted() {
687 let result = ChildResult::Aborted;
688 let dto: ChildResultDto = result.into();
689
690 assert!(dto.is_aborted());
691 }
692
693 #[test]
694 fn child_result_dto_serialization_err() {
695 let dto = ChildResultDto::Err {
696 kind: "timeout".into(),
697 message: "timeout after 5000ms".into(),
698 };
699
700 let json =
701 serde_json::to_string(&dto).expect("ChildResultDto::Err should serialize to JSON");
702 let restored: ChildResultDto =
703 serde_json::from_str(&json).expect("ChildResultDto::Err should deserialize from JSON");
704
705 assert_eq!(dto, restored);
706 }
707
708 #[test]
709 fn child_result_dto_serialization_ok_roundtrip() {
710 let original = ChildResultDto::Ok(serde_json::json!({"key": "value", "count": 42}));
711 let json =
712 serde_json::to_string(&original).expect("ChildResultDto::Ok should serialize to JSON");
713 let restored: ChildResultDto =
714 serde_json::from_str(&json).expect("ChildResultDto::Ok should deserialize from JSON");
715
716 assert_eq!(original, restored);
717 }
718
719 #[test]
720 fn child_result_dto_serialization_aborted_roundtrip() {
721 let original = ChildResultDto::Aborted;
722 let json = serde_json::to_string(&original)
723 .expect("ChildResultDto::Aborted should serialize to JSON");
724 let restored: ChildResultDto = serde_json::from_str(&json)
725 .expect("ChildResultDto::Aborted should deserialize from JSON");
726
727 assert_eq!(original, restored);
728 }
729
730 #[test]
731 fn child_result_to_dto_roundtrip() {
732 // ChildResult → ChildResultDto → JSON → ChildResultDto
733 let result = ChildResult::Err(ChildError::ExecutionFailed {
734 reason: "network error".into(),
735 });
736 let dto: ChildResultDto = result.into();
737 let json = serde_json::to_string(&dto)
738 .expect("ChildResultDto from ChildResult::Err should serialize to JSON");
739 let restored: ChildResultDto = serde_json::from_str(&json)
740 .expect("ChildResultDto should deserialize from JSON in roundtrip test");
741
742 assert_eq!(dto, restored);
743 if let ChildResultDto::Err { kind, message } = restored {
744 assert_eq!(kind, "execution_failed");
745 assert!(message.contains("network error"));
746 }
747 }
748
749 #[test]
750 fn child_result_dto_default() {
751 let dto = ChildResultDto::default();
752 assert!(dto.is_ok());
753 }
754
755 // --- RunnableChild tests ---
756
757 struct TestRunnableChild {
758 id: String,
759 status: Status,
760 }
761
762 impl Identifiable for TestRunnableChild {
763 fn id(&self) -> &str {
764 &self.id
765 }
766 }
767
768 impl SignalReceiver for TestRunnableChild {
769 fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
770 if signal.is_veto() {
771 self.status = Status::Aborted;
772 SignalResponse::Abort
773 } else {
774 SignalResponse::Handled
775 }
776 }
777
778 fn abort(&mut self) {
779 self.status = Status::Aborted;
780 }
781 }
782
783 impl Statusable for TestRunnableChild {
784 fn status(&self) -> Status {
785 self.status
786 }
787 }
788
789 impl Child for TestRunnableChild {}
790
791 impl RunnableChild for TestRunnableChild {
792 fn run(&mut self, input: Value) -> ChildResult {
793 self.status = Status::Running;
794 // Simulate work
795 let result = serde_json::json!({
796 "input": input,
797 "processed": true
798 });
799 self.status = Status::Idle;
800 ChildResult::Ok(result)
801 }
802 }
803
804 #[test]
805 fn runnable_child_run() {
806 let mut child = TestRunnableChild {
807 id: "worker-1".into(),
808 status: Status::Idle,
809 };
810
811 let input = serde_json::json!({"task": "test"});
812 let result = child.run(input.clone());
813
814 assert!(result.is_ok());
815 if let ChildResult::Ok(value) = result {
816 assert_eq!(value["input"], input);
817 assert_eq!(value["processed"], true);
818 }
819 assert_eq!(child.status(), Status::Idle);
820 }
821
822 #[test]
823 fn runnable_child_object_safety() {
824 let child: Box<dyn RunnableChild> = Box::new(TestRunnableChild {
825 id: "worker".into(),
826 status: Status::Idle,
827 });
828
829 // Can be used as dyn RunnableChild
830 assert_eq!(child.id(), "worker");
831 assert_eq!(child.status(), Status::Idle);
832 }
833
834 // --- AsyncRunnableChild tests ---
835
836 struct TestAsyncChild {
837 id: String,
838 status: Status,
839 }
840
841 impl Identifiable for TestAsyncChild {
842 fn id(&self) -> &str {
843 &self.id
844 }
845 }
846
847 impl SignalReceiver for TestAsyncChild {
848 fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
849 if signal.is_veto() {
850 self.status = Status::Aborted;
851 SignalResponse::Abort
852 } else {
853 SignalResponse::Handled
854 }
855 }
856
857 fn abort(&mut self) {
858 self.status = Status::Aborted;
859 }
860 }
861
862 impl Statusable for TestAsyncChild {
863 fn status(&self) -> Status {
864 self.status
865 }
866 }
867
868 impl Child for TestAsyncChild {}
869
870 #[async_trait]
871 impl AsyncRunnableChild for TestAsyncChild {
872 async fn run(&mut self, input: Value) -> ChildResult {
873 self.status = Status::Running;
874 // Simulate async work
875 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
876 let result = serde_json::json!({
877 "input": input,
878 "async": true
879 });
880 self.status = Status::Idle;
881 ChildResult::Ok(result)
882 }
883 }
884
885 #[tokio::test]
886 async fn async_runnable_child_run() {
887 let mut child = TestAsyncChild {
888 id: "async-worker-1".into(),
889 status: Status::Idle,
890 };
891
892 let input = serde_json::json!({"task": "async_test"});
893 let result = child.run(input.clone()).await;
894
895 assert!(result.is_ok());
896 if let ChildResult::Ok(value) = result {
897 assert_eq!(value["input"], input);
898 assert_eq!(value["async"], true);
899 }
900 assert_eq!(child.status(), Status::Idle);
901 }
902
903 #[tokio::test]
904 async fn async_runnable_child_object_safety() {
905 let child: Box<dyn AsyncRunnableChild> = Box::new(TestAsyncChild {
906 id: "async-worker".into(),
907 status: Status::Idle,
908 });
909
910 // Can be used as dyn AsyncRunnableChild
911 assert_eq!(child.id(), "async-worker");
912 assert_eq!(child.status(), Status::Idle);
913 }
914}