1use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16
17include!("schema_types.rs");
19
20pub mod paths;
22
23pub mod agent_meta;
25
26pub mod spec;
28
29mod step_registration;
31
32pub fn parse_execution_graph(json: &serde_json::Value) -> Result<ExecutionGraph, String> {
38 serde_json::from_value(json.clone())
39 .map_err(|e| format!("Failed to parse execution graph: {}", e))
40}
41
42pub fn parse_scenario(json: &serde_json::Value) -> Result<Scenario, String> {
44 serde_json::from_value(json.clone()).map_err(|e| format!("Failed to parse scenario: {}", e))
45}
46
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
49pub struct StepTypeInfo {
50 #[serde(rename = "type")]
51 pub step_type: String,
52 pub category: String,
53 pub description: String,
54}
55
56pub fn get_step_types() -> Vec<StepTypeInfo> {
62 let mut steps = vec![StepTypeInfo {
64 step_type: "Start".to_string(),
65 category: "control".to_string(),
66 description: "Entry point - receives scenario inputs".to_string(),
67 }];
68
69 for meta in agent_meta::get_all_step_types() {
71 steps.push(StepTypeInfo {
72 step_type: meta.id.to_string(),
73 category: meta.category.to_string(),
74 description: meta.description.to_string(),
75 });
76 }
77
78 steps.sort_by(|a, b| a.step_type.cmp(&b.step_type));
80
81 steps
82}
83
84impl MemoryTier {
89 pub fn total_memory_bytes(&self) -> usize {
91 match self {
92 MemoryTier::S => 8 * 1024 * 1024, MemoryTier::M => 64 * 1024 * 1024, MemoryTier::L => 128 * 1024 * 1024, MemoryTier::XL => 256 * 1024 * 1024, }
97 }
98
99 pub fn stack_size_bytes(&self) -> usize {
101 match self {
102 MemoryTier::S => 1 * 1024 * 1024, MemoryTier::M => 4 * 1024 * 1024, MemoryTier::L => 8 * 1024 * 1024, MemoryTier::XL => 8 * 1024 * 1024, }
107 }
108
109 pub fn as_str(&self) -> &'static str {
111 match self {
112 MemoryTier::S => "S",
113 MemoryTier::M => "M",
114 MemoryTier::L => "L",
115 MemoryTier::XL => "XL",
116 }
117 }
118
119 pub fn from_str(s: &str) -> Option<Self> {
121 match s.to_uppercase().as_str() {
122 "S" => Some(MemoryTier::S),
123 "M" => Some(MemoryTier::M),
124 "L" => Some(MemoryTier::L),
125 "XL" => Some(MemoryTier::XL),
126 _ => None,
127 }
128 }
129}
130
131impl std::fmt::Display for MemoryTier {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "{}", self.as_str())
134 }
135}
136
137impl SchemaFieldType {
142 pub fn as_str(&self) -> &'static str {
144 match self {
145 SchemaFieldType::String => "string",
146 SchemaFieldType::Integer => "integer",
147 SchemaFieldType::Number => "number",
148 SchemaFieldType::Boolean => "boolean",
149 SchemaFieldType::Array => "array",
150 SchemaFieldType::Object => "object",
151 SchemaFieldType::File => "file",
152 }
153 }
154}
155
156impl std::fmt::Display for SchemaFieldType {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(f, "{}", self.as_str())
159 }
160}
161
162impl From<&SchemaFieldType> for String {
163 fn from(t: &SchemaFieldType) -> Self {
164 t.as_str().to_string()
165 }
166}
167
168impl MappingValue {
173 pub fn is_reference(&self) -> bool {
175 matches!(self, MappingValue::Reference(_))
176 }
177
178 pub fn is_immediate(&self) -> bool {
180 matches!(self, MappingValue::Immediate(_))
181 }
182
183 pub fn is_composite(&self) -> bool {
185 matches!(self, MappingValue::Composite(_))
186 }
187
188 pub fn as_reference_str(&self) -> Option<&str> {
190 match self {
191 MappingValue::Reference(r) => Some(&r.value),
192 _ => None,
193 }
194 }
195
196 pub fn as_immediate_value(&self) -> Option<&serde_json::Value> {
198 match self {
199 MappingValue::Immediate(i) => Some(&i.value),
200 _ => None,
201 }
202 }
203
204 pub fn as_composite(&self) -> Option<&CompositeInner> {
206 match self {
207 MappingValue::Composite(c) => Some(&c.value),
208 _ => None,
209 }
210 }
211
212 pub fn collect_references(&self) -> Vec<&str> {
214 match self {
215 MappingValue::Reference(r) => vec![r.value.as_str()],
216 MappingValue::Immediate(_) => vec![],
217 MappingValue::Composite(c) => c.value.collect_references(),
218 }
219 }
220
221 pub fn has_references(&self) -> bool {
223 match self {
224 MappingValue::Reference(_) => true,
225 MappingValue::Immediate(_) => false,
226 MappingValue::Composite(c) => c.value.has_references(),
227 }
228 }
229}
230
231impl CompositeInner {
236 pub fn is_object(&self) -> bool {
238 matches!(self, CompositeInner::Object(_))
239 }
240
241 pub fn is_array(&self) -> bool {
243 matches!(self, CompositeInner::Array(_))
244 }
245
246 pub fn as_object(&self) -> Option<&HashMap<String, MappingValue>> {
248 match self {
249 CompositeInner::Object(map) => Some(map),
250 _ => None,
251 }
252 }
253
254 pub fn as_array(&self) -> Option<&Vec<MappingValue>> {
256 match self {
257 CompositeInner::Array(arr) => Some(arr),
258 _ => None,
259 }
260 }
261
262 pub fn collect_references(&self) -> Vec<&str> {
264 match self {
265 CompositeInner::Object(map) => {
266 map.values().flat_map(|v| v.collect_references()).collect()
267 }
268 CompositeInner::Array(arr) => arr.iter().flat_map(|v| v.collect_references()).collect(),
269 }
270 }
271
272 pub fn has_references(&self) -> bool {
274 match self {
275 CompositeInner::Object(map) => map.values().any(|v| v.has_references()),
276 CompositeInner::Array(arr) => arr.iter().any(|v| v.has_references()),
277 }
278 }
279}
280
281#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_get_step_types_from_inventory() {
291 let step_types = get_step_types();
292
293 assert!(
295 step_types.len() >= 7,
296 "Expected at least 7 step types, got {}",
297 step_types.len()
298 );
299
300 let step_ids: Vec<&str> = step_types.iter().map(|s| s.step_type.as_str()).collect();
302
303 assert!(step_ids.contains(&"Start"), "Missing Start step type");
304 assert!(step_ids.contains(&"Finish"), "Missing Finish step type");
305 assert!(step_ids.contains(&"Agent"), "Missing Agent step type");
306 assert!(
307 step_ids.contains(&"Conditional"),
308 "Missing Conditional step type"
309 );
310 assert!(step_ids.contains(&"Split"), "Missing Split step type");
311 assert!(step_ids.contains(&"Switch"), "Missing Switch step type");
312 assert!(
313 step_ids.contains(&"StartScenario"),
314 "Missing StartScenario step type"
315 );
316 }
317
318 #[test]
319 fn test_step_type_categories() {
320 let step_types = get_step_types();
321
322 for step in &step_types {
323 match step.step_type.as_str() {
324 "Agent" | "StartScenario" => {
325 assert_eq!(
326 step.category, "execution",
327 "{} should be execution category",
328 step.step_type
329 );
330 }
331 "Start" | "Finish" | "Conditional" | "Split" | "Switch" => {
332 assert_eq!(
333 step.category, "control",
334 "{} should be control category",
335 step.step_type
336 );
337 }
338 _ => {}
339 }
340 }
341 }
342
343 #[test]
344 fn test_step_type_schema_generation() {
345 for meta in agent_meta::get_all_step_types() {
347 let schema = (meta.schema_fn)();
348 assert!(
350 schema.schema.metadata.is_some() || schema.definitions.len() > 0,
351 "Schema for {} should have metadata or definitions",
352 meta.id
353 );
354 }
355 }
356
357 #[test]
362 fn test_value_type_serialization() {
363 assert_eq!(
365 serde_json::to_string(&ValueType::Integer).unwrap(),
366 "\"integer\""
367 );
368 assert_eq!(
369 serde_json::to_string(&ValueType::Number).unwrap(),
370 "\"number\""
371 );
372 assert_eq!(
373 serde_json::to_string(&ValueType::Boolean).unwrap(),
374 "\"boolean\""
375 );
376 assert_eq!(
377 serde_json::to_string(&ValueType::String).unwrap(),
378 "\"string\""
379 );
380 assert_eq!(serde_json::to_string(&ValueType::Json).unwrap(), "\"json\"");
381 assert_eq!(serde_json::to_string(&ValueType::File).unwrap(), "\"file\"");
382 }
383
384 #[test]
385 fn test_value_type_deserialization() {
386 assert_eq!(
388 serde_json::from_str::<ValueType>("\"integer\"").unwrap(),
389 ValueType::Integer
390 );
391 assert_eq!(
392 serde_json::from_str::<ValueType>("\"number\"").unwrap(),
393 ValueType::Number
394 );
395 assert_eq!(
396 serde_json::from_str::<ValueType>("\"boolean\"").unwrap(),
397 ValueType::Boolean
398 );
399 }
400
401 #[test]
402 fn test_schema_field_type_serialization() {
403 assert_eq!(
404 serde_json::to_string(&SchemaFieldType::String).unwrap(),
405 "\"string\""
406 );
407 assert_eq!(
408 serde_json::to_string(&SchemaFieldType::Integer).unwrap(),
409 "\"integer\""
410 );
411 assert_eq!(
412 serde_json::to_string(&SchemaFieldType::Number).unwrap(),
413 "\"number\""
414 );
415 assert_eq!(
416 serde_json::to_string(&SchemaFieldType::Boolean).unwrap(),
417 "\"boolean\""
418 );
419 assert_eq!(
420 serde_json::to_string(&SchemaFieldType::Array).unwrap(),
421 "\"array\""
422 );
423 assert_eq!(
424 serde_json::to_string(&SchemaFieldType::Object).unwrap(),
425 "\"object\""
426 );
427 }
428
429 #[test]
430 fn test_schema_field_type_as_str() {
431 assert_eq!(SchemaFieldType::String.as_str(), "string");
432 assert_eq!(SchemaFieldType::Integer.as_str(), "integer");
433 assert_eq!(SchemaFieldType::Number.as_str(), "number");
434 assert_eq!(SchemaFieldType::Boolean.as_str(), "boolean");
435 assert_eq!(SchemaFieldType::Array.as_str(), "array");
436 assert_eq!(SchemaFieldType::Object.as_str(), "object");
437 }
438
439 #[test]
440 fn test_schema_field_type_display() {
441 assert_eq!(format!("{}", SchemaFieldType::String), "string");
442 assert_eq!(format!("{}", SchemaFieldType::Integer), "integer");
443 }
444
445 #[test]
446 fn test_switch_match_type_serialization() {
447 assert_eq!(
448 serde_json::to_string(&SwitchMatchType::Eq).unwrap(),
449 "\"EQ\""
450 );
451 assert_eq!(
452 serde_json::to_string(&SwitchMatchType::Gt).unwrap(),
453 "\"GT\""
454 );
455 assert_eq!(
456 serde_json::to_string(&SwitchMatchType::Between).unwrap(),
457 "\"BETWEEN\""
458 );
459 }
460
461 #[test]
462 fn test_switch_config_serialization() {
463 let config = SwitchConfig {
464 value: MappingValue::Reference(ReferenceValue {
465 value: "data.status".to_string(),
466 type_hint: None,
467 default: None,
468 }),
469 cases: vec![SwitchCase {
470 match_type: SwitchMatchType::Eq,
471 match_value: serde_json::json!("active"),
472 output: serde_json::json!({"result": true}),
473 }],
474 default: Some(serde_json::json!({"result": false})),
475 };
476
477 let json = serde_json::to_value(&config).unwrap();
478 assert!(json.get("value").is_some());
479 assert!(json.get("cases").is_some());
480 assert!(json.get("default").is_some());
481 }
482
483 #[test]
484 fn test_split_config_serialization() {
485 let config = SplitConfig {
486 value: MappingValue::Reference(ReferenceValue {
487 value: "data.items".to_string(),
488 type_hint: None,
489 default: None,
490 }),
491 parallelism: Some(5),
492 sequential: Some(false),
493 dont_stop_on_failed: Some(true),
494 variables: None,
495 max_retries: None,
496 retry_delay: None,
497 timeout: None,
498 };
499
500 let json = serde_json::to_value(&config).unwrap();
501 assert!(json.get("value").is_some());
502 assert_eq!(json.get("parallelism").unwrap(), 5);
503 assert_eq!(json.get("sequential").unwrap(), false);
504 assert_eq!(json.get("dontStopOnFailed").unwrap(), true);
505 }
506
507 #[test]
508 fn test_switch_step_with_config() {
509 let step = SwitchStep {
510 id: "switch1".to_string(),
511 name: Some("My Switch".to_string()),
512 config: Some(SwitchConfig {
513 value: MappingValue::Immediate(ImmediateValue {
514 value: serde_json::json!("test"),
515 }),
516 cases: vec![],
517 default: None,
518 }),
519 };
520
521 let json = serde_json::to_value(&step).unwrap();
522 assert_eq!(json.get("id").unwrap(), "switch1");
523 assert!(json.get("config").is_some());
524 }
525
526 #[test]
527 fn test_split_step_with_config() {
528 let step = SplitStep {
529 id: "split1".to_string(),
530 name: None,
531 subgraph: Box::new(ExecutionGraph {
532 name: None,
533 description: None,
534 steps: HashMap::new(),
535 entry_point: "start".to_string(),
536 execution_plan: vec![],
537 variables: HashMap::new(),
538 input_schema: HashMap::new(),
539 output_schema: HashMap::new(),
540 notes: None,
541 nodes: None,
542 edges: None,
543 }),
544 config: Some(SplitConfig {
545 value: MappingValue::Reference(ReferenceValue {
546 value: "data.items".to_string(),
547 type_hint: None,
548 default: None,
549 }),
550 parallelism: None,
551 sequential: None,
552 dont_stop_on_failed: None,
553 variables: None,
554 max_retries: None,
555 retry_delay: None,
556 timeout: None,
557 }),
558 input_schema: HashMap::new(),
559 output_schema: HashMap::new(),
560 };
561
562 let json = serde_json::to_value(&step).unwrap();
563 assert_eq!(json.get("id").unwrap(), "split1");
564 assert!(json.get("config").is_some());
565 assert!(json.get("subgraph").is_some());
566 }
567
568 #[test]
569 fn test_dsl_version() {
570 assert_eq!(DSL_VERSION, "3.0.0");
571 }
572
573 #[test]
578 fn test_parse_execution_graph_minimal() {
579 let json = serde_json::json!({
580 "entryPoint": "start",
581 "steps": {},
582 "executionPlan": [],
583 "variables": {},
584 "inputSchema": {},
585 "outputSchema": {}
586 });
587
588 let graph = parse_execution_graph(&json).expect("Should parse minimal graph");
589 assert_eq!(graph.entry_point, "start");
590 assert!(graph.steps.is_empty());
591 }
592
593 #[test]
594 fn test_parse_execution_graph_with_steps() {
595 let json = serde_json::json!({
597 "entryPoint": "step1",
598 "steps": {
599 "step1": {
600 "stepType": "Finish",
601 "id": "step1",
602 "name": "End Step"
603 }
604 },
605 "executionPlan": [
606 { "fromStep": "start", "toStep": "step1" }
607 ],
608 "variables": {},
609 "inputSchema": {},
610 "outputSchema": {}
611 });
612
613 let graph = parse_execution_graph(&json).expect("Should parse graph with steps");
614 assert_eq!(graph.entry_point, "step1");
615 assert_eq!(graph.steps.len(), 1);
616 assert!(graph.steps.contains_key("step1"));
617 }
618
619 #[test]
620 fn test_parse_execution_graph_invalid_json() {
621 let json = serde_json::json!({
622 "wrong_field": "value"
623 });
624
625 let result = parse_execution_graph(&json);
626 assert!(result.is_err());
627 assert!(result.unwrap_err().contains("Failed to parse"));
628 }
629
630 #[test]
631 fn test_parse_scenario_minimal() {
632 let json = serde_json::json!({
633 "executionGraph": {
634 "name": "Test Scenario",
635 "description": "A test",
636 "entryPoint": "start",
637 "steps": {},
638 "executionPlan": [],
639 "variables": {},
640 "inputSchema": {},
641 "outputSchema": {}
642 }
643 });
644
645 let scenario = parse_scenario(&json).expect("Should parse minimal scenario");
646 assert_eq!(
647 scenario.execution_graph.name.as_deref(),
648 Some("Test Scenario")
649 );
650 assert_eq!(
651 scenario.execution_graph.description.as_deref(),
652 Some("A test")
653 );
654 }
655
656 #[test]
657 fn test_parse_scenario_with_metadata() {
658 let json = serde_json::json!({
659 "memoryTier": "L",
660 "debugMode": true,
661 "executionGraph": {
662 "name": "Complete Scenario",
663 "description": "With metadata",
664 "entryPoint": "start",
665 "steps": {},
666 "executionPlan": [],
667 "variables": {},
668 "inputSchema": {},
669 "outputSchema": {}
670 }
671 });
672
673 let scenario = parse_scenario(&json).expect("Should parse scenario with metadata");
674 assert_eq!(
675 scenario.execution_graph.name.as_deref(),
676 Some("Complete Scenario")
677 );
678 assert_eq!(scenario.memory_tier, Some(MemoryTier::L));
679 assert_eq!(scenario.debug_mode, Some(true));
680 }
681
682 #[test]
683 fn test_parse_scenario_invalid() {
684 let json = serde_json::json!({
685 "not_a_scenario": true
686 });
687
688 let result = parse_scenario(&json);
689 assert!(result.is_err());
690 assert!(result.unwrap_err().contains("Failed to parse scenario"));
691 }
692
693 #[test]
698 fn test_memory_tier_total_memory_bytes() {
699 assert_eq!(MemoryTier::S.total_memory_bytes(), 8 * 1024 * 1024);
700 assert_eq!(MemoryTier::M.total_memory_bytes(), 64 * 1024 * 1024);
701 assert_eq!(MemoryTier::L.total_memory_bytes(), 128 * 1024 * 1024);
702 assert_eq!(MemoryTier::XL.total_memory_bytes(), 256 * 1024 * 1024);
703 }
704
705 #[test]
706 fn test_memory_tier_stack_size_bytes() {
707 assert_eq!(MemoryTier::S.stack_size_bytes(), 1 * 1024 * 1024);
708 assert_eq!(MemoryTier::M.stack_size_bytes(), 4 * 1024 * 1024);
709 assert_eq!(MemoryTier::L.stack_size_bytes(), 8 * 1024 * 1024);
710 assert_eq!(MemoryTier::XL.stack_size_bytes(), 8 * 1024 * 1024);
711 }
712
713 #[test]
714 fn test_memory_tier_as_str() {
715 assert_eq!(MemoryTier::S.as_str(), "S");
716 assert_eq!(MemoryTier::M.as_str(), "M");
717 assert_eq!(MemoryTier::L.as_str(), "L");
718 assert_eq!(MemoryTier::XL.as_str(), "XL");
719 }
720
721 #[test]
722 fn test_memory_tier_from_str() {
723 assert_eq!(MemoryTier::from_str("S"), Some(MemoryTier::S));
724 assert_eq!(MemoryTier::from_str("M"), Some(MemoryTier::M));
725 assert_eq!(MemoryTier::from_str("L"), Some(MemoryTier::L));
726 assert_eq!(MemoryTier::from_str("XL"), Some(MemoryTier::XL));
727 }
728
729 #[test]
730 fn test_memory_tier_from_str_case_insensitive() {
731 assert_eq!(MemoryTier::from_str("s"), Some(MemoryTier::S));
732 assert_eq!(MemoryTier::from_str("m"), Some(MemoryTier::M));
733 assert_eq!(MemoryTier::from_str("l"), Some(MemoryTier::L));
734 assert_eq!(MemoryTier::from_str("xl"), Some(MemoryTier::XL));
735 assert_eq!(MemoryTier::from_str("Xl"), Some(MemoryTier::XL));
736 assert_eq!(MemoryTier::from_str("xL"), Some(MemoryTier::XL));
737 }
738
739 #[test]
740 fn test_memory_tier_from_str_invalid() {
741 assert_eq!(MemoryTier::from_str("XXL"), None);
742 assert_eq!(MemoryTier::from_str(""), None);
743 assert_eq!(MemoryTier::from_str("invalid"), None);
744 assert_eq!(MemoryTier::from_str("SM"), None);
745 }
746
747 #[test]
748 fn test_memory_tier_display() {
749 assert_eq!(format!("{}", MemoryTier::S), "S");
750 assert_eq!(format!("{}", MemoryTier::M), "M");
751 assert_eq!(format!("{}", MemoryTier::L), "L");
752 assert_eq!(format!("{}", MemoryTier::XL), "XL");
753 }
754
755 #[test]
756 fn test_memory_tier_serialization() {
757 assert_eq!(serde_json::to_string(&MemoryTier::S).unwrap(), "\"S\"");
758 assert_eq!(serde_json::to_string(&MemoryTier::XL).unwrap(), "\"XL\"");
759 }
760
761 #[test]
762 fn test_memory_tier_deserialization() {
763 assert_eq!(
764 serde_json::from_str::<MemoryTier>("\"S\"").unwrap(),
765 MemoryTier::S
766 );
767 assert_eq!(
768 serde_json::from_str::<MemoryTier>("\"XL\"").unwrap(),
769 MemoryTier::XL
770 );
771 }
772
773 #[test]
778 fn test_mapping_value_is_reference() {
779 let ref_val = MappingValue::Reference(ReferenceValue {
780 value: "data.field".to_string(),
781 type_hint: None,
782 default: None,
783 });
784 let imm_val = MappingValue::Immediate(ImmediateValue {
785 value: serde_json::json!("static"),
786 });
787
788 assert!(ref_val.is_reference());
789 assert!(!ref_val.is_immediate());
790 assert!(!imm_val.is_reference());
791 assert!(imm_val.is_immediate());
792 }
793
794 #[test]
795 fn test_mapping_value_as_reference_str() {
796 let ref_val = MappingValue::Reference(ReferenceValue {
797 value: "steps.agent1.outputs.data".to_string(),
798 type_hint: None,
799 default: None,
800 });
801 let imm_val = MappingValue::Immediate(ImmediateValue {
802 value: serde_json::json!("static"),
803 });
804
805 assert_eq!(
806 ref_val.as_reference_str(),
807 Some("steps.agent1.outputs.data")
808 );
809 assert_eq!(imm_val.as_reference_str(), None);
810 }
811
812 #[test]
813 fn test_mapping_value_as_immediate_value() {
814 let ref_val = MappingValue::Reference(ReferenceValue {
815 value: "data.field".to_string(),
816 type_hint: None,
817 default: None,
818 });
819 let imm_val = MappingValue::Immediate(ImmediateValue {
820 value: serde_json::json!({"key": "value"}),
821 });
822
823 assert!(ref_val.as_immediate_value().is_none());
824 assert_eq!(
825 imm_val.as_immediate_value(),
826 Some(&serde_json::json!({"key": "value"}))
827 );
828 }
829
830 #[test]
831 fn test_mapping_value_reference_with_type_hint() {
832 let ref_val = MappingValue::Reference(ReferenceValue {
833 value: "data.count".to_string(),
834 type_hint: Some(ValueType::Integer),
835 default: None,
836 });
837
838 assert!(ref_val.is_reference());
839 if let MappingValue::Reference(r) = ref_val {
840 assert_eq!(r.type_hint, Some(ValueType::Integer));
841 }
842 }
843
844 #[test]
845 fn test_mapping_value_reference_with_default() {
846 let ref_val = MappingValue::Reference(ReferenceValue {
847 value: "data.optional".to_string(),
848 type_hint: None,
849 default: Some(serde_json::json!("default_value")),
850 });
851
852 if let MappingValue::Reference(r) = ref_val {
853 assert_eq!(r.default, Some(serde_json::json!("default_value")));
854 }
855 }
856
857 #[test]
862 fn test_schema_field_type_from_string() {
863 let s: String = (&SchemaFieldType::String).into();
864 assert_eq!(s, "string");
865
866 let i: String = (&SchemaFieldType::Integer).into();
867 assert_eq!(i, "integer");
868
869 let o: String = (&SchemaFieldType::Object).into();
870 assert_eq!(o, "object");
871 }
872
873 #[test]
878 fn test_step_type_info_serialization() {
879 let info = StepTypeInfo {
880 step_type: "Agent".to_string(),
881 category: "execution".to_string(),
882 description: "Execute an agent capability".to_string(),
883 };
884
885 let json = serde_json::to_value(&info).unwrap();
886 assert_eq!(json.get("type").unwrap(), "Agent");
887 assert_eq!(json.get("category").unwrap(), "execution");
888 }
889
890 #[test]
891 fn test_step_type_info_deserialization() {
892 let json = serde_json::json!({
893 "type": "Conditional",
894 "category": "control",
895 "description": "Branch based on condition"
896 });
897
898 let info: StepTypeInfo = serde_json::from_value(json).unwrap();
899 assert_eq!(info.step_type, "Conditional");
900 assert_eq!(info.category, "control");
901 }
902
903 #[test]
904 fn test_get_step_types_sorted() {
905 let step_types = get_step_types();
906
907 for i in 1..step_types.len() {
909 assert!(
910 step_types[i - 1].step_type <= step_types[i].step_type,
911 "Step types should be sorted alphabetically"
912 );
913 }
914 }
915
916 #[test]
917 fn test_get_step_types_includes_start() {
918 let step_types = get_step_types();
919
920 let start = step_types.iter().find(|s| s.step_type == "Start");
921 assert!(start.is_some(), "Start step should be included");
922 let start = start.unwrap();
923 assert_eq!(start.category, "control");
924 assert!(start.description.contains("Entry point"));
925 }
926
927 #[test]
932 fn test_reference_value_serialization() {
933 let ref_val = ReferenceValue {
934 value: "steps.agent.outputs.result".to_string(),
935 type_hint: Some(ValueType::String),
936 default: Some(serde_json::json!("fallback")),
937 };
938
939 let json = serde_json::to_value(&ref_val).unwrap();
940 assert_eq!(json.get("value").unwrap(), "steps.agent.outputs.result");
942 assert_eq!(json.get("type").unwrap(), "string");
944 assert_eq!(json.get("default").unwrap(), "fallback");
945 }
946
947 #[test]
948 fn test_immediate_value_serialization() {
949 let imm_val = ImmediateValue {
950 value: serde_json::json!({
951 "nested": {
952 "array": [1, 2, 3]
953 }
954 }),
955 };
956
957 let json = serde_json::to_value(&imm_val).unwrap();
958 let value = json.get("value").expect("Should have value field");
960 assert!(value.get("nested").is_some());
961 }
962
963 #[test]
964 fn test_mapping_value_round_trip() {
965 let original = MappingValue::Reference(ReferenceValue {
967 value: "data.path".to_string(),
968 type_hint: None,
969 default: None,
970 });
971 let json = serde_json::to_string(&original).unwrap();
972 let parsed: MappingValue = serde_json::from_str(&json).unwrap();
973 assert!(parsed.is_reference());
974 assert_eq!(parsed.as_reference_str(), Some("data.path"));
975
976 let original_imm = MappingValue::Immediate(ImmediateValue {
978 value: serde_json::json!(42),
979 });
980 let json_imm = serde_json::to_string(&original_imm).unwrap();
981 let parsed_imm: MappingValue = serde_json::from_str(&json_imm).unwrap();
982 assert!(parsed_imm.is_immediate());
983 assert_eq!(
984 parsed_imm.as_immediate_value(),
985 Some(&serde_json::json!(42))
986 );
987 }
988
989 #[test]
994 fn test_composite_value_object_serialization() {
995 let mut fields = HashMap::new();
996 fields.insert(
997 "name".to_string(),
998 MappingValue::Reference(ReferenceValue {
999 value: "data.user.name".to_string(),
1000 type_hint: None,
1001 default: None,
1002 }),
1003 );
1004 fields.insert(
1005 "count".to_string(),
1006 MappingValue::Immediate(ImmediateValue {
1007 value: serde_json::json!(42),
1008 }),
1009 );
1010
1011 let composite = MappingValue::Composite(CompositeValue {
1012 value: CompositeInner::Object(fields),
1013 });
1014 let json = serde_json::to_value(&composite).unwrap();
1015
1016 assert_eq!(json.get("valueType").unwrap(), "composite");
1017 let value = json.get("value").unwrap();
1018 assert!(value.is_object());
1019 assert!(value.get("name").is_some());
1020 assert!(value.get("count").is_some());
1021 }
1022
1023 #[test]
1024 fn test_composite_value_array_serialization() {
1025 let elements = vec![
1026 MappingValue::Reference(ReferenceValue {
1027 value: "data.first".to_string(),
1028 type_hint: None,
1029 default: None,
1030 }),
1031 MappingValue::Immediate(ImmediateValue {
1032 value: serde_json::json!("static"),
1033 }),
1034 ];
1035
1036 let composite = MappingValue::Composite(CompositeValue {
1037 value: CompositeInner::Array(elements),
1038 });
1039 let json = serde_json::to_value(&composite).unwrap();
1040
1041 assert_eq!(json.get("valueType").unwrap(), "composite");
1042 let value = json.get("value").unwrap();
1043 assert!(value.is_array());
1044 assert_eq!(value.as_array().unwrap().len(), 2);
1045 }
1046
1047 #[test]
1048 fn test_composite_value_object_deserialization() {
1049 let json = r#"{
1050 "valueType": "composite",
1051 "value": {
1052 "userId": {"valueType": "reference", "value": "data.user.id"},
1053 "timestamp": {"valueType": "immediate", "value": 1234567890}
1054 }
1055 }"#;
1056
1057 let parsed: MappingValue = serde_json::from_str(json).unwrap();
1058 assert!(parsed.is_composite());
1059
1060 let inner = parsed.as_composite().unwrap();
1061 assert!(inner.is_object());
1062
1063 let fields = inner.as_object().unwrap();
1064 assert_eq!(fields.len(), 2);
1065 assert!(fields.get("userId").unwrap().is_reference());
1066 assert!(fields.get("timestamp").unwrap().is_immediate());
1067 }
1068
1069 #[test]
1070 fn test_composite_value_array_deserialization() {
1071 let json = r#"{
1072 "valueType": "composite",
1073 "value": [
1074 {"valueType": "reference", "value": "data.items[0]"},
1075 {"valueType": "immediate", "value": "fallback"}
1076 ]
1077 }"#;
1078
1079 let parsed: MappingValue = serde_json::from_str(json).unwrap();
1080 assert!(parsed.is_composite());
1081
1082 let inner = parsed.as_composite().unwrap();
1083 assert!(inner.is_array());
1084
1085 let elements = inner.as_array().unwrap();
1086 assert_eq!(elements.len(), 2);
1087 assert!(elements[0].is_reference());
1088 assert!(elements[1].is_immediate());
1089 }
1090
1091 #[test]
1092 fn test_nested_composite_value() {
1093 let json = r#"{
1094 "valueType": "composite",
1095 "value": {
1096 "outer": {
1097 "valueType": "composite",
1098 "value": {
1099 "inner": {"valueType": "immediate", "value": "nested"}
1100 }
1101 }
1102 }
1103 }"#;
1104
1105 let parsed: MappingValue = serde_json::from_str(json).unwrap();
1106 assert!(parsed.is_composite());
1107
1108 let outer = parsed.as_composite().unwrap().as_object().unwrap();
1109 let outer_val = outer.get("outer").unwrap();
1110 assert!(outer_val.is_composite());
1111
1112 let inner = outer_val.as_composite().unwrap().as_object().unwrap();
1113 assert!(inner.get("inner").unwrap().is_immediate());
1114 }
1115
1116 #[test]
1117 fn test_empty_composite_object() {
1118 let composite = MappingValue::Composite(CompositeValue {
1119 value: CompositeInner::Object(HashMap::new()),
1120 });
1121 let json = serde_json::to_value(&composite).unwrap();
1122 assert_eq!(json.get("valueType").unwrap(), "composite");
1123 assert!(json.get("value").unwrap().as_object().unwrap().is_empty());
1124 }
1125
1126 #[test]
1127 fn test_empty_composite_array() {
1128 let composite = MappingValue::Composite(CompositeValue {
1129 value: CompositeInner::Array(vec![]),
1130 });
1131 let json = serde_json::to_value(&composite).unwrap();
1132 assert_eq!(json.get("valueType").unwrap(), "composite");
1133 assert!(json.get("value").unwrap().as_array().unwrap().is_empty());
1134 }
1135
1136 #[test]
1137 fn test_mapping_value_collect_references() {
1138 let ref_val = MappingValue::Reference(ReferenceValue {
1140 value: "data.path".to_string(),
1141 type_hint: None,
1142 default: None,
1143 });
1144 assert_eq!(ref_val.collect_references(), vec!["data.path"]);
1145
1146 let imm_val = MappingValue::Immediate(ImmediateValue {
1148 value: serde_json::json!("test"),
1149 });
1150 assert!(imm_val.collect_references().is_empty());
1151
1152 let mut inner = HashMap::new();
1154 inner.insert(
1155 "nested".to_string(),
1156 MappingValue::Reference(ReferenceValue {
1157 value: "data.nested".to_string(),
1158 type_hint: None,
1159 default: None,
1160 }),
1161 );
1162
1163 let mut outer = HashMap::new();
1164 outer.insert(
1165 "top".to_string(),
1166 MappingValue::Reference(ReferenceValue {
1167 value: "data.top".to_string(),
1168 type_hint: None,
1169 default: None,
1170 }),
1171 );
1172 outer.insert(
1173 "inner".to_string(),
1174 MappingValue::Composite(CompositeValue {
1175 value: CompositeInner::Object(inner),
1176 }),
1177 );
1178
1179 let composite = MappingValue::Composite(CompositeValue {
1180 value: CompositeInner::Object(outer),
1181 });
1182 let refs = composite.collect_references();
1183
1184 assert_eq!(refs.len(), 2);
1185 assert!(refs.contains(&"data.top"));
1186 assert!(refs.contains(&"data.nested"));
1187 }
1188
1189 #[test]
1190 fn test_mapping_value_has_references() {
1191 let ref_val = MappingValue::Reference(ReferenceValue {
1192 value: "data.path".to_string(),
1193 type_hint: None,
1194 default: None,
1195 });
1196 assert!(ref_val.has_references());
1197
1198 let imm_val = MappingValue::Immediate(ImmediateValue {
1199 value: serde_json::json!("test"),
1200 });
1201 assert!(!imm_val.has_references());
1202
1203 let mut fields = HashMap::new();
1205 fields.insert(
1206 "a".to_string(),
1207 MappingValue::Immediate(ImmediateValue {
1208 value: serde_json::json!(1),
1209 }),
1210 );
1211 let comp_no_refs = MappingValue::Composite(CompositeValue {
1212 value: CompositeInner::Object(fields),
1213 });
1214 assert!(!comp_no_refs.has_references());
1215
1216 let mut fields_with_refs = HashMap::new();
1218 fields_with_refs.insert(
1219 "a".to_string(),
1220 MappingValue::Reference(ReferenceValue {
1221 value: "data.a".to_string(),
1222 type_hint: None,
1223 default: None,
1224 }),
1225 );
1226 let comp_with_refs = MappingValue::Composite(CompositeValue {
1227 value: CompositeInner::Object(fields_with_refs),
1228 });
1229 assert!(comp_with_refs.has_references());
1230 }
1231
1232 #[test]
1233 fn test_mapping_value_is_composite() {
1234 let ref_val = MappingValue::Reference(ReferenceValue {
1235 value: "data.field".to_string(),
1236 type_hint: None,
1237 default: None,
1238 });
1239 let imm_val = MappingValue::Immediate(ImmediateValue {
1240 value: serde_json::json!("static"),
1241 });
1242 let comp_val = MappingValue::Composite(CompositeValue {
1243 value: CompositeInner::Object(HashMap::new()),
1244 });
1245
1246 assert!(!ref_val.is_composite());
1247 assert!(!imm_val.is_composite());
1248 assert!(comp_val.is_composite());
1249 }
1250
1251 #[test]
1252 fn test_composite_value_round_trip() {
1253 let mut fields = HashMap::new();
1255 fields.insert(
1256 "key".to_string(),
1257 MappingValue::Reference(ReferenceValue {
1258 value: "data.key".to_string(),
1259 type_hint: None,
1260 default: None,
1261 }),
1262 );
1263 let original = MappingValue::Composite(CompositeValue {
1264 value: CompositeInner::Object(fields),
1265 });
1266 let json = serde_json::to_string(&original).unwrap();
1267 let parsed: MappingValue = serde_json::from_str(&json).unwrap();
1268 assert!(parsed.is_composite());
1269 assert!(parsed.as_composite().unwrap().is_object());
1270
1271 let elements = vec![MappingValue::Immediate(ImmediateValue {
1273 value: serde_json::json!("test"),
1274 })];
1275 let original_arr = MappingValue::Composite(CompositeValue {
1276 value: CompositeInner::Array(elements),
1277 });
1278 let json_arr = serde_json::to_string(&original_arr).unwrap();
1279 let parsed_arr: MappingValue = serde_json::from_str(&json_arr).unwrap();
1280 assert!(parsed_arr.is_composite());
1281 assert!(parsed_arr.as_composite().unwrap().is_array());
1282 }
1283
1284 #[test]
1289 fn test_log_level_serialization() {
1290 assert_eq!(
1291 serde_json::to_string(&LogLevel::Debug).unwrap(),
1292 "\"debug\""
1293 );
1294 assert_eq!(serde_json::to_string(&LogLevel::Info).unwrap(), "\"info\"");
1295 assert_eq!(serde_json::to_string(&LogLevel::Warn).unwrap(), "\"warn\"");
1296 assert_eq!(
1297 serde_json::to_string(&LogLevel::Error).unwrap(),
1298 "\"error\""
1299 );
1300 }
1301
1302 #[test]
1303 fn test_log_step_serialization() {
1304 let step = LogStep {
1305 id: "log1".to_string(),
1306 name: Some("Debug Log".to_string()),
1307 level: LogLevel::Debug,
1308 message: "Processing item".to_string(),
1309 context: None,
1310 };
1311
1312 let json = serde_json::to_value(&step).unwrap();
1313 assert_eq!(json.get("id").unwrap(), "log1");
1314 assert_eq!(json.get("level").unwrap(), "debug");
1315 assert_eq!(json.get("message").unwrap(), "Processing item");
1316 }
1317
1318 #[test]
1323 fn test_while_step_serialization() {
1324 let step = WhileStep {
1325 id: "while1".to_string(),
1326 name: Some("Retry Loop".to_string()),
1327 condition: ConditionExpression::Value(MappingValue::Reference(ReferenceValue {
1328 value: "data.retry".to_string(),
1329 type_hint: Some(ValueType::Boolean),
1330 default: None,
1331 })),
1332 subgraph: Box::new(ExecutionGraph {
1333 name: None,
1334 description: None,
1335 steps: HashMap::new(),
1336 entry_point: "start".to_string(),
1337 execution_plan: vec![],
1338 variables: HashMap::new(),
1339 input_schema: HashMap::new(),
1340 output_schema: HashMap::new(),
1341 notes: None,
1342 nodes: None,
1343 edges: None,
1344 }),
1345 config: Some(WhileConfig {
1346 max_iterations: Some(10),
1347 timeout: Some(5000),
1348 }),
1349 };
1350
1351 let json = serde_json::to_value(&step).unwrap();
1352 assert_eq!(json.get("id").unwrap(), "while1");
1353 let config = json.get("config").unwrap();
1354 assert_eq!(config.get("maxIterations").unwrap(), 10);
1355 assert_eq!(config.get("timeout").unwrap(), 5000);
1357 }
1358
1359 #[test]
1364 fn test_connection_step_serialization() {
1365 let step = ConnectionStep {
1366 id: "conn1".to_string(),
1367 name: Some("API Connection".to_string()),
1368 connection_id: "my-api-key".to_string(),
1369 integration_id: "http_bearer".to_string(),
1370 };
1371
1372 let json = serde_json::to_value(&step).unwrap();
1373 assert_eq!(json.get("id").unwrap(), "conn1");
1374 assert_eq!(json.get("connectionId").unwrap(), "my-api-key");
1375 assert_eq!(json.get("integrationId").unwrap(), "http_bearer");
1376 }
1377}