diff --git a/crates/frontend/src/components/condition_pills.rs b/crates/frontend/src/components/condition_pills.rs index b39e45be..02d4bc1d 100644 --- a/crates/frontend/src/components/condition_pills.rs +++ b/crates/frontend/src/components/condition_pills.rs @@ -1,15 +1,12 @@ -pub mod types; -pub mod utils; +use crate::{ + logic::{Condition, Conditions, Operand, Operator}, + schema::HtmlDisplay, +}; use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; -use serde_json::Value; use wasm_bindgen::JsCast; use web_sys::Element; -use crate::components::condition_pills::types::ConditionOperator; - -use self::types::Condition; - use derive_more::{Deref, DerefMut}; #[derive(Debug, Clone, Deref, DerefMut, Default)] @@ -60,30 +57,13 @@ pub fn condition_expression( } else { ("condition-item-collapsed", "condition-value-collapsed") }; - let Condition { left_operand: dimension, operator, right_operand: value } = condition - .get_value(); - let filtered_vals: Vec = value - .into_iter() - .filter_map(|v| { - if v.is_object() && v.get("var").is_some() { - None - } else { - match v { - Value::String(s) => Some(s.to_string()), - Value::Number(n) => Some(n.to_string()), - Value::Bool(b) => Some(b.to_string()), - Value::Array(arr) => { - Some( - arr - .iter() - .map(|v| v.to_string()) - .collect::>() - .join(","), - ) - } - Value::Object(o) => serde_json::to_string_pretty(&o).ok(), - _ => None, - } + let Condition { dimension, operator, operands } = condition.get_value(); + let operand_str: Vec = operands + .iter() + .filter_map(|operand| { + match operand { + Operand::Dimension(_) => None, + Operand::Value(v) => Some(v.html_display()), } }) .collect(); @@ -106,18 +86,18 @@ pub fn condition_expression( {match operator { - ConditionOperator::Between => { - if filtered_vals.len() == 2 { + Operator::Between => { + if operand_str.len() == 2 { view! { <> - {&filtered_vals[0]} + {&operand_str[0]} {"and"} - {&filtered_vals[1]} + {&operand_str[1]} } @@ -132,7 +112,7 @@ pub fn condition_expression( } } _ => { - let rendered_value = filtered_vals.join(", "); + let rendered_value = operand_str.join(", "); view! { {rendered_value} }.into_view() } }} @@ -146,7 +126,7 @@ pub fn condition_expression( #[component] pub fn condition( #[prop(into)] id: String, - #[prop(into)] conditions: Vec, + #[prop(into)] conditions: Conditions, #[prop(into, default=String::new())] class: String, #[prop(default = true)] grouped_view: bool, ) -> impl IntoView { @@ -164,11 +144,17 @@ pub fn condition( .clone()> {conditions .get_value() - .into_iter() + .iter() .enumerate() .map(|(idx, condition)| { let item_id = format!("{}-{}", id, idx); - view! { } + view! { + + } }) .collect::>()} diff --git a/crates/frontend/src/components/condition_pills/types.rs b/crates/frontend/src/components/condition_pills/types.rs deleted file mode 100644 index 9bff2921..00000000 --- a/crates/frontend/src/components/condition_pills/types.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::fmt::Display; - -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; -use superposition_types::Context; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ConditionOperator { - Is, - In, - Has, - Between, - Other(String), -} - -impl Display for ConditionOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Has => f.write_str("has"), - Self::Is => f.write_str("is"), - Self::In => f.write_str("in"), - Self::Between => f.write_str("between"), - Self::Other(o) => f.write_str(o), - } - } -} - -impl From for ConditionOperator { - fn from(op: String) -> Self { - match op.as_str() { - "==" => ConditionOperator::Is, - "<=" => ConditionOperator::Between, - "in" => ConditionOperator::In, - "has" => ConditionOperator::Has, - other => ConditionOperator::Other(other.to_string()), - } - } -} - -impl From<(String, &Vec)> for ConditionOperator { - fn from(value: (String, &Vec)) -> Self { - let (operator, operands) = value; - let operand_0 = operands.first(); - let operand_1 = operands.get(1); - let operand_2 = operands.get(2); - match (operator.as_str(), operand_0, operand_1, operand_2) { - // assuming there will be only two operands, one with the dimension name and other with the value - ("==", _, _, None) => ConditionOperator::Is, - ("<=", Some(_), Some(Value::Object(a)), Some(_)) if a.contains_key("var") => { - ConditionOperator::Between - } - // assuming there will be only two operands, one with the dimension name and other with the value - ("in", Some(Value::Object(a)), Some(_), None) if a.contains_key("var") => { - ConditionOperator::In - } - // assuming there will be only two operands, one with the dimension name and other with the value - ("in", Some(_), Some(Value::Object(a)), None) if a.contains_key("var") => { - ConditionOperator::Has - } - _ => ConditionOperator::Other(operator), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Condition { - pub left_operand: String, - pub operator: ConditionOperator, - pub right_operand: Vec, -} - -#[derive(Default, Clone)] -pub struct Conditions(pub Vec); - -impl TryFrom<&Map> for Condition { - type Error = &'static str; - fn try_from(source: &Map) -> Result { - if let Some(operator) = source.keys().next() { - let emty_vec = vec![]; - let operands = source[operator].as_array().unwrap_or(&emty_vec); - - let operator = ConditionOperator::from((operator.to_owned(), operands)); - - let dimension_name = operands - .iter() - .find_map(|item| match item.as_object() { - Some(o) if o.contains_key("var") => { - Some(o["var"].as_str().unwrap_or("")) - } - _ => None, - }) - .unwrap_or(""); - - return Ok(Condition { - operator, - left_operand: dimension_name.to_owned(), - right_operand: operands - .to_vec() - .into_iter() - .filter(|operand| { - !(operand.is_object() && operand.get("var").is_some()) - }) - .collect(), - }); - } - - Err("not a valid condition map") - } -} - -impl TryFrom<&Value> for Condition { - type Error = &'static str; - fn try_from(value: &Value) -> Result { - let obj = value - .as_object() - .ok_or("not a valid condition value, should be an object")?; - Condition::try_from(obj) - } -} - -impl TryFrom<&Context> for Conditions { - type Error = &'static str; - fn try_from(context: &Context) -> Result { - let obj: Map = context.condition.clone().into(); - match obj.get("and") { - Some(v) => v - .as_array() - .ok_or("failed to parse value of and as array") - .and_then(|arr| { - arr.iter() - .map(Condition::try_from) - .collect::, &'static str>>() - }), - None => Condition::try_from(&obj).map(|v| vec![v]), - } - .map(Self) - } -} diff --git a/crates/frontend/src/components/condition_pills/utils.rs b/crates/frontend/src/components/condition_pills/utils.rs deleted file mode 100644 index 53f3092c..00000000 --- a/crates/frontend/src/components/condition_pills/utils.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::types::Condition; -use serde_json::Value; - -pub fn extract_conditions(context: &Value) -> Vec { - context - .as_object() - .and_then(|obj| { - obj.get("and") - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|condition| Condition::try_from(condition).ok()) - .collect::>() - }) - .or_else(|| Condition::try_from(obj).ok().map(|v| vec![v])) - }) - .unwrap_or_default() -} diff --git a/crates/frontend/src/components/context_card.rs b/crates/frontend/src/components/context_card.rs index 36a7e9d4..e6104442 100644 --- a/crates/frontend/src/components/context_card.rs +++ b/crates/frontend/src/components/context_card.rs @@ -2,10 +2,12 @@ use leptos::*; use serde_json::{Map, Value}; use superposition_types::Context; -use crate::components::condition_pills::types::{ConditionOperator, Conditions}; -use crate::components::{ - condition_pills::Condition as ConditionComponent, - table::{types::Column, Table}, +use crate::{ + components::{ + condition_pills::Condition as ConditionComponent, + table::{types::Column, Table}, + }, + logic::{Conditions, Operator}, }; #[component] @@ -50,12 +52,12 @@ pub fn context_card( && !conditions .0 .iter() - .any(|condition| condition.left_operand == "variantIds"); + .any(|condition| condition.dimension == "variantIds"); let edit_unsupported = conditions .0 .iter() - .any(|condition| matches!(condition.operator, ConditionOperator::Other(_))); + .any(|condition| matches!(condition.operator, Operator::Other(_))); view! {
@@ -109,7 +111,7 @@ pub fn context_card(
diff --git a/crates/frontend/src/components/context_form.rs b/crates/frontend/src/components/context_form.rs index 72e13335..844fa6ab 100644 --- a/crates/frontend/src/components/context_form.rs +++ b/crates/frontend/src/components/context_form.rs @@ -1,469 +1,379 @@ pub mod utils; use std::collections::{HashMap, HashSet}; -use crate::components::{ - condition_pills::types::ConditionOperator, - dropdown::{Dropdown, DropdownDirection}, - input_components::{BooleanToggle, EnumDropdown}, -}; +use crate::components::input::{Input, InputType}; +use crate::logic::{Condition, Conditions, Operand, Operands, Operator}; +use crate::schema::EnumVariants; use crate::types::Dimension; -use crate::utils::get_key_type; +use crate::{ + components::dropdown::{Dropdown, DropdownDirection}, + schema::SchemaType, +}; use leptos::*; -use serde_json::{Map, Value}; -use web_sys::MouseEvent; +use serde_json::Value; + +#[component] +pub fn condition_input( + disabled: bool, + resolve_mode: bool, + allow_remove: bool, + condition: StoredValue, + input_type: StoredValue, + schema_type: StoredValue, + #[prop(into)] on_remove: Callback, + #[prop(into)] on_value_change: Callback<(usize, Value), ()>, + #[prop(into)] on_operator_change: Callback, +) -> impl IntoView { + let Condition { + dimension, + operator, + operands, + } = condition.get_value(); + + view! { +
+
+ + +
+
+ + + + +
+
+ +
+ + {operands + .0 + .clone() + .into_iter() + .enumerate() + .map(|(idx, operand): (usize, Operand)| { + match operand { + Operand::Dimension(_) => view! {}.into_view(), + Operand::Value(v) => { + view! { + + } + .into_view() + } + } + }) + .collect_view()} + + +
+
+
+ } +} #[component] pub fn context_form( handle_change: NF, + context: Conditions, dimensions: Vec, - #[prop(default = false)] is_standalone: bool, - context: Vec, - #[prop(default = String::new())] heading_sub_text: String, #[prop(default = false)] disabled: bool, - #[prop(default = DropdownDirection::Right)] dropdown_direction: DropdownDirection, #[prop(default = false)] resolve_mode: bool, + #[prop(default = String::new())] heading_sub_text: String, + #[prop(default = DropdownDirection::Right)] dropdown_direction: DropdownDirection, ) -> impl IntoView where - NF: Fn(Vec) + 'static, + NF: Fn(Conditions) + 'static, { - // let _has_dimensions = !dimensions.is_empty(); - - let (used_dimensions, set_used_dimensions) = create_signal( + let dimension_map = store_value( + dimensions + .iter() + .map(|v| (v.dimension.clone(), v.clone())) + .collect::>(), + ); + let (used_dimensions_rs, used_dimensions_ws) = create_signal( context .iter() - .map(|condition| condition.left_operand.clone()) + .map(|condition| condition.dimension.clone()) .collect::>(), ); - let (context, set_context) = create_signal(context.clone()); + let (context_rs, context_ws) = create_signal(context.clone()); let dimensions = StoredValue::new(dimensions); let mandatory_dimensions = StoredValue::new( dimensions .get_value() .into_iter() - .filter_map(|dim| { - if dim.mandatory { - Some(dim.dimension) - } else { - None - } - }) + .filter(|dim| dim.mandatory) + .map(|dim| dim.dimension) .collect::>(), ); - let last_idx = create_memo(move |_| context.get().len().max(1) - 1); - - let on_click = move |event: MouseEvent| { - event.prevent_default(); - logging::log!("Context form submit"); - //TODO: submit logic for this - }; + let last_idx = create_memo(move |_| context_rs.get().len().max(1) - 1); create_effect(move |_| { - let f_context = context.get(); // context will now be a Value + let f_context = context_rs.get(); // context will now be a Value logging::log!("Context form effect {:?}", f_context); handle_change(f_context.clone()); // handle_change now expects Value }); - let handle_select_dropdown_option = - Callback::new(move |selected_dimension: Dimension| { - let dimension_name = selected_dimension.dimension; - set_used_dimensions.update(|value: &mut HashSet| { + let on_select_dimension = Callback::new(move |selected_dimension: Dimension| { + let dimension_name = selected_dimension.dimension; + + if let Ok(r#type) = SchemaType::try_from(selected_dimension.schema) { + used_dimensions_ws.update(|value: &mut HashSet| { value.insert(dimension_name.clone()); }); - set_context.update(|value| { - value.push(Condition { - left_operand: dimension_name.clone(), - operator: ConditionOperator::Is, - right_operand: vec![Value::String("".to_string())], - }) + context_ws.update(|value| { + value.push( + Condition::try_from((Operator::Is, dimension_name, r#type)).unwrap(), + ) }); - }); - - view! { -
-
-
- -
-
-
- -
- -
-
- {move || { - let dimensions_map = dimensions - .get_value() - .into_iter() - .map(|ele| (ele.dimension.clone(), ele)) - .collect::>(); - view! { - >() - } - - key=|(idx, condition)| { - format!("{}-{}-{}", condition.left_operand, idx, condition.operator) - } - - children=move |(idx, condition)| { - let dimension_label = condition.left_operand.to_string(); - // let dimension_label = dimension.to_string(); - let dimension_name = StoredValue::new( - condition.left_operand.to_string(), - ); - let schema: Map = serde_json::from_value( - dimensions_map.get(&dimension_label).unwrap().schema.clone(), - ) - .unwrap(); - let dimension_type = get_key_type(&schema); - if let ConditionOperator::Other(ref op_str) = condition.operator { - if op_str.is_empty() { - set_context.update_untracked(|curr_context| { - curr_context[idx].operator = ConditionOperator::Is; - }); - let mut_operator = String::from("=="); - set_context.update_untracked(|curr_context| { - curr_context[idx].operator = ConditionOperator::Other(mut_operator.clone()); - }); - } - } - view! { - // - -
-
- - -
-
- - - - -
-
- -
+ } + // TODO show alert in case of invalid dimension + }); - { - // Generate input fields based on the condition's right_operand (Vec) - let input_fields = match &condition.right_operand { - values => { - // Filter out any elements that are objects containing a "var" key - let filtered_elements: Vec<_> = values - .iter() - .filter(|v| !v.is_object() || !v.get("var").is_some()) // Exclude elements with "var" - .collect(); + let on_operator_change = Callback::new( + move |(idx, d_name, d_type, operator): (usize, String, SchemaType, Operator)| { + if let Ok(operands) = + Operands::try_from((&operator, d_name.as_str(), &d_type)) + { + context_ws.update(|v| { + if idx < v.len() { + v[idx].operator = operator; + v[idx].operands = operands.clone(); + } + }) + } + // TODO show alert in case of invalid dimension operator combinations + }, + ); - // Directly return the input fields - filtered_elements - .into_iter() // Use `into_iter` to consume the filtered_elements - .enumerate() - .map(|(i, element)| match element { - Value::String(s) => view! { - - }.into_view(), + let on_value_change = + Callback::new(move |(idx, operand_idx, value): (usize, usize, Value)| { + context_ws.update(|v| { + if idx < v.len() { + let operands = &(v[idx].operands); + if operand_idx < operands.len() + && matches!(operands[operand_idx], Operand::Value(_)) + { + v[idx].operands[operand_idx] = Operand::from_operand_json(value); + } + } + }) + }); - Value::Number(n) => view! { - () { // Try to parse input as f64 - set_context.update(|curr_context| { - if let Some(elem) = curr_context[idx].right_operand.get_mut(i) { - if !elem.is_object() || !elem.get("var").is_some() { // Exclude elements with "var" - *elem = Value::Number(serde_json::Number::from_f64(parsed).unwrap()); - } - } - }); - } - } - name="context-dimension-value" - type="number" - placeholder="Type here" - class="input input-bordered w-full bg-white text-gray-700 shadow-md" - /> - }.into_view(), + let on_remove = Callback::new(move |(idx, d_name): (usize, String)| { + used_dimensions_ws.update(|value| { + value.remove(&d_name); + }); + context_ws.update(|v| { + v.remove(idx); + }); + }); - Value::Bool(b) => view! { - +
+ +
+
+
+ +
+ - }.into_view(), +
+
+ >() + } - _ => view! { - - }.into_view(), - }) - .collect::>() // Collect the result into a Vec - } - }; - match condition.operator { - ConditionOperator::Is => { - match dimension_type.as_str() { - "ENUM" => { - let filtered_value = condition.right_operand - .iter() - .find(|v| match v { - Value::Object(obj) => !obj.contains_key("var"), - _ => true, - }) - .map(|v| v.to_string().replace('"', "")) - .unwrap_or_else(|| String::new()); + key=|(idx, condition)| { + format!("{}-{}-{}", condition.dimension, idx, condition.operator) + } - view! { - - } - .into_view() - } - "BOOLEAN" => { - // Ensure we handle Value properly and default to false if not a valid boolean - let is_checked = match &condition.right_operand[0] { - Value::Bool(b) => *b, // Extract the boolean value directly - _ => false, // Default to false if not a boolean - }; - view! { - - } - .into_view() - } - _ => view! { - { - logging::log!("Condition operator and saurav {:?}", condition.operator); - input_fields.into_view() - } - } // Fallback to input field if not ENUM or BOOLEAN - } - } - _ => view! { - {input_fields.into_view()} - } // For other operators, use input_fields as default - } - } - - - -
-
-
+ let schema_type = store_value(schema_type.unwrap()); + let allow_remove = !disabled + && !mandatory_dimensions.get_value().contains(&condition.dimension); + let input_type = store_value( + InputType::from(( + schema_type.get_value(), + enum_variants.unwrap(), + condition.operator.clone(), + )), + ); + logging::log!("here {:?} {:?}", input_type.get_value(), condition.operator); + let condition = store_value(condition); + let on_remove = move |d_name| on_remove.call((idx, d_name)); + let on_value_change = move |(operand_idx, value)| { + on_value_change.call((idx, operand_idx, value)) + }; + let on_operator_change = move |operator| { + on_operator_change + .call(( + idx, + condition.with_value(|v| v.dimension.clone()), + schema_type.get_value(), + operator, + )) + }; + view! { + // TODO: get rid of unwraps here - {move || { - if last_idx.get() != idx { - view! { -
- "&&" -
- } - .into_view() - } else { - view! {}.into_view() - } - }} - } + + {move || { + if last_idx.get() != idx { + view! { +
+ "&&" +
} + .into_view() + } else { + view! {}.into_view() + } + }} + }.into_view() + } + /> + + +
+ + {move || { + let dimensions = dimensions + .get_value() + .into_iter() + .filter(|dimension| { + !used_dimensions_rs.get().contains(&dimension.dimension) + }) + .collect::>(); + view! { + } }} - -
- - {move || { - let dimensions = dimensions - .get_value() - .into_iter() - .filter(|dimension| { - !used_dimensions.get().contains(&dimension.dimension) - }) - .collect::>(); - view! { - - } - }} - -
-
-
-
+ +
- -
- -
-
- } +
+ } } diff --git a/crates/frontend/src/components/context_form/utils.rs b/crates/frontend/src/components/context_form/utils.rs index bc11c421..02e31bc4 100644 --- a/crates/frontend/src/components/context_form/utils.rs +++ b/crates/frontend/src/components/context_form/utils.rs @@ -1,240 +1,27 @@ -use crate::{ - components::condition_pills::types::{Condition, ConditionOperator}, - types::Dimension, - utils::{ - construct_request_headers, get_config_value, get_host, parse_json_response, - request, ConfigType, - }, -}; +use super::Conditions; +use crate::utils::{construct_request_headers, get_host, parse_json_response, request}; + use anyhow::Result; -use leptos::logging; use serde_json::{json, Map, Value}; -pub fn get_condition_schema( - condition: &Condition, - dimensions: Vec, -) -> Result { - let var = &condition.left_operand; // Dimension name - let op = &condition.operator; // Operator type - let val = &condition.right_operand; // Vec - - // Extract non-"var" elements from the right_operand - let filtered_values: Vec<&Value> = val - .iter() - .filter(|v| !v.is_object() || !v.get("var").is_some()) // Ignore objects with "var" - .collect(); - let dimensions_clone = dimensions.clone(); - - match op { - ConditionOperator::Between => { - // Expecting three elements for "Between" condition: two operands and one "var" object - if filtered_values.len() != 2 { - return Err( - "Invalid number of operands for 'between' condition.".to_string() - ); - } - - let first_operand = &filtered_values[0]; // The first value - let third_operand = &filtered_values[1]; // The third value - - let first_operand_value = get_config_value( - var, - first_operand, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - - let third_operand_value = get_config_value( - var, - third_operand, - &dimensions_clone - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - - Ok(json!({ - "<=": [ - first_operand_value, - { "var": var }, - third_operand_value - ] - })) - } - ConditionOperator::Is => { - // Expecting two elements for "Is" condition: one "var" object and one value - if filtered_values.len() != 1 { - return Err("Invalid number of operands for 'is' condition.".to_string()); - } - - let value = &filtered_values[0]; // The value after "var" - let first_operand_value = get_config_value( - var, - value, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - - Ok(json!({ - "==": [ - { "var": var }, - first_operand_value - ] - })) - } - ConditionOperator::In => { - if filtered_values.len() != 1 { - return Err("Invalid number of operands for 'in' condition.".to_string()); - } - let value = &filtered_values[0]; // The value after "var" - let first_operand_value = get_config_value( - var, - value, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - - Ok(json!({ - "in": [ - { "var": var }, - first_operand_value - ] - })) - } - ConditionOperator::Has => { - if filtered_values.len() != 1 { - return Err("Invalid number of operands for 'has' condition.".to_string()); - } - let value = &filtered_values[0]; // The value after "var" - let first_operand_value = get_config_value( - var, - value, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - - Ok(json!({ - "in": [ - first_operand_value, - { "var": var } - ] - })) - } - ConditionOperator::Other(op) => { - if filtered_values.len() == 1 { - let value = &filtered_values[0]; // The value after "var" - let first_operand_value = get_config_value( - var, - value, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - Ok(json!({ - op: [ - { "var": var }, - first_operand_value - ] - })) - } else if filtered_values.len() == 2 { - let first_operand = &filtered_values[0]; // The first value - let second_operand = &filtered_values[1]; // The second value - let first_operand_value = get_config_value( - var, - first_operand, - &dimensions - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - let second_operand_value = get_config_value( - var, - second_operand, - &dimensions_clone - .into_iter() - .map(ConfigType::Dimension) - .collect::>(), - )?; - Ok(json!({ - op: [ - first_operand_value, - { "var": var }, - second_operand_value - ] - })) - } else { - Err("Invalid number of operands for custom operator.".to_string()) - } - } - } -} - -pub fn construct_context( - conditions: Vec, - dimensions: Vec, -) -> Value { - if conditions.is_empty() { - json!({}) - } else { - let condition_schemas = conditions - .iter() - .map( - |condition| match get_condition_schema(condition, dimensions.clone()) { - Ok(ans) => ans, - Err(err) => { - logging::log!("Error while getting condition schema {:?}", err); - Value::Null - } - }, - ) - .collect::>(); - - if condition_schemas.len() == 1 { - condition_schemas[0].clone() - } else { - json!({ "and": condition_schemas }) - } - } -} - -pub fn construct_request_payload( - overrides: Map, - conditions: Vec, - dimensions: Vec, -) -> Value { - // Construct the override section - let override_section: Map = overrides; - - // Construct the context section - let context_section = construct_context(conditions, dimensions.clone()); - - // Construct the entire request payload - let request_payload = json!({ - "override": override_section, - "context": context_section +pub fn context_payload(overrides: Map, conditions: Conditions) -> Value { + let context: Value = conditions.to_context_json(); + let payload = json!({ + "override": overrides, + "context": context }); - request_payload + payload } pub async fn create_context( tenant: String, overrides: Map, - conditions: Vec, - dimensions: Vec, -) -> Result { + conditions: Conditions, +) -> Result { let host = get_host(); let url = format!("{host}/context"); - let request_payload = construct_request_payload(overrides, conditions, dimensions); + let request_payload = context_payload(overrides, conditions); let response = request( url, reqwest::Method::PUT, @@ -249,13 +36,11 @@ pub async fn create_context( pub async fn update_context( tenant: String, overrides: Map, - conditions: Vec, - dimensions: Vec, + conditions: Conditions, ) -> Result { let host = get_host(); let url = format!("{host}/context/overrides"); - let request_payload = - construct_request_payload(overrides, conditions, dimensions.clone()); + let request_payload = context_payload(overrides, conditions); let response = request( url, reqwest::Method::PUT, diff --git a/crates/frontend/src/components/experiment.rs b/crates/frontend/src/components/experiment.rs index f61db1f8..51c90b6d 100644 --- a/crates/frontend/src/components/experiment.rs +++ b/crates/frontend/src/components/experiment.rs @@ -2,8 +2,10 @@ use leptos::*; use serde_json::{Map, Value}; use std::collections::HashMap; -use crate::components::table::Table; -use crate::schema::HtmlDisplay; +use crate::{components::table::Table, schema::HtmlDisplay}; + +use crate::logic::{Condition, Operand}; + use crate::types::{Experiment, ExperimentStatusType}; use crate::{ components::table::types::Column, @@ -178,35 +180,34 @@ where

Context

- {move || { - let mut view = Vec::new(); - for token in contexts.clone() { - let (dimension, values) = (token.left_operand, token.right_operand); - let mut value_views = Vec::new(); - for value in values.iter() { - if value.is_object() && value.get("var").is_some() { - continue; - } - value_views - .push( - view! { -
- {value.html_display()} -
- }, - ); + + {contexts + .iter() + .map(|condition| { + let Condition { dimension, operands, .. } = condition; + let operand_views = operands + .iter() + .filter_map(|op| { + match op { + Operand::Dimension(_) => None, + Operand::Value(v) => { + Some( + view! { +
{v.html_display()}
+ }, + ) + } + } + }) + .collect_view(); + view! { +
+
{dimension}
+ {operand_views} +
} - view.push( - view! { -
-
{dimension}
- {value_views} -
- }, - ); - } - view - }} + }) + .collect_view()}
diff --git a/crates/frontend/src/components/experiment_conclude_form/utils.rs b/crates/frontend/src/components/experiment_conclude_form/utils.rs index b73829c9..7113a55a 100644 --- a/crates/frontend/src/components/experiment_conclude_form/utils.rs +++ b/crates/frontend/src/components/experiment_conclude_form/utils.rs @@ -1,13 +1,13 @@ use leptos::logging::log; use serde_json::json; -use crate::{types::Experiment, utils::get_host}; +use crate::{types::ExperimentResponse, utils::get_host}; pub async fn conclude_experiment( exp_id: String, variant_id: String, tenant: &String, -) -> Result { +) -> Result { let client = reqwest::Client::new(); let host = get_host(); match client @@ -20,7 +20,7 @@ pub async fn conclude_experiment( Ok(experiment) => { log!("experiment response {:?}", experiment); Ok(experiment - .json::() + .json::() .await .map_err(|err| err.to_string())?) } diff --git a/crates/frontend/src/components/experiment_form.rs b/crates/frontend/src/components/experiment_form.rs index b85f9973..e042660a 100644 --- a/crates/frontend/src/components/experiment_form.rs +++ b/crates/frontend/src/components/experiment_form.rs @@ -9,7 +9,7 @@ use crate::types::{DefaultConfig, Dimension, VariantFormT, VariantType}; use leptos::*; use web_sys::MouseEvent; -use super::condition_pills::types::Condition; +use crate::logic::Conditions; fn default_variants_for_form() -> Vec<(String, VariantFormT)> { vec![ @@ -50,7 +50,7 @@ pub fn experiment_form( #[prop(default = false)] edit: bool, #[prop(default = String::new())] id: String, name: String, - context: Vec, + context: Conditions, variants: Vec, handle_submit: NF, default_config: Vec, @@ -68,7 +68,7 @@ where let (f_variants, set_variants) = create_signal(init_variants); let (req_inprogess_rs, req_inprogress_ws) = create_signal(false); - let handle_context_form_change = move |updated_ctx: Vec| { + let handle_context_form_change = move |updated_ctx: Conditions| { set_context.set_untracked(updated_ctx); }; @@ -103,14 +103,8 @@ where let result = if edit { update_experiment(experiment_id, f_variants, tenant).await } else { - create_experiment( - f_context, - f_variants, - f_experiment_name, - tenant, - dimensions.get_value().clone(), - ) - .await + create_experiment(f_context, f_variants, f_experiment_name, tenant) + .await }; match result { @@ -152,11 +146,9 @@ where let context = f_context.get(); view! { dimensions=dimensions.get_value() context=context handle_change=handle_context_form_change - is_standalone=false disabled=edit heading_sub_text=String::from( "Define rules under which this experiment would run", diff --git a/crates/frontend/src/components/experiment_form/utils.rs b/crates/frontend/src/components/experiment_form/utils.rs index 431b85fa..06b44e9f 100644 --- a/crates/frontend/src/components/experiment_form/utils.rs +++ b/crates/frontend/src/components/experiment_form/utils.rs @@ -1,7 +1,6 @@ use super::types::{ExperimentCreateRequest, ExperimentUpdateRequest}; -use crate::components::condition_pills::types::Condition; -use crate::components::context_form::utils::construct_context; -use crate::types::{Dimension, VariantFormT}; +use crate::logic::Conditions; +use crate::types::VariantFormT; use crate::utils::{construct_request_headers, get_host, parse_json_response, request}; use serde_json::Value; @@ -13,16 +12,15 @@ pub fn validate_experiment(experiment: &ExperimentCreateRequest) -> Result, + conditions: Conditions, variants: Vec, name: String, tenant: String, - dimensions: Vec, ) -> Result { let payload = ExperimentCreateRequest { name, variants: FromIterator::from_iter(variants), - context: construct_context(conditions, dimensions.clone()), + context: conditions.to_context_json(), }; let _ = validate_experiment(&payload)?; diff --git a/crates/frontend/src/components/experiment_ramp_form/utils.rs b/crates/frontend/src/components/experiment_ramp_form/utils.rs index 7018ad61..9722c7be 100644 --- a/crates/frontend/src/components/experiment_ramp_form/utils.rs +++ b/crates/frontend/src/components/experiment_ramp_form/utils.rs @@ -1,13 +1,13 @@ use leptos::logging::log; use serde_json::json; -use crate::{types::Experiment, utils::get_host}; +use crate::{types::ExperimentResponse, utils::get_host}; pub async fn ramp_experiment( exp_id: &String, percent: u8, tenant: &String, -) -> Result { +) -> Result { let client = reqwest::Client::new(); let host = get_host(); match client @@ -20,7 +20,7 @@ pub async fn ramp_experiment( Ok(experiment) => { log!("experiment response {:?}", experiment); Ok(experiment - .json::() + .json::() .await .map_err(|err| err.to_string())?) } diff --git a/crates/frontend/src/components/override_form.rs b/crates/frontend/src/components/override_form.rs index be8fd336..3ee61885 100644 --- a/crates/frontend/src/components/override_form.rs +++ b/crates/frontend/src/components/override_form.rs @@ -10,7 +10,6 @@ use crate::{ }; use leptos::*; use serde_json::Value; -use web_sys::MouseEvent; #[component] fn type_badge(r#type: Option) -> impl IntoView { @@ -112,7 +111,6 @@ pub fn override_form( default_config: Vec, handle_change: NF, #[prop(into, default=String::new())] id: String, - #[prop(default = false)] is_standalone: bool, #[prop(default = false)] disable_remove: bool, #[prop(default = true)] show_add_override: bool, #[prop(into, default = None)] handle_key_remove: Option>, @@ -133,11 +131,6 @@ where .map(|ele| (ele.clone().key, ele)) .collect(); - let on_submit = move |event: MouseEvent| { - event.prevent_default(); - logging::log!("{:?}", overrides.get()); - }; - let handle_config_key_select = Callback::new(move |default_config: DefaultConfig| { let config_key = default_config.key; @@ -279,13 +272,6 @@ where
- -
- -
-
} } diff --git a/crates/frontend/src/components/variant_form.rs b/crates/frontend/src/components/variant_form.rs index d40ad749..c954b8b9 100644 --- a/crates/frontend/src/components/variant_form.rs +++ b/crates/frontend/src/components/variant_form.rs @@ -294,7 +294,6 @@ where overrides=overrides default_config=default_config.get_value() handle_change=handle_change - is_standalone=false show_add_override=false handle_key_remove=Some(Callback::new(on_key_remove)) /> @@ -306,7 +305,6 @@ where overrides=overrides default_config=default_config.get_value() handle_change=handle_change - is_standalone=false show_add_override=false disable_remove=true /> diff --git a/crates/frontend/src/pages/context_override.rs b/crates/frontend/src/pages/context_override.rs index 1e429534..cd3107a6 100644 --- a/crates/frontend/src/pages/context_override.rs +++ b/crates/frontend/src/pages/context_override.rs @@ -8,8 +8,6 @@ use crate::api::fetch_config; use crate::api::{delete_context, fetch_default_config, fetch_dimensions}; use crate::components::alert::AlertType; use crate::components::button::Button; -use crate::components::condition_pills::types::{Condition, ConditionOperator}; -use crate::components::condition_pills::utils::extract_conditions; use crate::components::context_card::ContextCard; use crate::components::context_form::utils::{create_context, update_context}; use crate::components::context_form::ContextForm; @@ -17,14 +15,16 @@ use crate::components::delete_modal::DeleteModal; use crate::components::drawer::{close_drawer, open_drawer, Drawer, DrawerBtn}; use crate::components::override_form::OverrideForm; use crate::components::skeleton::{Skeleton, SkeletonVariant}; +use crate::logic::{Condition, Conditions, Operator}; use crate::providers::alert_provider::enqueue_alert; use crate::providers::condition_collapse_provider::ConditionCollapseProvider; use crate::providers::editor_provider::EditorProvider; +use crate::schema::SchemaType; use crate::types::{DefaultConfig, Dimension, ListFilters, PaginatedResponse}; #[derive(Clone, Debug, Default)] pub struct Data { - pub context: Vec, + pub context: Conditions, pub overrides: Vec<(String, Value)>, } @@ -43,7 +43,7 @@ enum FormMode { #[component] fn form( - context: Vec, + context: Conditions, overrides: Vec<(String, Value)>, dimensions: Vec, edit: bool, @@ -61,13 +61,11 @@ fn form( spawn_local(async move { let f_context = context.get(); let f_overrides = overrides.get(); - let dimensions = dimensions.get_value().clone(); let result = if edit { update_context( tenant_rs.get().clone(), Map::from_iter(f_overrides), f_context, - dimensions.clone(), ) .await } else { @@ -75,7 +73,6 @@ fn form( tenant_rs.get().clone(), Map::from_iter(f_overrides), f_context, - dimensions.clone(), ) .await }; @@ -96,7 +93,6 @@ fn form( impl IntoView { let tenant_rs = use_context::>().unwrap(); - let (selected_data, set_selected_data) = create_signal::>(None); + let (selected_context_rs, selected_context_ws) = create_signal::>(None); let (form_mode, set_form_mode) = create_signal::>(None); let (modal_visible, set_modal_visible) = create_signal(false); let (delete_id, set_delete_id) = create_signal::>(None); @@ -172,71 +167,78 @@ pub fn context_override() -> impl IntoView { }, ); - let handle_context_create = Callback::new(move |_| { + let on_create_context_click = Callback::new(move |_| { set_form_mode.set(Some(FormMode::Create)); let PageResource { dimensions, .. } = page_resource.get().unwrap_or_default(); - let context_with_mandatory_dimensions = dimensions - .into_iter() - .filter_map(|dim| { - if dim.mandatory { - Some(Condition { - left_operand: dim.dimension, - operator: ConditionOperator::Is, - right_operand: vec![Value::from("")], - }) - } else { - None - } - }) - .collect::>(); - set_selected_data.set(Some(Data { - context: context_with_mandatory_dimensions, + let mut default_ctx: Conditions = Conditions(vec![]); + for dim in dimensions.iter().filter(|v| v.mandatory) { + let r#type = SchemaType::try_from(dim.schema.clone()); + if let Err(_) = r#type { + //TODO emit an alert and return + return; + } + + let condition = Condition::try_from(( + Operator::Is, + dim.dimension.clone(), + r#type.unwrap(), + )); + if let Err(_) = condition { + //TODO emit and alert and return + return; + } + + default_ctx.push(condition.unwrap()); + } + + selected_context_ws.set(Some(Data { + context: default_ctx, overrides: vec![], })); open_drawer("context_and_override_drawer"); }); - let handle_submit = Callback::new(move |_| { + let on_submit = Callback::new(move |_| { close_drawer("context_and_override_drawer"); set_form_mode.set(None); - set_selected_data.set(None); + selected_context_ws.set(None); page_resource.refetch(); }); - let handle_context_edit = - Callback::new(move |data: (Context, Map)| { - let (context, overrides) = data; - let conditions = extract_conditions(&Value::Object(context.condition.into())); + let on_context_edit = Callback::new(move |data: (Context, Map)| { + let (context, overrides) = data; + let conditions = + Conditions::from_context_json(&context.condition.into()).unwrap(); - set_selected_data.set(Some(Data { - context: conditions, - overrides: overrides.into_iter().collect::>(), - })); - set_form_mode.set(Some(FormMode::Edit)); + selected_context_ws.set(Some(Data { + context: conditions, + overrides: overrides.into_iter().collect::>(), + })); + set_form_mode.set(Some(FormMode::Edit)); - open_drawer("context_and_override_drawer"); - }); + open_drawer("context_and_override_drawer"); + }); - let handle_context_clone = - Callback::new(move |data: (Context, Map)| { - let (context, overrides) = data; - let conditions = extract_conditions(&Value::Object(context.condition.into())); + let on_context_clone = Callback::new(move |data: (Context, Map)| { + let (context, overrides) = data; + let conditions = + Conditions::from_context_json(&context.condition.into()).unwrap(); - set_selected_data.set(Some(Data { - context: conditions, - overrides: overrides.into_iter().collect::>(), - })); - set_form_mode.set(Some(FormMode::Create)); + selected_context_ws.set(Some(Data { + context: conditions, + overrides: overrides.into_iter().collect::>(), + })); + set_form_mode.set(Some(FormMode::Create)); - open_drawer("context_and_override_drawer"); - }); + open_drawer("context_and_override_drawer"); + }); - let handle_context_delete = Callback::new(move |id: String| { + let on_context_delete = Callback::new(move |id: String| { set_delete_id.set(Some(id.clone())); set_modal_visible.set(true); }); - let confirm_delete = Callback::new(move |_| { + let on_delete_confirm = Callback::new(move |_| { if let Some(id) = delete_id.get().clone() { spawn_local(async move { let result = delete_context(tenant_rs.get(), id).await; @@ -262,7 +264,7 @@ pub fn context_override() -> impl IntoView {

Overrides

Create Override @@ -278,7 +280,7 @@ pub fn context_override() -> impl IntoView { let PageResource { config: _, dimensions, default_config } = page_resource .get() .unwrap_or_default(); - let data = selected_data.get(); + let data = selected_context_rs.get(); let drawer_header = match form_mode.get() { Some(FormMode::Edit) => "Update Overrides", Some(FormMode::Create) => "Create Overrides", @@ -291,7 +293,7 @@ pub fn context_override() -> impl IntoView { handle_close=move || { close_drawer("context_and_override_drawer"); set_form_mode.set(None); - set_selected_data.set(None); + selected_context_ws.set(None); } > @@ -304,7 +306,7 @@ pub fn context_override() -> impl IntoView { overrides=data.overrides dimensions=dimensions default_config=default_config - handle_submit=handle_submit + handle_submit=on_submit edit=true /> } @@ -318,7 +320,7 @@ pub fn context_override() -> impl IntoView { overrides=overrides dimensions=dimensions default_config=default_config - handle_submit=handle_submit + handle_submit=on_submit edit=false /> } @@ -382,9 +384,9 @@ pub fn context_override() -> impl IntoView { } }) @@ -398,7 +400,7 @@ pub fn context_override() -> impl IntoView { impl IntoView { Vec { @@ -100,17 +103,19 @@ pub fn experiment_table_columns() -> Vec { "context".to_string(), None, |_, row: &Map| { - let context = match row.get("context") { - Some(value) => value.to_owned(), - None => json!(""), - }; + let context = row + .get("context") + .and_then(|v| v.as_object().cloned()) + .unwrap_or(Map::new()); let id = row.get("id").map_or(String::from(""), |value| { value.as_str().unwrap_or("").to_string() }); + let conditions = + Conditions::from_context_json(&context).unwrap_or_default(); view! {
- +
} .into_view() diff --git a/crates/frontend/src/pages/home.rs b/crates/frontend/src/pages/home.rs index e2dbe09f..39f54277 100644 --- a/crates/frontend/src/pages/home.rs +++ b/crates/frontend/src/pages/home.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::time::Duration; use leptos::*; @@ -9,8 +8,8 @@ use superposition_types::Config; use wasm_bindgen::JsCast; use web_sys::{HtmlButtonElement, HtmlSpanElement, MouseEvent}; -use crate::components::condition_pills::types::Conditions; use crate::components::skeleton::{Skeleton, SkeletonVariant}; +use crate::logic::Conditions; use crate::providers::condition_collapse_provider::ConditionCollapseProvider; use crate::types::ListFilters; use crate::{ @@ -21,10 +20,7 @@ use crate::{ utils::{check_url_and_return_val, get_element_by_id, get_host}, }; use crate::{ - components::condition_pills::{ - types::{Condition, ConditionOperator}, - Condition as ConditionComponent, - }, + components::condition_pills::Condition as ConditionComponent, types::PaginatedResponse, }; @@ -68,14 +64,9 @@ fn all_context_view(config: Config) -> impl IntoView { overrides, default_configs, } = config; - let rows = |k: &String, v: &Value, striked: bool| { - let default_iter = vec![(k.clone(), v.clone())]; - v - .as_object() - .unwrap_or(&Map::from_iter(default_iter)) - .iter() + let rows = |m: &Map, striked: bool| { + m.iter() .map(|(key, value)| { - let key = key.replace('"', "").trim().to_string(); let value = value .as_str() .unwrap_or(value.to_string().trim_matches('"')) @@ -117,7 +108,7 @@ fn all_context_view(config: Config) -> impl IntoView { let rows: Vec<_> = context .override_with_keys .iter() - .filter_map(|key| overrides.get(key).map(|o| rows(key, &Value::Object(o.clone().into()), true))) + .filter_map(|key| overrides.get(key).map(|o| rows(o, true))) .collect(); let conditions: Conditions = context.try_into().unwrap_or_default(); view! { @@ -127,7 +118,7 @@ fn all_context_view(config: Config) -> impl IntoView {
@@ -162,10 +153,7 @@ fn all_context_view(config: Config) -> impl IntoView { - {default_configs - .iter() - .map(|(k, v)| rows(k, v, false)) - .collect::>()} + {rows(&default_configs, false)} @@ -201,7 +189,7 @@ pub fn home() -> impl IntoView { }, ); - let (context_rs, context_ws) = create_signal::>(vec![]); + let (context_rs, context_ws) = create_signal::(Conditions::default()); let (selected_tab_rs, selected_tab_ws) = create_signal(ResolveTab::AllConfig); let (req_inprogess_rs, req_inprogress_ws) = create_signal(false); @@ -259,42 +247,6 @@ pub fn home() -> impl IntoView { } }; - let gen_query_context = |query: Vec| -> String { - let mut context: Vec = vec![]; - for condition in query.iter() { - let dimension = condition.left_operand.clone(); - let op = match condition.operator.clone() { - ConditionOperator::Is => Cow::Borrowed("="), - ConditionOperator::In => Cow::Borrowed("IN"), - ConditionOperator::Has => Cow::Borrowed("HAS"), - ConditionOperator::Between => Cow::Borrowed("BETWEEN"), - ConditionOperator::Other(op) => Cow::Owned(op), - }; - let value = condition - .right_operand - .clone() - .into_iter() - .filter_map(|value| { - if value.is_object() && value.get("var").is_some() { - None - } else { - Some(value) - } - }) - .map(|value| match value { - Value::String(s) => s.clone(), - Value::Number(n) => n.to_string(), - Value::Bool(b) => b.to_string(), - Value::Null => String::from("null"), - _ => format!("{}", value), - }) - .collect::>() - .join(","); - context.push(format!("{}{op}{}", dimension, value)); - } - context.join("&").to_string() - }; - let resolve_click = move |ev: MouseEvent| { ev.prevent_default(); req_inprogress_ws.set(true); @@ -322,7 +274,7 @@ pub fn home() -> impl IntoView { let context_updated = context_rs.get(); // resolve the context and get the config that would apply spawn_local(async move { - let context = gen_query_context(context_updated); + let context = context_updated.to_query_string(); let mut config = match resolve_config(tenant_rs.get_untracked(), context) .await .unwrap() @@ -403,10 +355,9 @@ pub fn home() -> impl IntoView { .to_owned() .unwrap_or(PaginatedResponse::default()) .data - context=vec![] + context=Conditions::default() heading_sub_text="Query your configs".to_string() dropdown_direction=DropdownDirection::Right - is_standalone=false resolve_mode=true handle_change=move |new_context| { context_ws diff --git a/crates/frontend/src/types.rs b/crates/frontend/src/types.rs index c5816ddb..8aa8326c 100644 --- a/crates/frontend/src/types.rs +++ b/crates/frontend/src/types.rs @@ -6,10 +6,8 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use derive_more::{Deref, DerefMut}; use serde_json::{json, Map, Value}; -use crate::components::{ - condition_pills::{types::Condition, utils::extract_conditions}, - dropdown::utils::DropdownOption, -}; +use crate::components::dropdown::utils::DropdownOption; +use crate::logic::Conditions; #[derive(Clone, Debug)] pub struct AppRoute { @@ -193,7 +191,7 @@ pub struct Experiment { pub(crate) name: String, pub(crate) id: String, pub(crate) traffic_percentage: u8, - pub(crate) context: Vec, + pub(crate) context: Conditions, pub(crate) status: ExperimentStatusType, pub(crate) override_keys: Value, pub(crate) created_by: String, @@ -215,7 +213,10 @@ impl From for Experiment { last_modified: value.last_modified, chosen_variant: value.chosen_variant, variants: serde_json::from_value(value.variants).unwrap_or_default(), - context: extract_conditions(&value.context), + context: Conditions::from_context_json( + value.context.as_object().unwrap_or(&Map::new()), + ) + .unwrap_or(Conditions(vec![])), } } } diff --git a/crates/frontend/src/utils.rs b/crates/frontend/src/utils.rs index 35678e0e..5310e379 100644 --- a/crates/frontend/src/utils.rs +++ b/crates/frontend/src/utils.rs @@ -3,13 +3,12 @@ use std::env; use crate::{ components::alert::AlertType, providers::alert_provider::enqueue_alert, - types::{DefaultConfig, Dimension, Envs, ErrorResponse}, + types::{Envs, ErrorResponse}, }; use cfg_if::cfg_if; use leptos::*; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde::de::DeserializeOwned; -use serde_json::{Map, Value}; use std::str::FromStr; use url::Url; use wasm_bindgen::JsCast; @@ -194,170 +193,6 @@ pub fn check_url_and_return_val(s: String) -> String { } } -pub enum ConfigType { - // Default config is not actually dead code, it's used - // but for some reason the compiler thinks it's dead code - // this gets rid of the warning - #[allow(dead_code)] - DefaultConfig(DefaultConfig), - Dimension(Dimension), -} - -#[derive(Clone, Debug)] -pub enum ConfigValueType { - Boolean, - Number, - String, - Null, - Integer, - Other, -} - -impl FromStr for ConfigValueType { - type Err = String; - fn from_str(s: &str) -> Result { - match s { - "boolean" => Ok(ConfigValueType::Boolean), - "number" => Ok(ConfigValueType::Number), - "string" => Ok(ConfigValueType::String), - "null" => Ok(ConfigValueType::Null), - "integer" => Ok(ConfigValueType::Integer), - _ => Ok(ConfigValueType::Other), - } - } -} - -pub fn get_config_type( - configs: &[ConfigType], - key_name: &str, -) -> Option> { - let config = configs.iter().find(|conf| match conf { - ConfigType::DefaultConfig(default_conf) => default_conf.key == key_name, - ConfigType::Dimension(dimension) => dimension.dimension == key_name, - }); - - let type_from_str = |type_str: Option<&str>| { - type_str - .map(|t| ConfigValueType::from_str(t).ok()) - .flatten() - .unwrap_or(ConfigValueType::Other) - }; - - let types_mapping = |schema_type: &Value| match schema_type { - Value::Array(types) => types - .iter() - .map(|item: &Value| type_from_str(item.as_str())) - .collect::>(), - Value::String(type_str) => vec![type_from_str(Some(type_str.as_str()))], - _ => vec![ConfigValueType::Other], - }; - - config.and_then(|config| match config { - ConfigType::DefaultConfig(default_conf) => { - default_conf.schema.get("type").map(|t| types_mapping(t)) - } - - ConfigType::Dimension(dimension) => { - dimension.schema.get("type").map(|t| types_mapping(t)) - } - }) -} - -pub fn parse_value(val: &Value, config_type: ConfigValueType) -> Result { - match config_type { - ConfigValueType::Boolean => { - match val { - Value::Bool(_) => Ok(val.clone()), - - Value::String(s) => { - // Attempting to parse the string as a boolean - match s.to_lowercase().as_str() { - "true" => Ok(Value::Bool(true)), - "false" => Ok(Value::Bool(false)), - _ => Err(format!("Invalid boolean string: {:?}", s)), // Error if not a valid boolean string - } - } - - _ => Err(format!("Invalid boolean value: {:?}", val)), - } - } - - ConfigValueType::Number | ConfigValueType::Integer => { - match val { - Value::Number(num) => Ok(Value::Number(num.clone())), - - Value::String(s) => { - // Attempting to parse as integer first, then as float - if let Ok(int_val) = s.parse::() { - Ok(Value::Number(int_val.into())) - } else if let Ok(float_val) = s.parse::() { - Ok(Value::Number( - serde_json::Number::from_f64(float_val).unwrap(), - )) - } else { - Err(format!("Invalid number format: {:?}", s)) - } - } - - Value::Array(arr) => { - // Ensuring all items in the array are numbers - if arr.iter().all(|item| item.is_number()) { - Ok(val.clone()) - } else { - Err("Array contains non-number value".to_string()) - } - } - - _ => Err(format!( - "{:?} is neither a valid number nor an array of numbers.", - val - )), - } - } - - ConfigValueType::String => match val { - Value::String(_) => Ok(val.clone()), - Value::Number(i) => Ok(Value::String(i.to_string())), - Value::Bool(b) => Ok(Value::String(b.to_string())), - Value::Array(arr) => { - if arr.iter().all(|item| item.is_string()) { - Ok(val.clone()) - } else { - Err("Array contains non-string value".to_string()) - } - } - _ => Err(format!( - "{:?} is neither a valid string nor an array of strings.", - val - )), - }, - - ConfigValueType::Null if val.is_null() => Ok(Value::Null), - - _ => Ok(val.clone()), - } -} - -pub fn get_config_value( - name: &str, - val: &Value, - configs: &[ConfigType], -) -> Result { - let config_type = get_config_type(configs, name); - - match config_type { - Some(possible_types) => { - for possible_type in possible_types { - if let Ok(parsed_value) = parse_value(val, possible_type) { - return Ok(parsed_value); - } - } - Err("Error parsing config value".to_string()) - } - None => Ok(val.clone()), - } -} - /********* Request Utils **********/ use once_cell::sync::Lazy; @@ -486,17 +321,6 @@ pub fn set_local_storage(_key: &str, _value: &str) -> Option<()> { } } -pub fn get_key_type(schema: &Map) -> String { - if schema.contains_key("enum") { - String::from("ENUM") - } else { - match schema.get("type").unwrap_or(&Value::Null) { - Value::String(str_) => str_.to_ascii_uppercase(), - _ => String::from("STRING"), - } - } -} - pub fn update_page_direction( page: Option, total_pages: i64,