From f37a15fd7e9007d89041a334954e0a4c01d1e22a Mon Sep 17 00:00:00 2001 From: Prathiksha-Nataraja <90592522+Prathiksha-Nataraja@users.noreply.github.com> Date: Thu, 16 May 2024 16:53:18 +0530 Subject: [PATCH 1/4] fix: handle rustypes List, Tuple, HashMap, Struct --- echo-library/src/common/starlark_modules.rs | 199 +++++++++++++++++++- 1 file changed, 196 insertions(+), 3 deletions(-) diff --git a/echo-library/src/common/starlark_modules.rs b/echo-library/src/common/starlark_modules.rs index f268e8c..799219f 100644 --- a/echo-library/src/common/starlark_modules.rs +++ b/echo-library/src/common/starlark_modules.rs @@ -176,9 +176,202 @@ pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) { return Err(anyhow!("Value must be either true or false")); } } - RustType::HashMap(_, _) => {} - RustType::List(_) => {} - RustType::Tuple(_, _) => {} + RustType::HashMap(ref key_type, ref value_type) => { + let parsed_map = serde_json::from_str::< + serde_json::Map, + >(&value_str) + .map_err(|_| { + anyhow!("Value must be a valid JSON object for HashMap type") + })?; + + // Iterate through each key-value pair in the map and validate types + for (key, value) in parsed_map.iter() { + // Validate the key + match **key_type { + RustType::String => { + if !key.parse::().is_ok() { + return Err(anyhow!( + "Key must be a string for HashMap(String, _)" + )); + } + } + RustType::Int => { + if key.parse::().is_err() { + return Err(anyhow!( + "Key must be an integer for HashMap(Int, _)" + )); + } + } + RustType::Uint => { + if key.parse::().is_err() { + return Err(anyhow!( + "Key must be an unsigned integer for HashMap(Uint, _)" + )); + } + } + RustType::Float => { + if !value.is_f64() { + return Err(anyhow!( + "Key must be a float for HashMap(Float, _)" + )); + } + } + _ => return Err(anyhow!("Unsupported key type for HashMap")), + } + + // Validate the value + match **value_type { + RustType::String => { + if !value.is_string() { + return Err(anyhow!( + "Value must be a string for HashMap(_, String)" + )); + } + } + RustType::Int => { + if !value.is_i64() { + return Err(anyhow!( + "Value must be an integer for HashMap(_, Int)" + )); + } + } + RustType::Uint => { + if !value.is_u64() { + return Err(anyhow!("Value must be an unsigned integer for HashMap(_, Uint)")); + } + } + RustType::Float => { + if !value.is_f64() { + return Err(anyhow!( + "Value must be a float for HashMap(_, Float)" + )); + } + } + _ => return Err(anyhow!("Unsupported value type for HashMap")), + } + } + } + RustType::List(ref item_type) => { + let parsed_array = serde_json::from_str::>( + &value_str, + ) + .map_err(|_| anyhow!("Value must be a valid JSON array for List type"))?; + + // Check if each element in the array matches the expected item type + for element in parsed_array.iter() { + match **item_type { + RustType::String => { + if !element.is_string() { + return Err(anyhow!( + "List elements must be strings for List(String)" + )); + } + } + RustType::Int => { + if !element.is_i64() && !element.is_i64() { + return Err(anyhow!( + "List elements must be integers for List(Int)" + )); + } + } + RustType::Uint => { + if !element.is_f64() && !element.is_u64() { + return Err(anyhow!( + "List elements must be integers for List(Uint)" + )); + } + } + RustType::Float => { + if !element.is_i64() && !element.is_f64() { + return Err(anyhow!( + "List elements must be integers for List(Float)" + )); + } + } + _ => {} + } + } + } + RustType::Tuple(ref key_type, ref value_type) => { + // Parse the value_str as a JSON array + let parsed_tuple = serde_json::from_str::>( + &value_str, + ) + .map_err(|_| anyhow!("Value must be a valid JSON tuple for Tuple type"))?; + + // Ensure the tuple has exactly two elements + if parsed_tuple.len() != 2 { + return Err(anyhow!("Tuple must have exactly two elements")); + } + + // Validate the first element of the tuple + match **key_type { + RustType::String => { + if !parsed_tuple[0].is_string() { + return Err(anyhow!( + "First element of the tuple must be a string" + )); + } + } + RustType::Int => { + if !parsed_tuple[0].is_i64() { + return Err(anyhow!( + "First element of the tuple must be an integer" + )); + } + } + RustType::Uint => { + if !parsed_tuple[0].is_u64() { + return Err(anyhow!( + "First element of the tuple must be an unsigned integer" + )); + } + } + RustType::Float => { + if !parsed_tuple[0].is_f64() { + return Err(anyhow!( + "First element of the tuple must be a float" + )); + } + } + + _ => {} + } + + // Validate the second element of the tuple + match **value_type { + RustType::String => { + if !parsed_tuple[1].is_string() { + return Err(anyhow!( + "Second element of the tuple must be a string" + )); + } + } + RustType::Int => { + if !parsed_tuple[1].is_i64() { + return Err(anyhow!( + "Second element of the tuple must be an integer" + )); + } + } + RustType::Uint => { + if !parsed_tuple[1].is_u64() { + return Err(anyhow!( + "Second element of the tuple must be an unsigned integer" + )); + } + } + RustType::Float => { + if !parsed_tuple[1].is_f64() { + return Err(anyhow!( + "Second element of the tuple must be a float" + )); + } + } + + _ => {} + } + } RustType::Struct(_) => {} _ => { return Err(anyhow!("Unsupported input type for default value")); From 4ddcbfbe173d1a2bdc598e0bc9c7b047450a0f0b Mon Sep 17 00:00:00 2001 From: Prathiksha-Nataraja <90592522+Prathiksha-Nataraja@users.noreply.github.com> Date: Tue, 28 May 2024 17:06:44 +0530 Subject: [PATCH 2/4] chore: update handling rustypes --- echo-library/src/common/starlark_modules.rs | 309 ++++++-------------- echo-library/src/types/rust_types.rs | 2 + test/main.echo | 17 ++ 3 files changed, 101 insertions(+), 227 deletions(-) create mode 100644 test/main.echo diff --git a/echo-library/src/common/starlark_modules.rs b/echo-library/src/common/starlark_modules.rs index 799219f..6c3c235 100644 --- a/echo-library/src/common/starlark_modules.rs +++ b/echo-library/src/common/starlark_modules.rs @@ -1,5 +1,83 @@ use super::*; use anyhow::anyhow; + +fn validate_value(value: &serde_json::Value, expected_type: &RustType) -> anyhow::Result<()> { + match expected_type { + RustType::String => { + if !value.is_string() { + return Err(anyhow!("Value must be a string")); + } + } + RustType::Int => { + if !value.is_i64() { + return Err(anyhow!("Value must be an integer")); + } + } + RustType::Float => { + if !value.is_f64() { + return Err(anyhow!("Value must be a float")); + } + } + RustType::Uint => { + if !value.is_u64() { + return Err(anyhow!("Value must be a positive integer")); + } + } + RustType::Boolean => { + if !value.is_boolean() { + return Err(anyhow!("Value must be a boolean")); + } + } + + RustType::HashMap(key_type, value_type) => { + if let Some(map) = value.as_object() { + for (key, val) in map.iter() { + validate_value(val, value_type)?; + } + } else { + return Err(anyhow!("Value must be a JSON object")); + } + } + + RustType::List(item_type) => { + let parsed_array = value + .as_array() + .ok_or_else(|| anyhow!("Value must be a JSON array"))?; + + for element in parsed_array.iter() { + validate_value(element, item_type)?; + } + } + + RustType::Tuple(key_type, value_type) => { + let parsed_tuple = value + .as_array() + .ok_or_else(|| anyhow!("Value must be a JSON tuple with two elements"))?; + + if parsed_tuple.len() != 2 { + return Err(anyhow!("Tuple must have exactly two elements")); + } + + // Pattern matching for key and value types + match (&parsed_tuple[0], &parsed_tuple[1]) { + (serde_json::Value::String(key), value) => validate_value(value, value_type)?, + (serde_json::Value::Number(number), value) if number.is_i64() => { + validate_value(value, value_type)? + } + (serde_json::Value::Number(number), value) if number.is_f64() => { + validate_value(value, value_type)? + } + (serde_json::Value::Bool(bool_value), value) => validate_value(value, value_type)?, + _ => return Err(anyhow!("Unsupported tuple element types")), + } + } + + _ => return Err(anyhow!("Unsupported input type for default value")), + } + + Ok(()) +} + #[allow(clippy::type_complexity)] #[starlark_module] pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) { @@ -136,6 +214,7 @@ pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) { /// /// * A Result containing the input object of `Input` type /// + fn argument( name: String, input_type: Value, @@ -150,233 +229,9 @@ pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) { .to_json() .map_err(|err| anyhow!("Failed to parse default value: {}", err))?; - match input_type { - RustType::String => { - if !value_str.contains("\"") { - return Err(anyhow!("Value must be in String type")); - } - } - RustType::Int => { - if value_str.parse::().is_err() { - return Err(anyhow!("Value must be an integer")); - } - } - RustType::Float => { - if value_str.parse::().is_err() { - return Err(anyhow!("Value must be a float")); - } - } - RustType::Uint => { - if value_str.parse::().is_err() { - return Err(anyhow!("Value must be a positive integer")); - } - } - RustType::Boolean => { - if value_str != "true" && value_str != "false" { - return Err(anyhow!("Value must be either true or false")); - } - } - RustType::HashMap(ref key_type, ref value_type) => { - let parsed_map = serde_json::from_str::< - serde_json::Map, - >(&value_str) - .map_err(|_| { - anyhow!("Value must be a valid JSON object for HashMap type") - })?; - - // Iterate through each key-value pair in the map and validate types - for (key, value) in parsed_map.iter() { - // Validate the key - match **key_type { - RustType::String => { - if !key.parse::().is_ok() { - return Err(anyhow!( - "Key must be a string for HashMap(String, _)" - )); - } - } - RustType::Int => { - if key.parse::().is_err() { - return Err(anyhow!( - "Key must be an integer for HashMap(Int, _)" - )); - } - } - RustType::Uint => { - if key.parse::().is_err() { - return Err(anyhow!( - "Key must be an unsigned integer for HashMap(Uint, _)" - )); - } - } - RustType::Float => { - if !value.is_f64() { - return Err(anyhow!( - "Key must be a float for HashMap(Float, _)" - )); - } - } - _ => return Err(anyhow!("Unsupported key type for HashMap")), - } - - // Validate the value - match **value_type { - RustType::String => { - if !value.is_string() { - return Err(anyhow!( - "Value must be a string for HashMap(_, String)" - )); - } - } - RustType::Int => { - if !value.is_i64() { - return Err(anyhow!( - "Value must be an integer for HashMap(_, Int)" - )); - } - } - RustType::Uint => { - if !value.is_u64() { - return Err(anyhow!("Value must be an unsigned integer for HashMap(_, Uint)")); - } - } - RustType::Float => { - if !value.is_f64() { - return Err(anyhow!( - "Value must be a float for HashMap(_, Float)" - )); - } - } - _ => return Err(anyhow!("Unsupported value type for HashMap")), - } - } - } - RustType::List(ref item_type) => { - let parsed_array = serde_json::from_str::>( - &value_str, - ) - .map_err(|_| anyhow!("Value must be a valid JSON array for List type"))?; - - // Check if each element in the array matches the expected item type - for element in parsed_array.iter() { - match **item_type { - RustType::String => { - if !element.is_string() { - return Err(anyhow!( - "List elements must be strings for List(String)" - )); - } - } - RustType::Int => { - if !element.is_i64() && !element.is_i64() { - return Err(anyhow!( - "List elements must be integers for List(Int)" - )); - } - } - RustType::Uint => { - if !element.is_f64() && !element.is_u64() { - return Err(anyhow!( - "List elements must be integers for List(Uint)" - )); - } - } - RustType::Float => { - if !element.is_i64() && !element.is_f64() { - return Err(anyhow!( - "List elements must be integers for List(Float)" - )); - } - } - _ => {} - } - } - } - RustType::Tuple(ref key_type, ref value_type) => { - // Parse the value_str as a JSON array - let parsed_tuple = serde_json::from_str::>( - &value_str, - ) - .map_err(|_| anyhow!("Value must be a valid JSON tuple for Tuple type"))?; - - // Ensure the tuple has exactly two elements - if parsed_tuple.len() != 2 { - return Err(anyhow!("Tuple must have exactly two elements")); - } - - // Validate the first element of the tuple - match **key_type { - RustType::String => { - if !parsed_tuple[0].is_string() { - return Err(anyhow!( - "First element of the tuple must be a string" - )); - } - } - RustType::Int => { - if !parsed_tuple[0].is_i64() { - return Err(anyhow!( - "First element of the tuple must be an integer" - )); - } - } - RustType::Uint => { - if !parsed_tuple[0].is_u64() { - return Err(anyhow!( - "First element of the tuple must be an unsigned integer" - )); - } - } - RustType::Float => { - if !parsed_tuple[0].is_f64() { - return Err(anyhow!( - "First element of the tuple must be a float" - )); - } - } - - _ => {} - } - - // Validate the second element of the tuple - match **value_type { - RustType::String => { - if !parsed_tuple[1].is_string() { - return Err(anyhow!( - "Second element of the tuple must be a string" - )); - } - } - RustType::Int => { - if !parsed_tuple[1].is_i64() { - return Err(anyhow!( - "Second element of the tuple must be an integer" - )); - } - } - RustType::Uint => { - if !parsed_tuple[1].is_u64() { - return Err(anyhow!( - "Second element of the tuple must be an unsigned integer" - )); - } - } - RustType::Float => { - if !parsed_tuple[1].is_f64() { - return Err(anyhow!( - "Second element of the tuple must be a float" - )); - } - } - - _ => {} - } - } - RustType::Struct(_) => {} - _ => { - return Err(anyhow!("Unsupported input type for default value")); - } - } + let parsed_value = serde_json::from_str(&value_str)?; + validate_value(&parsed_value, &input_type)?; + // validate_value(&serde_json::from_str(&value_str)?, &input_type)?; Some(value_str) } diff --git a/echo-library/src/types/rust_types.rs b/echo-library/src/types/rust_types.rs index 7e9087d..2b3f751 100644 --- a/echo-library/src/types/rust_types.rs +++ b/echo-library/src/types/rust_types.rs @@ -1,3 +1,5 @@ +use rayon::collections::btree_map::IterMut; + use super::*; #[derive(Debug, PartialEq, Eq, Allocative, ProvidesStaticType, Clone, Deserialize, Serialize)] diff --git a/test/main.echo b/test/main.echo new file mode 100644 index 0000000..7bf7cad --- /dev/null +++ b/test/main.echo @@ -0,0 +1,17 @@ +hello_world = task( + kind = "hello_world", + action_name = "hello_world", + input_arguments = [ + argument( + name="name", + input_type = HashMap(String, Int), + default_value = {"ff" : 5} + ), + ], +) + +workflows( + name = "test", + version = "0.0.1", + tasks = [hello_world] +) \ No newline at end of file From 1311ffcffac6a4dabd112d2a1935dce1d5e3f370 Mon Sep 17 00:00:00 2001 From: Prathiksha-Nataraja <90592522+Prathiksha-Nataraja@users.noreply.github.com> Date: Fri, 31 May 2024 12:32:55 +0530 Subject: [PATCH 3/4] chore: update handling for the Rustype struct, value --- echo-library/src/common/starlark_modules.rs | 9 ++++++++- echo-library/src/types/rust_types.rs | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/echo-library/src/common/starlark_modules.rs b/echo-library/src/common/starlark_modules.rs index 6c3c235..2f8ff41 100644 --- a/echo-library/src/common/starlark_modules.rs +++ b/echo-library/src/common/starlark_modules.rs @@ -29,6 +29,14 @@ fn validate_value(value: &serde_json::Value, expected_type: &RustType) -> anyhow } } + RustType::Struct(_) => {} + + RustType::Value => { + if !value.is_object() { + return Err(anyhow!("value must be a Json Object")); + } + } + RustType::HashMap(key_type, value_type) => { if let Some(map) = value.as_object() { for (key, val) in map.iter() { @@ -231,7 +239,6 @@ pub fn starlark_workflow_module(builder: &mut GlobalsBuilder) { let parsed_value = serde_json::from_str(&value_str)?; validate_value(&parsed_value, &input_type)?; - // validate_value(&serde_json::from_str(&value_str)?, &input_type)?; Some(value_str) } diff --git a/echo-library/src/types/rust_types.rs b/echo-library/src/types/rust_types.rs index 2b3f751..7e9087d 100644 --- a/echo-library/src/types/rust_types.rs +++ b/echo-library/src/types/rust_types.rs @@ -1,5 +1,3 @@ -use rayon::collections::btree_map::IterMut; - use super::*; #[derive(Debug, PartialEq, Eq, Allocative, ProvidesStaticType, Clone, Deserialize, Serialize)] From d235e9fa8f0b2ac096c17ba695c29a9f71a58bb2 Mon Sep 17 00:00:00 2001 From: Prathiksha-Nataraja <90592522+Prathiksha-Nataraja@users.noreply.github.com> Date: Fri, 31 May 2024 16:28:55 +0530 Subject: [PATCH 4/4] Delete test/main.echo --- test/main.echo | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 test/main.echo diff --git a/test/main.echo b/test/main.echo deleted file mode 100644 index 7bf7cad..0000000 --- a/test/main.echo +++ /dev/null @@ -1,17 +0,0 @@ -hello_world = task( - kind = "hello_world", - action_name = "hello_world", - input_arguments = [ - argument( - name="name", - input_type = HashMap(String, Int), - default_value = {"ff" : 5} - ), - ], -) - -workflows( - name = "test", - version = "0.0.1", - tasks = [hello_world] -) \ No newline at end of file