echo_library/common/
parse_module.rs

1use super::*;
2
3/// Validates the kind name of the task and returns the formatted kind if valid
4///
5/// # Arguments
6///
7/// * `kind` - A reference to the kind name of the task
8///
9/// # Returns
10///
11/// * An Ok Result containing the formatted kind if the input is valid
12/// * An Err Result with an ErrorKind::NotFound if the input is not valid
13///
14pub fn get_task_kind(kind: &str) -> Result<String, ErrorKind> {
15    match kind.to_lowercase().as_str() {
16        "openwhisk" => Ok("OpenWhisk".to_string()),
17        "polkadot" => Ok("Polkadot".to_string()),
18        "hello_world" => Ok("HelloWorldDerive".to_string()),
19        _ => Err(ErrorKind::NotFound),
20    }
21}
22
23fn get_main_method_code_template(tasks_length: usize) -> String {
24    format!(
25        "#[allow(dead_code, unused)]
26pub fn main(args: Value) -> Result<Value, String> {{
27    const LIMIT: usize = {tasks_length};
28    let mut workflow = WorkflowGraph::new(LIMIT);
29    let input: Input = serde_json::from_value(args).map_err(|e| e.to_string())?;
30"
31    )
32}
33
34/// Formats the attributes from the given HashMap into a specific string format
35/// This string will be passed to the macros as arguments
36///
37/// # Arguments
38///
39/// * `map` - A reference to the HashMap containing attribute key-value pairs
40///
41/// # Returns
42///
43/// * A String containing formatted attribute key-value pairs enclosed in square brackets
44///
45/// This formats the value of the attributes as enclosed by double quots
46pub fn get_attributes(attributes: &HashMap<String, String>) -> String {
47    let mut build_string = Vec::new();
48
49    for (key, value) in attributes {
50        build_string.push(format!("{}:\"{}\"", key.to_case(Case::Pascal), value));
51    }
52
53    format!("[{}]", build_string.join(","))
54}
55
56
57fn get_default_value_functions_code(workflow: &Workflow) -> String {
58    let mut default_value_functions = String::new();
59
60    for task in workflow.tasks.values() {
61        for input in task.input_arguments.iter() {
62            if !input.is_depend {
63                if let Some(val) = input.default_value.as_ref() {
64                    let content = match input.input_type {
65                        RustType::String => format!("{val:?}.to_string()"),
66                        _ => format!(
67                            "let val = serde_json::from_str::<{}>({:?}).unwrap();val",
68                            input.input_type, val
69                        ),
70                    };
71
72                    let make_fn = format!(
73                        "pub fn {}_fn() -> {}{{{}}}\n",
74                        input.name, input.input_type, content
75                    );
76
77                    default_value_functions.push_str(&make_fn);
78                }
79            };
80        }
81    }
82
83    default_value_functions
84}
85
86/// Creates a Rust code to generate a struct with fields representing inputs not
87/// depending on any task
88///
89/// # Arguments
90///
91/// * `workflow_index` - The index of the workflow
92///
93/// # Returns
94///
95/// * A String containing Rust code to create a struct representing inputs not depending
96///   on any task
97///
98fn get_task_common_input_type_constructor(
99    composer_custom_types: &HashMap<String, String>,
100    workflow: &Workflow,
101) -> Result<String, Error> {
102    let mut common = Vec::<String>::new();
103    let mut workflow_custom_types = Vec::<String>::new();
104
105    for task in workflow.tasks.values() {
106        for input in task.input_arguments.iter() {
107            if let RustType::Struct(name) = &input.input_type {
108                workflow_custom_types.push(name.to_string());
109            }
110
111            if !input.is_depend {
112                if input.default_value.as_ref().is_some() {
113                    common.push(format!(
114                        "#[\"{}_fn\"] {}:{}",
115                        input.name, input.name, input.input_type
116                    ));
117                } else {
118                    common.push(format!("{}:{}", input.name, input.input_type));
119                };
120            }
121        }
122    }
123
124    let workflow_custom_types = if !workflow_custom_types.is_empty() {
125        let mut build_string = String::new();
126
127        for custom_type in workflow_custom_types.iter() {
128            let typ = match composer_custom_types.get(custom_type) {
129                Some(t) => t,
130                None => return Err(Error::msg("Missing custom type in workflow")),
131            };
132            build_string = format!("{build_string}{typ}");
133        }
134
135        build_string
136    } else {
137        "".to_string()
138    };
139    Ok(format!(
140        "{workflow_custom_types}
141make_input_struct!(
142Input,
143[{}],
144[Debug, Clone, Default, Serialize, Deserialize]
145);",
146        common.join(",")
147    ))
148}
149
150fn get_task_type_constructors(workflow: &Workflow) -> String {
151    let mut constructors = String::new();
152
153    for task in workflow.tasks.values() {
154        let mut parameters = String::new();
155
156        for argument in task.input_arguments.iter() {
157            if !argument.is_depend {
158                parameters.push_str(&format!("input.{},", argument.name));
159            }
160        }
161
162        let constructor = format!(
163            "let {} = {}::new({}\"{}\".to_string());\n",
164            task.action_name.to_case(Case::Snake),
165            task.action_name.to_case(Case::Pascal),
166            parameters,
167            task.action_name.clone()
168        );
169
170        constructors.push_str(&constructor);
171    }
172
173    constructors
174}
175
176fn get_task_input_type_constructors(workflow: &Workflow) -> String {
177    let mut input_type_build_string = String::new();
178
179    for task in workflow.tasks.values() {
180        let mut arguments = Vec::new();
181
182        for field in task.input_arguments.iter() {
183            arguments.push(format!("{}:{}", field.name, field.input_type));
184        }
185
186        input_type_build_string.push_str(&format!(
187            "make_input_struct!(\n{}Input,\n[{}],\n[Debug, Clone, Default, Serialize, Deserialize]\n);",
188            task.action_name.to_case(Case::Pascal),
189            arguments.join(",")
190        ));
191    }
192
193    input_type_build_string
194}
195
196fn get_independent_fields(task: &Task) -> Vec<String> {
197    let mut independent_fields = Vec::<String>::new();
198
199    for field in task.input_arguments.iter() {
200        if !field.is_depend {
201            independent_fields.push(format!("{}:{}", field.name, field.input_type));
202        }
203    }
204
205    independent_fields
206}
207
208/// Generates Rust code to create structs for each task and its input, and creates object
209/// for these types inside the main function
210///
211/// # Arguments
212///
213/// * `workflow_index` - The index of the workflow
214///
215/// # Returns
216///
217/// * An array of Strings containing Rust code to create structs and objects for the
218///   specified workflow
219///
220fn get_task_main_type_constructors(workflow: &Workflow) -> Result<String, Error> {
221    let mut input_structs = String::new();
222
223    for (task_name, task) in workflow.tasks.iter() {
224        let task_name = task_name.to_case(Case::Pascal);
225
226        let independent_fields = get_independent_fields(task);
227
228        let output_field = if task.operation.is_map() {
229            "mapout"
230        } else {
231            "output"
232        };
233
234        input_structs = format!(
235            "{input_structs}
236make_main_struct!(
237    {task_name},
238    {task_name}Input,
239    [Debug, Clone, Default, Serialize, Deserialize, {}],
240    {},
241    {}
242);
243impl_new!(
244    {task_name},
245    {task_name}Input,
246    [{}]
247);
248",
249            get_task_kind(&task.kind).unwrap(),
250            get_attributes(&task.attributes),
251            output_field,
252            independent_fields.join(",")
253        );
254    }
255
256    Ok(input_structs)
257}
258
259fn get_impl_setters_code(workflow: &Workflow) -> Result<String, Error> {
260    let mut impl_setters_code = String::new();
261
262    for (task_name, task) in workflow.tasks.iter() {
263        let task_name = task_name.to_case(Case::Pascal);
264
265        let mut setter_fields = Vec::<String>::new();
266
267        let mut set = HashMap::<String, i32>::new();
268        let mut index: i32 = 0;
269
270        for dependent in task.depend_on.iter() {
271            let current_index = if let Some(current_index) = set.get(&dependent.task_name) {
272                index -= 1;
273                current_index
274            } else {
275                set.insert(dependent.task_name.to_string(), index);
276                &index
277            };
278
279            if task.operation.is_combine() {
280                let dependent_task = match workflow.tasks.get(&dependent.task_name) {
281                    Some(t) => t,
282                    None => return Err(Error::msg("Missing custom type in workflow")),
283                };
284
285                if dependent_task.operation.is_map() {
286                    setter_fields.push(format!(
287                        "(value)[{}]{}:\"{}\"",
288                        current_index, dependent.cur_field, dependent.prev_field
289                    ));
290                } else {
291                    setter_fields.push(format!(
292                        "[{}]{}:\"{}\"",
293                        current_index, dependent.cur_field, dependent.prev_field
294                    ));
295                }
296            } else {
297                setter_fields.push(format!(
298                    "{}:\"{}\"",
299                    dependent.cur_field, dependent.prev_field
300                ));
301            }
302
303            index += 1;
304        }
305
306        let setter_build_string = match &task.operation {
307            Operation::Map(field) => format!(
308                "impl_map_setter!({}, {}, {}, \"{}\");\n",
309                task_name,
310                setter_fields.join(","),
311                task.input_arguments[0].input_type,
312                field
313            ),
314            Operation::Concat => format!(
315                "impl_concat_setter!({}, {});\n",
316                task_name, task.input_arguments[0].name
317            ),
318            Operation::Combine => format!(
319                "impl_combine_setter!({},[{}]);\n",
320                task_name,
321                setter_fields.join(","),
322            ),
323            _ => format!(
324                "impl_setter!({}, [{}]);\n",
325                task_name,
326                setter_fields.join(",")
327            ),
328        };
329
330        impl_setters_code.push_str(&setter_build_string);
331    }
332
333    Ok(impl_setters_code)
334}
335
336/// Generates Rust code to call the `impl_execute_trait!` macro with the arguments as all
337/// of the task names
338///
339/// # Arguments
340///
341/// * `workflow_index` - The index of the workflow
342///
343/// # Returns
344///
345/// * A String containing the Rust code to call the `impl_execute_trait!` macro
346///
347fn get_impl_execute_trait_code(workflow: &Workflow) -> String {
348    let mut task_names = Vec::new();
349
350    for task_name in workflow.tasks.keys() {
351        task_names.push(task_name.to_case(Case::Pascal));
352    }
353
354    format!("impl_execute_trait!({});", task_names.join(","))
355}
356
357fn get_add_nodes_code(flow: &Vec<String>) -> String {
358    let mut add_nodes_code = String::new();
359
360    for i in flow {
361        add_nodes_code.push_str(&format!(
362            "let {}_index = workflow.add_node(Box::new({}));\n",
363            i.to_case(Case::Snake),
364            i.to_case(Case::Snake)
365        ));
366    }
367
368    add_nodes_code
369}
370
371fn get_add_edges_code(workflow: &Workflow, flow: &Vec<String>) -> Result<String, Error> {
372    let mut add_edges_code = "workflow.add_edges(&[\n".to_string();
373
374    for index in 0..flow.len() - 1 {
375        if let Some(dependent_task) = workflow.tasks.get(&flow[index + 1]) {
376            let mut set = HashSet::<String>::new();
377
378            for dependent_task in dependent_task.depend_on.iter() {
379                if !set.contains(&dependent_task.task_name) {
380                    add_edges_code = format!(
381                        "{add_edges_code}({}_index, {}_index),\n",
382                        dependent_task.task_name.to_case(Case::Snake),
383                        flow[index + 1].to_case(Case::Snake)
384                    );
385                    set.insert(dependent_task.task_name.clone());
386                }
387            }
388        } else {
389            return Err(Error::msg(" Error adding the edges "));
390        }
391    }
392
393    add_edges_code += "]);";
394    Ok(add_edges_code)
395}
396
397fn get_add_execute_workflow_code(workflow: &Workflow, flow: &Vec<String>) -> Result<String, Error> {
398    let mut execute_code = "let result = workflow\n.init()?".to_string();
399
400    for task_index in 0..flow.len() - 1 {
401        execute_code = if task_index + 1 == flow.len() - 1 {
402            match workflow
403                .tasks
404                .get(&flow[task_index + 1])
405                .unwrap()
406                .depend_on
407                .len()
408            {
409                0 | 1 => {
410                    format!(
411                        "{execute_code}\n.term(Some({}_index))?;",
412                        flow[task_index + 1].to_case(Case::Snake)
413                    )
414                }
415
416                _ => {
417                    format!(
418                        "{execute_code}\n.pipe({}_index)?\n.term(None)?;",
419                        flow[task_index + 1].to_case(Case::Snake)
420                    )
421                }
422            }
423        } else {
424            format!(
425                "{execute_code}\n.pipe({}_index)?",
426                flow[task_index + 1].to_case(Case::Snake)
427            )
428        }
429    }
430
431    Ok(execute_code)
432}
433
434/// Generates Rust code to add workflow nodes and edges
435///
436/// # Arguments
437///
438/// * `workflow_index` - The index of the workflow
439///
440/// # Returns
441///
442/// * An array containing the Rust code to add workflow nodes and edges
443///
444fn get_workflow_nodes_and_edges_code(workflow: &Workflow) -> Result<String, Error> {
445    let flow: Vec<String> = workflow.get_flow();
446
447    if flow.is_empty() {
448        return Ok("".to_string());
449    }
450
451    if flow.len() == 1 {
452        return Ok(format!(
453            "\
454let {}_index = workflow.add_node(Box::new({}));
455\tlet result = workflow\n\t\t.init()?
456\t\t.term(None)?;
457Ok(result)
458",
459            flow[0].to_case(Case::Snake),
460            flow[0].to_case(Case::Snake)
461        ));
462    }
463
464    Ok(format!(
465        "{}\n{}\n{}let result = serde_json::to_value(result).unwrap();\nOk(result)",
466        get_add_nodes_code(&flow),
467        get_add_edges_code(workflow, &flow)?,
468        get_add_execute_workflow_code(workflow, &flow)?,
469    ))
470}
471
472/// Generates the main Rust code for the workflow package and creates the `types.rs` file
473///
474/// # Arguments
475///
476/// * `workflow_index` - The index of the workflow
477///
478/// # Returns
479///
480/// * A String containing the Rust code to be written to `types.rs` file in the workflow package
481///
482pub fn generate_types_rs_file_code(
483    workflow: &Workflow,
484    custom_types: &HashMap<String, String>,
485) -> Result<String, Error> {
486    let main_file = format!(
487        "use super::*;\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}}}",
488        add_polkadot_openwhisk(workflow),
489        get_task_input_type_constructors(workflow),
490        get_task_main_type_constructors(workflow)?,
491        get_impl_setters_code(workflow)?,
492        get_default_value_functions_code(workflow),
493        get_task_common_input_type_constructor(custom_types, workflow)?,
494        get_impl_execute_trait_code(workflow),
495        get_main_method_code_template(workflow.tasks.len()),
496        get_task_type_constructors(workflow),
497        get_workflow_nodes_and_edges_code(workflow)?
498    );
499    Ok(main_file)
500}
501
502fn get_openwhisk_kind_dependencies() -> String {
503    "
504openwhisk_macro = \"0.1.6\"
505
506"
507    .to_string()
508}
509
510fn get_polkadot_kind_dependencies() -> String {
511    // some of the polkadot dependencies
512    "substrate_macro = \"0.1.3\"
513    pallet-staking = { git = \"https://github.com/paritytech/substrate.git\", package = \"pallet-staking\", rev = \"eb1a2a8\" }
514    substrate-api-client = { git = \"https://github.com/HugoByte/substrate-api-client.git\", default-features = false, features = [\"staking-xt\"], branch =\"wasm-support\"}
515sp-core = { version = \"6.0.0\", default-features = false, features = [\"full_crypto\"], git = \"https://github.com/paritytech/substrate.git\", rev = \"eb1a2a8\" }
516sp-runtime = { version = \"6.0.0\", default-features = false, git = \"https://github.com/paritytech/substrate.git\", rev = \"eb1a2a8\" }
517     "
518        .to_string()
519}
520
521pub fn generate_cargo_toml_dependencies(workflow: &Workflow) -> String {
522    let mut dependency_map = HashMap::new();
523
524    let hello_world_dependency = "hello_world_macro = {git= \"https://github.com/HugoByte/aurras.git\", branch = \"next\", package = \"hello_world_macro\"}"
525    .to_string();
526
527    dependency_map.insert("hello_world", hello_world_dependency);
528    dependency_map.insert("openwhisk", get_openwhisk_kind_dependencies());
529    dependency_map.insert("polkadot", get_polkadot_kind_dependencies());
530
531    let kinds = get_common_kind(workflow);
532    if kinds.is_empty() {
533        return String::new();
534    }
535
536    let mut toml_dependencies = String::new();
537    for (kind, dependency_string) in dependency_map.iter() {
538        if kinds.contains(&kind.to_string()) {
539            toml_dependencies.push_str(dependency_string);
540        }
541    }
542
543    toml_dependencies
544}
545
546pub fn handle_multiple_dependency() -> String {
547    let openwhisk_dependency = get_openwhisk_kind_dependencies();
548    let polkadot_dependency = get_polkadot_kind_dependencies();
549
550    let combined_dependencies = format!("{}{}", openwhisk_dependency, polkadot_dependency);
551    combined_dependencies
552}
553
554pub fn get_polkadot() -> String {
555    "\
556    use substrate_macro::Polkadot;
557    use sp_core::H256;
558
559    "
560    .to_string()
561}
562
563pub fn get_openwhisk() -> String {
564    "\
565    use openwhisk_macro::*;
566    use openwhisk_rust::*;
567    
568    "
569    .to_string()
570}
571
572pub fn add_polkadot_openwhisk(workflow: &Workflow) -> String {
573    let kinds = get_common_kind(workflow);
574
575    let mut toml_dependencies = String::new();
576
577    if kinds.contains("openwhisk") {
578        toml_dependencies = get_openwhisk();
579    }
580
581    if kinds.contains("polkadot") {
582        toml_dependencies = get_polkadot();
583    }
584
585    if kinds.contains("openwhisk") && kinds.contains("polkadot") {
586        toml_dependencies = handle_multiple_kinds();
587    }
588
589    if kinds.contains("hello_world") {
590        toml_dependencies += "\nuse hello_world_macro::HelloWorldDerive;";
591    }
592
593    toml_dependencies
594}
595
596pub fn staking_ledger() -> String {
597    "\
598use sp_runtime::AccountId32;
599
600#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
601 pub struct StakingLedger {
602 pub stash: AccountId32,
603 #[codec(compact)]
604 pub total: u128,
605 #[codec(compact)]
606 pub active: u128,
607 pub unlocking: Vec<u32>,
608 pub claimed_rewards: Vec<u32>,
609}
610    "
611    .to_string()
612}
613
614pub fn get_struct_stake_ledger(workflow: &Workflow) -> String {
615    let kinds = get_common_kind(workflow);
616
617    let mut toml_dependencies = String::new();
618
619    if kinds.contains("polkadot") {
620        toml_dependencies = staking_ledger();
621    }
622
623    toml_dependencies
624}
625
626pub fn get_common_kind(workflow: &Workflow) -> HashSet<String> {
627    let mut kinds = HashSet::new();
628    for task in workflow.tasks.values() {
629        kinds.insert(task.kind.to_lowercase());
630    }
631    kinds
632}
633
634pub fn handle_multiple_kinds() -> String {
635    let openwhisk = get_openwhisk();
636    let polkadot = get_polkadot();
637
638    let combined_dependencies = format!("{}{}", openwhisk, polkadot);
639    combined_dependencies
640}
641
642#[cfg(test)]
643mod tests {
644    use super::*;
645
646    #[test]
647    fn test_get_main_method_code_template() {
648        let output = get_main_method_code_template(4);
649
650        assert_eq!(
651            &output,
652            "#[allow(dead_code, unused)]
653pub fn main(args: Value) -> Result<Value, String> {
654    const LIMIT: usize = 4;
655    let mut workflow = WorkflowGraph::new(LIMIT);
656    let input: Input = serde_json::from_value(args).map_err(|e| e.to_string())?;
657"
658        );
659    }
660
661    #[test]
662    fn test_get_attributes() {
663        let mut attributes = HashMap::new();
664        attributes.insert("key".to_string(), "value".to_string());
665
666        let output = get_attributes(&attributes);
667        assert_eq!(output, "[Key:\"value\"]");
668      
669    }
670
671    #[test]
672    fn test_get_default_value_functions_code() {
673        let task1 = Task {
674            action_name: "task0".to_string(),
675            input_arguments: vec![
676                Input {
677                    name: "argument_1".to_string(),
678                    input_type: RustType::String,
679                    default_value: Some("value_x".to_string()),
680                    ..Default::default()
681                },
682                Input {
683                    name: "argument_2".to_string(),
684                    input_type: RustType::List(Box::new(RustType::String)),
685                    default_value: Some("[\"val1,\"val2\"]".to_string()),
686                    ..Default::default()
687                },
688            ],
689            ..Default::default()
690        };
691
692        let mut tasks = HashMap::new();
693        tasks.insert("task1".to_string(), task1);
694
695        let workflow = Workflow {
696            name: "test-workflow".to_string(),
697            version: "0.0.1".to_string(),
698            tasks,
699        };
700
701        let output = get_default_value_functions_code(&workflow);
702
703        assert_eq!(
704        output,
705        "\
706pub fn argument_1_fn() -> String{\"value_x\".to_string()}
707pub fn argument_2_fn() -> Vec<String>{let val = serde_json::from_str::<Vec<String>>(\"[\\\"val1,\\\"val2\\\"]\").unwrap();val}
708"
709    )
710    }
711
712    #[test]
713    fn test_get_task_common_input_type_constructor() {
714        let task0 = Task {
715            action_name: "task0".to_string(),
716            input_arguments: vec![
717                Input {
718                    name: "argument_1".to_string(),
719                    input_type: RustType::Boolean,
720                    is_depend: true,
721                    ..Default::default()
722                },
723                Input {
724                    name: "argument_2".to_string(),
725                    input_type: RustType::Int,
726                    ..Default::default()
727                },
728                Input {
729                    name: "argument_3".to_string(),
730                    input_type: RustType::List(Box::new(RustType::Uint)),
731                    ..Default::default()
732                },
733                Input {
734                    name: "argument_4".to_string(),
735                    input_type: RustType::Float,
736                    is_depend: true,
737                    ..Default::default()
738                },
739                Input {
740                    name: "argument_5".to_string(),
741                    input_type: RustType::String,
742                    ..Default::default()
743                },
744                Input {
745                    name: "argument_6".to_string(),
746                    input_type: RustType::HashMap(
747                        Box::new(RustType::Int),
748                        Box::new(RustType::Float),
749                    ),
750                    ..Default::default()
751                },
752                Input {
753                    name: "argument_7".to_string(),
754                    input_type: RustType::Tuple(Box::new(RustType::Int), Box::new(RustType::Float)),
755                    ..Default::default()
756                },
757                Input {
758                    name: "argument_8".to_string(),
759                    input_type: RustType::Struct("Struct1".to_string()),
760                    ..Default::default()
761                },
762            ],
763            ..Default::default()
764        };
765
766        let mut tasks = HashMap::new();
767        tasks.insert("task0".to_string(), task0);
768
769        let workflow = Workflow {
770            name: "test-workflow".to_string(),
771            version: "0.0.1".to_string(),
772            tasks,
773        };
774
775        let mut custom_types = HashMap::new();
776
777        custom_types.insert(
778        "Struct1".to_string(),
779        "make_input_struct!(\nStruct1,\n{field1:i32},\n[Default, Clone, Debug, Deserialize, Serialize]\n);".to_string());
780
781        let output = get_task_common_input_type_constructor(&custom_types, &workflow);
782        assert_eq!(
783        &output.unwrap(),
784        "\
785make_input_struct!(
786Struct1,
787{field1:i32},
788[Default, Clone, Debug, Deserialize, Serialize]
789);
790make_input_struct!(
791Input,
792[argument_2:i32,argument_3:Vec<u32>,argument_5:String,argument_6:HashMap<i32,f32>,argument_7:(i32,f32),argument_8:Struct1],
793[Debug, Clone, Default, Serialize, Deserialize]
794);")
795    }
796
797    #[test]
798    fn test_get_task_type_constructors() {
799        let task0 = Task {
800            action_name: "task0".to_string(),
801            input_arguments: vec![
802                Input {
803                    name: "argument_1".to_string(),
804                    input_type: RustType::Boolean,
805                    ..Default::default()
806                },
807                Input {
808                    name: "argument_2".to_string(),
809                    input_type: RustType::Int,
810                    ..Default::default()
811                },
812            ],
813            ..Default::default()
814        };
815
816        let mut tasks = HashMap::new();
817        tasks.insert("task0".to_string(), task0);
818
819        let workflow = Workflow {
820            name: "test-workflow".to_string(),
821            version: "0.0.1".to_string(),
822            tasks,
823        };
824
825        let output = get_task_type_constructors(&workflow);
826
827        assert_eq!(
828            output,
829            "let task_0 = Task0::new(input.argument_1,input.argument_2,\"task0\".to_string());\n"
830        );
831    }
832
833    #[test]
834    fn test_get_task_input_type_constructors() {
835        let task0 = Task {
836            action_name: "task0".to_string(),
837            input_arguments: vec![
838                Input {
839                    name: "argument_1".to_string(),
840                    input_type: RustType::Boolean,
841                    ..Default::default()
842                },
843                Input {
844                    name: "argument_2".to_string(),
845                    input_type: RustType::Int,
846                    ..Default::default()
847                },
848            ],
849            ..Default::default()
850        };
851
852        let mut tasks = HashMap::new();
853        tasks.insert("task0".to_string(), task0);
854
855        let workflow = Workflow {
856            name: "test-workflow".to_string(),
857            version: "0.0.1".to_string(),
858            tasks,
859        };
860
861        let output = get_task_input_type_constructors(&workflow);
862
863        println!("{:?}", output);
864
865        assert_eq!(
866            output,
867            "make_input_struct!(
868Task0Input,
869[argument_1:bool,argument_2:i32],
870[Debug, Clone, Default, Serialize, Deserialize]
871);"
872        );
873    }
874
875    #[test]
876    fn test_get_independent_fields() {
877        let task0 = Task {
878            action_name: "task0".to_string(),
879            input_arguments: vec![
880                Input {
881                    name: "argument_1".to_string(),
882                    input_type: RustType::Boolean,
883                    is_depend: true,
884                    ..Default::default()
885                },
886                Input {
887                    name: "argument_2".to_string(),
888                    input_type: RustType::Int,
889                    ..Default::default()
890                },
891            ],
892            ..Default::default()
893        };
894
895        let output = get_independent_fields(&task0);
896
897        assert_eq!(output, vec!["argument_2:i32"]);
898    }
899
900    #[test]
901    fn test_get_task_main_type_constructors() {
902        let task0 = Task {
903            action_name: "task0".to_string(),
904            kind: "Openwhisk".to_string(),
905            input_arguments: vec![
906                Input {
907                    name: "argument_1".to_string(),
908                    input_type: RustType::Boolean,
909                    ..Default::default()
910                },
911                Input {
912                    name: "argument_2".to_string(),
913                    input_type: RustType::Int,
914                    ..Default::default()
915                },
916            ],
917            ..Default::default()
918        };
919
920        let mut tasks = HashMap::new();
921        tasks.insert("task0".to_string(), task0);
922
923        let workflow = Workflow {
924            name: "test-workflow".to_string(),
925            version: "0.0.1".to_string(),
926            tasks,
927        };
928
929        let output = get_task_main_type_constructors(&workflow);
930
931        assert_eq!(
932            output.unwrap(),
933            "
934make_main_struct!(
935    Task0,
936    Task0Input,
937    [Debug, Clone, Default, Serialize, Deserialize, OpenWhisk],
938    [],
939    output
940);
941impl_new!(
942    Task0,
943    Task0Input,
944    [argument_1:bool,argument_2:i32]
945);
946"
947        );
948    }
949
950    #[test]
951    fn test_get_impl_setters_code() {
952        let task0 = Task {
953            action_name: "task0".to_string(),
954            kind: "Openwhisk".to_string(),
955            input_arguments: vec![
956                Input {
957                    name: "argument_1".to_string(),
958                    input_type: RustType::Boolean,
959                    is_depend: true,
960                    ..Default::default()
961                },
962                Input {
963                    name: "argument_2".to_string(),
964                    input_type: RustType::Int,
965                    ..Default::default()
966                },
967            ],
968            depend_on: vec![Depend {
969                task_name: "task1".to_string(),
970                cur_field: "argument_1".to_string(),
971                prev_field: "data_field".to_string(),
972            }],
973            ..Default::default()
974        };
975
976        let mut tasks = HashMap::new();
977        tasks.insert("task0".to_string(), task0);
978
979        let workflow = Workflow {
980            name: "test-workflow".to_string(),
981            version: "0.0.1".to_string(),
982            tasks,
983        };
984
985        let output = get_impl_setters_code(&workflow);
986
987        assert_eq!(
988            output.unwrap(),
989            "impl_setter!(Task0, [argument_1:\"data_field\"]);\n"
990        );
991    }
992
993    #[test]
994    fn test_get_impl_execute_trait_code() {
995        let task0 = Task {
996            action_name: "task0".to_string(),
997            kind: "Openwhisk".to_string(),
998            ..Default::default()
999        };
1000
1001        let task1 = Task {
1002            action_name: "task1".to_string(),
1003            kind: "Openwhisk".to_string(),
1004            ..Default::default()
1005        };
1006
1007        let mut tasks = HashMap::new();
1008        tasks.insert("task0".to_string(), task0);
1009        tasks.insert("task1".to_string(), task1);
1010
1011        let workflow = Workflow {
1012            name: "test-workflow".to_string(),
1013            version: "0.0.1".to_string(),
1014            tasks,
1015        };
1016
1017        let output = get_impl_execute_trait_code(&workflow);
1018        assert!(
1019            output == "impl_execute_trait!(Task0,Task1);"
1020                || output == "impl_execute_trait!(Task1,Task0);"
1021        );
1022    }
1023
1024    #[test]
1025    fn test_get_add_nodes_code() {
1026        let flow = vec![
1027            "task0".to_string(),
1028            "task2".to_string(),
1029            "task1".to_string(),
1030            "task4".to_string(),
1031            "task3".to_string(),
1032        ];
1033
1034        let output = get_add_nodes_code(&flow);
1035
1036        assert_eq!(
1037            output,
1038            "\
1039let task_0_index = workflow.add_node(Box::new(task_0));
1040let task_2_index = workflow.add_node(Box::new(task_2));
1041let task_1_index = workflow.add_node(Box::new(task_1));
1042let task_4_index = workflow.add_node(Box::new(task_4));
1043let task_3_index = workflow.add_node(Box::new(task_3));
1044"
1045        )
1046    }
1047
1048    #[test]
1049    fn test_get_add_edges_code() {
1050        let task0 = Task {
1051            action_name: "task0".to_string(),
1052            ..Default::default()
1053        };
1054        let task1 = Task {
1055            action_name: "task1".to_string(),
1056            depend_on: vec![Depend {
1057                task_name: "task0".to_string(),
1058                ..Default::default()
1059            }],
1060            ..Default::default()
1061        };
1062
1063        let task2 = Task {
1064            action_name: "task2".to_string(),
1065            depend_on: vec![
1066                Depend {
1067                    task_name: "task1".to_string(),
1068                    ..Default::default()
1069                },
1070                Depend {
1071                    task_name: "task0".to_string(),
1072                    ..Default::default()
1073                },
1074            ],
1075            ..Default::default()
1076        };
1077
1078        let task3 = Task {
1079            action_name: "task3".to_string(),
1080            depend_on: vec![Depend {
1081                task_name: "task2".to_string(),
1082                ..Default::default()
1083            }],
1084            ..Default::default()
1085        };
1086
1087        let task4 = Task {
1088            action_name: "task4".to_string(),
1089            depend_on: vec![
1090                Depend {
1091                    task_name: "task3".to_string(),
1092                    ..Default::default()
1093                },
1094                Depend {
1095                    task_name: "task2".to_string(),
1096                    ..Default::default()
1097                },
1098            ],
1099            ..Default::default()
1100        };
1101
1102        let mut tasks = HashMap::new();
1103        tasks.insert("task0".to_string(), task0);
1104        tasks.insert("task1".to_string(), task1);
1105        tasks.insert("task2".to_string(), task2);
1106        tasks.insert("task3".to_string(), task3);
1107        tasks.insert("task4".to_string(), task4);
1108
1109        let workflow = Workflow {
1110            name: "test-workflow".to_string(),
1111            version: "0.0.1".to_string(),
1112            tasks,
1113        };
1114
1115        let flow = workflow.get_flow();
1116
1117        let output = get_add_edges_code(&workflow, &flow);
1118
1119        assert_eq!(
1120            output.unwrap(),
1121            "\
1122workflow.add_edges(&[
1123(task_0_index, task_1_index),
1124(task_1_index, task_2_index),
1125(task_0_index, task_2_index),
1126(task_2_index, task_3_index),
1127(task_3_index, task_4_index),
1128(task_2_index, task_4_index),
1129]);"
1130        );
1131    }
1132
1133    #[test]
1134    fn test_get_add_execute_workflow_code() {
1135        let task0 = Task {
1136            action_name: "task0".to_string(),
1137            ..Default::default()
1138        };
1139        let task1 = Task {
1140            action_name: "task1".to_string(),
1141            depend_on: vec![Depend {
1142                task_name: "task0".to_string(),
1143                ..Default::default()
1144            }],
1145            ..Default::default()
1146        };
1147
1148        let task2 = Task {
1149            action_name: "task2".to_string(),
1150            depend_on: vec![
1151                Depend {
1152                    task_name: "task1".to_string(),
1153                    ..Default::default()
1154                },
1155                Depend {
1156                    task_name: "task0".to_string(),
1157                    ..Default::default()
1158                },
1159            ],
1160            ..Default::default()
1161        };
1162
1163        let task3 = Task {
1164            action_name: "task3".to_string(),
1165            depend_on: vec![Depend {
1166                task_name: "task2".to_string(),
1167                ..Default::default()
1168            }],
1169            ..Default::default()
1170        };
1171
1172        let task4 = Task {
1173            action_name: "task4".to_string(),
1174            depend_on: vec![
1175                Depend {
1176                    task_name: "task3".to_string(),
1177                    ..Default::default()
1178                },
1179                Depend {
1180                    task_name: "task2".to_string(),
1181                    ..Default::default()
1182                },
1183            ],
1184            ..Default::default()
1185        };
1186
1187        let mut tasks = HashMap::new();
1188        tasks.insert("task0".to_string(), task0);
1189        tasks.insert("task1".to_string(), task1);
1190        tasks.insert("task2".to_string(), task2);
1191        tasks.insert("task3".to_string(), task3);
1192        tasks.insert("task4".to_string(), task4);
1193
1194        let workflow = Workflow {
1195            name: "test-workflow".to_string(),
1196            version: "0.0.1".to_string(),
1197            tasks,
1198        };
1199
1200        let flow = workflow.get_flow();
1201
1202        let output = get_add_execute_workflow_code(&workflow, &flow);
1203
1204        assert_eq!(
1205            output.unwrap(),
1206            "\
1207let result = workflow
1208.init()?
1209.pipe(task_1_index)?
1210.pipe(task_2_index)?
1211.pipe(task_3_index)?
1212.pipe(task_4_index)?
1213.term(None)?;"
1214        );
1215    }
1216}