swarm_engine_eval/scenario/
actions.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ActionParamDef {
14 pub name: String,
16 pub description: String,
18 #[serde(default)]
20 pub required: bool,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum ScenarioActionCategory {
33 #[default]
35 NodeExpand,
36 NodeStateChange,
38}
39
40impl ScenarioActionCategory {
41 pub fn to_core(&self) -> swarm_engine_core::actions::ActionCategory {
43 match self {
44 Self::NodeExpand => swarm_engine_core::actions::ActionCategory::NodeExpand,
45 Self::NodeStateChange => swarm_engine_core::actions::ActionCategory::NodeStateChange,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ScenarioActionDef {
60 pub name: String,
62 pub description: String,
64 #[serde(default)]
66 pub category: ScenarioActionCategory,
67 #[serde(default)]
69 pub params: Vec<ActionParamDef>,
70 #[serde(default)]
72 pub example: Option<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, Default)]
84pub struct ScenarioActions {
85 #[serde(default)]
87 pub actions: Vec<ScenarioActionDef>,
88}
89
90impl ScenarioActions {
91 pub fn to_core_config(&self) -> swarm_engine_core::actions::ActionsConfig {
93 use swarm_engine_core::actions::{ActionDef, ActionsConfig, ParamDef};
94
95 let mut config = ActionsConfig::new();
96 for action_def in &self.actions {
97 let mut def = ActionDef::new(&action_def.name, &action_def.description)
98 .category(action_def.category.to_core())
99 .group("default");
100
101 for param in &action_def.params {
102 let param_def = if param.required {
103 ParamDef::required(¶m.name, ¶m.description)
104 } else {
105 ParamDef::optional(¶m.name, ¶m.description)
106 };
107 def = def.param(param_def);
108 }
109
110 if let Some(ref ex) = action_def.example {
111 def = def.example(ex);
112 }
113
114 config.add_action(&action_def.name, def);
115 }
116 config
117 }
118
119 pub fn action_names(&self) -> Vec<String> {
121 self.actions.iter().map(|a| a.name.clone()).collect()
122 }
123}
124
125#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_scenario_action_category_default() {
135 let category = ScenarioActionCategory::default();
136 assert_eq!(category, ScenarioActionCategory::NodeExpand);
137 }
138
139 #[test]
140 fn test_scenario_actions_default() {
141 let actions = ScenarioActions::default();
142 assert!(actions.actions.is_empty());
143 assert!(actions.action_names().is_empty());
144 }
145
146 #[test]
147 fn test_scenario_actions_deserialize_toml() {
148 let toml_str = r#"
149 [[actions]]
150 name = "grep"
151 description = "Search for patterns in files"
152 category = "node_expand"
153
154 [[actions.params]]
155 name = "pattern"
156 description = "Search pattern"
157 required = true
158
159 [[actions]]
160 name = "read"
161 description = "Read file contents"
162 category = "node_state_change"
163 "#;
164 let actions: ScenarioActions = toml::from_str(toml_str).unwrap();
165 assert_eq!(actions.actions.len(), 2);
166 assert_eq!(actions.action_names(), vec!["grep", "read"]);
167
168 let grep = &actions.actions[0];
169 assert_eq!(grep.name, "grep");
170 assert_eq!(grep.category, ScenarioActionCategory::NodeExpand);
171 assert_eq!(grep.params.len(), 1);
172 assert!(grep.params[0].required);
173 }
174
175 #[test]
176 fn test_scenario_actions_to_core_config() {
177 let actions = ScenarioActions {
178 actions: vec![ScenarioActionDef {
179 name: "test_action".to_string(),
180 description: "A test action".to_string(),
181 category: ScenarioActionCategory::NodeExpand,
182 params: vec![ActionParamDef {
183 name: "param1".to_string(),
184 description: "A parameter".to_string(),
185 required: true,
186 }],
187 example: Some(r#"{"action": "test_action"}"#.to_string()),
188 }],
189 };
190
191 let core_config = actions.to_core_config();
192 let action_def = core_config.get("test_action");
193 assert!(action_def.is_some());
194 }
195}