echo_library/common/
starlark_modules.rs

1use super::*;
2use anyhow::anyhow;
3#[allow(clippy::type_complexity)]
4#[starlark_module]
5pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) {
6    /// Creates a new task of the workflow and returns a task object of `Task` type
7    /// This method will be invoked inside the config file.
8    ///
9    /// # Arguments
10    ///
11    /// * `kind` - A string that holds the kind of the task (i.e "polkadot", "openwhisk")
12    /// * `action_name` - A string that holds the the name of the action associated with the task
13    /// * `input_args` - The input arguments for the task
14    /// * `attributes` - The attributes of the task
15    /// * `operation` - An optional argument to mention type of the task operation
16    /// * `depend_on` - The dependencies of the task
17    ///   (i.e "map", "concat")
18    ///
19    /// # Returns
20    ///
21    /// * A Result containing the task object if the task is created successfully
22    ///
23    fn task(
24        kind: String,
25        action_name: String,
26        input_arguments: Value,
27        attributes: Option<Value>,
28        operation: Option<Value>,
29        depend_on: Option<Value>,
30    ) -> anyhow::Result<Task> {
31        if kind == "openwhisk" || kind == "polkadot" {
32            if attributes.is_none() {
33                return Err(anyhow!("Attributes are mandatory for kind: openwhisk or polkadot"));
34            }
35        }
36        
37        let mut input_arguments: Vec<Input> = serde_json::from_str(&input_arguments.to_json()?)
38            .map_err(|err| anyhow!("Failed to parse input arguments: {}", err))?;
39
40        let attributes: HashMap<String, String> = match attributes {
41            Some(attributes) => serde_json::from_str(&attributes.to_json()?)
42                .map_err(|err| anyhow!("Failed to parse the attributes: {}", err))?,
43            _ => HashMap::default(),
44        };
45
46        let depend_on: Vec<Depend> = match depend_on {
47            Some(val) => serde_json::from_str(&val.to_json()?)
48                .map_err(|err| anyhow!("Failed to parse depend-on: {}", err))?,
49            None => Vec::default(),
50        };
51
52        for depend in depend_on.iter() {
53            for argument in input_arguments.iter_mut() {
54                if argument.name == depend.cur_field {
55                    argument.is_depend = true;
56                    break;
57                }
58            }
59        }
60
61        let operation: Operation = match operation {
62            Some(op) => serde_json::from_str(&op.to_json()?)
63                .map_err(|err| anyhow!("Failed to parse the task operation value: {}", err))?,
64            _ => Operation::Normal,
65        };
66
67        Ok(Task {
68            kind,
69            action_name,
70            input_arguments,
71            attributes,
72            operation,
73            depend_on,
74        })
75    }
76
77    /// Creates and adds a new workflow to the composer
78    /// This method will be invoked inside the config file.
79    ///
80    /// # Arguments
81    ///
82    /// * `name` - A string that holds the name of the workflow
83    /// * `version` - A string that holds the version of the workflow
84    /// * `tasks` - The tasks of the workflow
85    /// * `custom_types` - Optional custom types for the workflow
86    /// * `eval` - A mutable reference to the Evaluator (injected by the starlark rust package)
87    ///
88    /// # Returns
89    ///
90    /// * a workflow object of `Workflow` type
91    ///
92    fn workflows(
93        name: String,
94        version: String,
95        tasks: Value,
96        eval: &mut Evaluator,
97    ) -> anyhow::Result<Workflow> {
98        let tasks: Vec<Task> = serde_json::from_str(&tasks.to_json()?)
99            .map_err(|err| anyhow!("Failed to parse task value: {}", err))?;
100
101        let mut task_hashmap = HashMap::new();
102
103        for task in tasks {
104            if task_hashmap.contains_key(&task.action_name) {
105                return Err(Error::msg("Duplicate tasks, Task names must be unique"));
106            } else {
107                task_hashmap.insert(task.action_name.clone(), task);
108            }
109        }
110
111        eval.extra
112            .as_ref()
113            .and_then(|extra| extra.downcast_ref::<Composer>())
114            .ok_or_else(|| anyhow!("Failed to obtain Composer from Evaluator"))?
115            .add_workflow(name.clone(), version.clone(), task_hashmap.clone())
116            .map_err(|err| anyhow!("Failed to add workflow: {}", err))?;
117
118        Ok(Workflow {
119            name,
120            version,
121            tasks: task_hashmap,
122        })
123    }
124
125    /// Creates a new field for the input argument of a task
126    ///
127    /// # Arguments
128    ///
129    /// * `name` - A string that holds the name of the input field
130    /// * `input_type` - A string that holds the type of the input field
131    /// * `default_value` - An optional JSON default value for the input field
132    ///
133    /// # Returns
134    ///
135    /// * A Result containing the input object of `Input` type
136    ///
137    fn argument(
138        name: String,
139        input_type: Value,
140        default_value: Option<Value>,
141    ) -> anyhow::Result<Input> {
142        let input_type: RustType = serde_json::from_str(&input_type.to_json()?)
143            .map_err(|err| anyhow!("Failed to parse input arguments: {}", err))?;
144
145        let default_value: Option<String> = match default_value {
146            Some(value) => {
147                let value_str = value
148                    .to_json()
149                    .map_err(|err| anyhow!("Failed to parse default value: {}", err))?;
150
151                match input_type {
152                    RustType::String => {
153                        if !value_str.contains("\"") {
154                            return Err(anyhow!("Value must be in String type"));
155                        }
156                    }
157                    RustType::Int => {
158                        if value_str.parse::<i32>().is_err() {
159                            return Err(anyhow!("Value must be an integer"));
160                        }
161                    }
162                    RustType::Float => {
163                        if value_str.parse::<f32>().is_err() {
164                            return Err(anyhow!("Value must be a float"));
165                        }
166                    }
167                    RustType::Uint => {
168                        if value_str.parse::<u32>().is_err() {
169                            return Err(anyhow!("Value must be a positive integer"));
170                        }
171                    }
172                    RustType::Boolean => {
173                        if value_str != "true" && value_str != "false" {
174                            return Err(anyhow!("Value must be either true or false"));
175                        }
176                    }
177
178                    _ => return Err(anyhow!("Unsupported input type for default value")),
179                }
180
181                Some(value_str)
182            }
183            None => Default::default(),
184        };
185
186        Ok(Input {
187            name,
188            input_type,
189            default_value,
190            is_depend: false,
191        })
192    }
193
194    fn depend(task_name: String, cur_field: String, prev_field: String) -> anyhow::Result<Depend> {
195        Ok(Depend {
196            task_name,
197            cur_field,
198            prev_field,
199        })
200    }
201
202    /// Creates a user-defined type inside the `types.rs`.
203    /// This method will be invoked inside the config file.
204    ///
205    /// # Arguments
206    ///
207    /// * `name` - The name of the user-defined type
208    /// * `fields` - The fields of the user-defined type in JSON format
209    /// * `eval` - A mutable reference to the Evaluator (injected by the starlark rust package)
210    ///
211    /// # Returns
212    ///
213    /// * A Result containing the name of the user-defined type
214    ///
215    fn EchoStruct(name: String, fields: Value, eval: &mut Evaluator) -> anyhow::Result<RustType> {
216        let fields: HashMap<String, RustType> = serde_json::from_str(&fields.to_json()?)
217            .map_err(|err| anyhow!("Failed to parse fields: {}", err))?;
218
219        let composer = eval
220            .extra
221            .as_ref()
222            .and_then(|extra| extra.downcast_ref::<Composer>())
223            .ok_or_else(|| anyhow!("Failed to obtain Composer from Evaluator"))?;
224        let name = name.to_case(Case::Pascal);
225
226        let mut build_string = Vec::new();
227
228        for (key, value) in fields {
229            build_string.push(format!("{}:{}", key, value));
230        }
231
232        let build_string = format!("[{}]", build_string.join(","));
233
234        composer
235            .custom_types
236            .borrow_mut()
237            .insert(
238                name.to_string(),
239                format!(
240                "make_input_struct!(\n{},\n{},\n[Default, Clone, Debug, Deserialize, Serialize]\n);",
241                &name,
242                build_string
243            ));
244
245        Ok(RustType::Struct(name))
246    }
247}
248
249#[starlark_module]
250pub fn starlark_datatype_module(builder: &mut GlobalsBuilder) {
251    /// Returns the Rust type for a tuple with specified types of the key and vale
252    /// This method will be invoked inside the config file.
253    ///
254    /// # Arguments
255    ///
256    /// * `type_1` - The type of the tuple field-1
257    /// * `type_2` - The type of the tuple field-2
258    ///
259    /// # Returns
260    ///
261    /// * A Result containing the Rust type for a map
262    ///
263    fn Tuple(type_1: Value, type_2: Value) -> anyhow::Result<RustType> {
264        let type_1: RustType = serde_json::from_str(&type_1.to_json()?)
265            .map_err(|err| anyhow!("Failed to parse values: {}", err))?;
266        let type_2: RustType = serde_json::from_str(&type_2.to_json()?)
267            .map_err(|err| anyhow!("Failed to parse values: {}", err))?;
268
269        Ok(RustType::Tuple(Box::new(type_1), Box::new(type_2)))
270    }
271
272    /// Returns the Rust type for a map with specified types of the key and vale
273    /// This method will be invoked inside the config file.
274    ///
275    /// # Arguments
276    ///
277    /// * `type_1` - The type of the key
278    /// * `type_2` - The type of the value
279    ///
280    /// # Returns
281    ///
282    /// * A Result containing the Rust type for a map
283    ///
284    fn HashMap(type_1: Value, type_2: Value) -> anyhow::Result<RustType> {
285        let type_1: RustType = serde_json::from_str(&type_1.to_json()?)
286            .map_err(|err| anyhow!("Failed to parse values: {}", err))?;
287        let type_2: RustType = serde_json::from_str(&type_2.to_json()?)
288            .map_err(|err| anyhow!("Failed to parse values: {}", err))?;
289
290        Ok(RustType::HashMap(Box::new(type_1), Box::new(type_2)))
291    }
292
293    /// Returns the Rust type for a list with specified element type
294    /// This method will be invoked inside the config file.
295    ///
296    /// # Arguments
297    ///
298    /// * `type_of` - The type of the element in the list
299    ///
300    /// # Returns
301    ///
302    ///  * A Result containing the Rust type for a list
303    ///
304    fn List(type_of: Value) -> anyhow::Result<RustType> {
305        let type_of: RustType = serde_json::from_str(&type_of.to_json()?)
306            .map_err(|err| anyhow!("Failed to parse values: {}", err))?;
307        Ok(RustType::List(Box::new(type_of)))
308    }
309}
310
311#[starlark_module]
312pub fn starlark_operation_module(builder: &mut GlobalsBuilder) {
313    /// Returns `Operation::Normal` task-operation type to the config file
314    /// This method will be invoked inside the config file
315    ///
316    /// # Returns
317    ///
318    /// * A Result containing Operation::Normal
319    ///   
320    fn normal() -> anyhow::Result<Operation> {
321        Ok(Operation::Normal)
322    }
323
324    /// Returns `Operation::Concat` task-operation type to the config file
325    /// This method will be invoked inside the config file
326    ///
327    /// # Returns
328    ///
329    /// * A Result containing Operation::Concat
330    ///   
331    fn concat() -> anyhow::Result<Operation> {
332        Ok(Operation::Concat)
333    }
334
335    /// Returns `Operation::Concat` task-operation type to the config file
336    /// This method will be invoked inside the config file
337    ///
338    /// # Returns
339    ///
340    /// * A Result containing Operation::Concat
341    ///   
342    fn combine() -> anyhow::Result<Operation> {
343        Ok(Operation::Combine)
344    }
345
346    /// Returns `Operation::Map(field)` task-operation type to the config file
347    /// This method will be invoked inside the config file
348    ///
349    /// # Arguments
350    ///
351    /// * `field` - A String containing name of the field that should be fetch from the previous task
352    ///
353    /// # Returns
354    ///
355    /// * A Result containing Operation::Map(field)
356    ///   
357    fn map(field: String) -> anyhow::Result<Operation> {
358        Ok(Operation::Map(field))
359    }
360}