icepick workflow: detach simulator from normal flow

This commit is contained in:
Ryan Heywood 2024-12-19 21:36:04 -05:00
parent 57ab484995
commit f09fb4dd59
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
2 changed files with 53 additions and 42 deletions

View File

@ -107,8 +107,9 @@ pub fn do_cli_thing() {
workflows.push((module.name.clone(), module.workflows.clone())); workflows.push((module.name.clone(), module.workflows.clone()));
} }
let workflows = workflows.leak(); let workflows = workflows.leak();
let mut workflow_command = let mut workflow_command = clap::Command::new("workflow")
clap::Command::new("workflow").about("Run a pre-defined Icepick workflow"); .about("Run a pre-defined Icepick workflow")
.arg(clap::arg!(--"simulate-workflow").global(true));
for module in workflows.iter() { for module in workflows.iter() {
let mut module_subcommand = clap::Command::new(module.0.as_str()); let mut module_subcommand = clap::Command::new(module.0.as_str());
for workflow in &module.1 { for workflow in &module.1 {
@ -124,12 +125,16 @@ pub fn do_cli_thing() {
for command in commands.iter() { for command in commands.iter() {
let mut subcommand = clap::Command::new(command.0.as_str()); let mut subcommand = clap::Command::new(command.0.as_str());
for op in &command.2 { for op in &command.2 {
let mut op_command = clap::Command::new(op.name.replace('_', "-")).about(&op.description); let mut op_command =
clap::Command::new(op.name.replace('_', "-")).about(&op.description);
for arg in &op.arguments { for arg in &op.arguments {
let mut op_arg = clap::Arg::new(arg.name.replace('_', "-")).help(arg.description.as_str()); let mut op_arg =
clap::Arg::new(arg.name.replace('_', "-")).help(arg.description.as_str());
op_arg = match arg.r#type { op_arg = match arg.r#type {
ArgumentType::Required => op_arg.required(true), ArgumentType::Required => op_arg.required(true),
ArgumentType::Optional => op_arg.required(false).long(arg.name.replace('_', "-")), ArgumentType::Optional => {
op_arg.required(false).long(arg.name.replace('_', "-"))
}
}; };
op_command = op_command.arg(op_arg); op_command = op_command.arg(op_arg);
} }

View File

@ -82,6 +82,44 @@ impl Workflow {
map map
} }
pub fn simulate_workflow(&self, mut data: HashSet<String>, operations: &[InvocableOperation]) {
// simulate the steps by using a HashSet to traverse the inputs and outputs and ensure
// there's no inconsistencies
for (i, step) in self.steps.iter().enumerate() {
// NOTE: overflow possible but unlikely
let step_index = i + 1;
// Find the relevant Operation
let Some(invocable) = operations.iter().find(|op| op.name == step.r#type) else {
panic!("Could not find operation: {}", step.r#type);
};
// Check if we have the keys we want to pass into the module.
for in_memory_name in step.inputs.values() {
if !data.contains(in_memory_name) && !step.values.contains_key(in_memory_name) {
panic!("Failed simulation: step #{step_index}: missing value {in_memory_name}");
}
}
// Check that the module accepts those keys.
for module_input_name in step.inputs.keys() {
if !invocable
.operation
.arguments
.iter()
.any(|arg| *module_input_name == arg.name)
{
eprintln!("Simulation: step #{step_index}: input value {module_input_name} will be passed through as JSON input");
}
}
// Add the keys we get from the module.
for in_memory_name in step.outputs.values() {
data.insert(in_memory_name.clone());
}
}
}
pub fn handle(&self, matches: &clap::ArgMatches, modules: Commands) { pub fn handle(&self, matches: &clap::ArgMatches, modules: Commands) {
let inputs = self.load_inputs(matches); let inputs = self.load_inputs(matches);
let data: HashMap<String, Value> = inputs let data: HashMap<String, Value> = inputs
@ -103,43 +141,11 @@ impl Workflow {
} }
} }
// simulate the steps by using a HashSet to traverse the inputs and outputs and ensure if matches.get_flag("simulate-workflow") {
// there's no inconsistencies self.simulate_workflow(data.into_keys().collect(), &operations);
let mut simulated_values = data.keys().collect::<HashSet<_>>(); return;
for (i, step) in self.steps.iter().enumerate() {
// NOTE: overflow possible but unlikely
let step_index = i + 1;
// Find the relevant Operation
let Some(invocable) = operations.iter().find(|op| op.name == step.r#type) else {
panic!("Could not find operation: {}", step.r#type);
};
// Check if we have the keys we want to pass into the module.
for in_memory_name in step.inputs.values() {
if !simulated_values.contains(in_memory_name)
&& !step.values.contains_key(in_memory_name)
{
panic!("Failed simulation: step #{step_index}: missing value {in_memory_name}");
}
} }
// Check that the module accepts those keys. todo!("Unsimulated transaction!");
for module_input_name in step.inputs.keys() {
if !invocable
.operation
.arguments
.iter()
.any(|arg| *module_input_name == arg.name)
{
eprintln!("Simulation: step #{step_index}: input value {module_input_name} will be passed through as JSON input");
}
}
// Add the keys we get from the module.
for in_memory_name in step.outputs.values() {
simulated_values.insert(in_memory_name);
}
}
} }
} }