From 71868e633984bf4bac512fd97cd4669976b1a5d4 Mon Sep 17 00:00:00 2001 From: Shubhranshu Sanjeev Date: Wed, 4 Dec 2024 13:12:39 +0530 Subject: [PATCH] feat: JsonLogic algebraic representation --- crates/frontend/src/components/input.rs | 94 +++++++- crates/frontend/src/lib.rs | 1 + crates/frontend/src/logic.rs | 278 ++++++++++++++++++++++++ crates/frontend/src/schema.rs | 19 +- 4 files changed, 373 insertions(+), 19 deletions(-) create mode 100644 crates/frontend/src/logic.rs diff --git a/crates/frontend/src/components/input.rs b/crates/frontend/src/components/input.rs index 49a805c9..69edb8ec 100644 --- a/crates/frontend/src/components/input.rs +++ b/crates/frontend/src/components/input.rs @@ -12,16 +12,16 @@ use crate::{ schema::{EnumVariants, HtmlDisplay, JsonSchemaType, SchemaType}, }; +use crate::logic::Operator; + #[derive(Debug, Clone, PartialEq)] pub enum InputType { Text, Number, Integer, - Toggle, Monaco, Select(EnumVariants), - Disabled, } @@ -64,7 +64,30 @@ impl From<(SchemaType, EnumVariants)> for InputType { } } -fn str_to_value(s: &str, type_: &JsonSchemaType) -> Result { +impl From<(SchemaType, EnumVariants, Operator)> for InputType { + fn from( + (schema_type, enum_variants, operator): (SchemaType, EnumVariants, Operator), + ) -> Self { + if operator == Operator::In { + return InputType::Text; + } + + if !enum_variants.is_empty() { + return InputType::Select(enum_variants); + } + + match schema_type { + SchemaType::Single(JsonSchemaType::Number) => InputType::Number, + SchemaType::Single(JsonSchemaType::Integer) => InputType::Integer, + SchemaType::Single(JsonSchemaType::Boolean) => InputType::Toggle, + SchemaType::Single(JsonSchemaType::Null) => InputType::Disabled, + _ => InputType::Text, + } + } +} + +// TODO: Also add schema validation in frontend ::::: +fn parse(s: &str, type_: &JsonSchemaType) -> Result { match type_ { JsonSchemaType::String => Ok(Value::String(s.to_string())), JsonSchemaType::Number => s @@ -90,14 +113,55 @@ fn str_to_value(s: &str, type_: &JsonSchemaType) -> Result { } } -fn parse_input_value(value: String, schema_type: SchemaType) -> Result { +fn parse_with_operator( + s: &str, + type_: &JsonSchemaType, + op: &Operator, +) -> Result { + match op { + Operator::In => match type_ { + JsonSchemaType::String => serde_json::from_str::>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of strings".to_string()), + JsonSchemaType::Number => serde_json::from_str::>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of numbers".to_string()), + JsonSchemaType::Integer => serde_json::from_str::>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of integers".to_string()), + JsonSchemaType::Boolean => serde_json::from_str::>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of booleans".to_string()), + JsonSchemaType::Array => serde_json::from_str::>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of arrays".to_string()), + JsonSchemaType::Object => serde_json::from_str::>>(s) + .map(|v| json!(v)) + .map_err(|_| "not a valid array of objects".to_string()), + JsonSchemaType::Null if s == "null" => Ok(Value::Null), + JsonSchemaType::Null => Err("not a null value".to_string()), + }, + _ => parse(s, type_), + } +} + +fn parse_input( + value: String, + schema_type: SchemaType, + op: &Option, +) -> Result { + let parse_single = |r#type: &JsonSchemaType| match op { + Some(op) => parse_with_operator(&value, r#type, op), + None => parse(&value, r#type), + }; + match schema_type { - SchemaType::Single(ref type_) => str_to_value(&value, type_), + SchemaType::Single(ref r#type) => parse_single(r#type), SchemaType::Multiple(mut types) => { types.sort_by(|a, b| a.precedence().cmp(&b.precedence())); - for type_ in types.iter() { - let v = str_to_value(&value, type_); + for r#type in types.iter() { + let v = parse_single(r#type); if v.is_ok() { return v; } @@ -183,6 +247,7 @@ fn basic_input( value: Value, schema_type: SchemaType, on_change: Callback, + #[prop(default = None)] operator: Option, ) -> impl IntoView { let schema_type = store_value(schema_type); let (error_rs, error_ws) = create_signal::>(None); @@ -208,7 +273,7 @@ fn basic_input( value=value.html_display() on:change=move |e| { let v = event_target_value(&e); - match parse_input_value(v, schema_type.get_value()) { + match parse_input(v, schema_type.get_value(), &operator) { Ok(v) => { on_change.call(v); error_ws.set(None); @@ -244,6 +309,7 @@ pub fn monaco_input( value: Value, on_change: Callback, schema_type: SchemaType, + #[prop(default = None)] operator: Option, ) -> impl IntoView { let id = store_value(id); let schema_type = store_value(schema_type); @@ -273,7 +339,7 @@ pub fn monaco_input( logging::log!("Saving editor value: {}", editor_value); let parsed_value = - parse_input_value(editor_value.clone(), schema_type.get_value()); + parse_input(editor_value.clone(), schema_type.get_value(), &operator); match parsed_value { Ok(v) => { logging::log!("Saving parsed value: {}", editor_value); @@ -421,24 +487,25 @@ pub fn monaco_input( pub fn input( value: Value, schema_type: SchemaType, - on_change: Callback, + #[prop(into)] on_change: Callback, #[prop(into)] r#type: InputType, #[prop(default = false)] disabled: bool, #[prop(into, default = String::new())] id: String, #[prop(into, default = String::new())] class: String, #[prop(into, default = String::new())] name: String, + #[prop(default = None)] operator: Option, ) -> impl IntoView { match r#type { InputType::Toggle => match value.as_bool() { Some(ref v) => { view! { }.into_view() } - None => view! { An error occured }.into_view(), + None => view! { }.into_view(), }, InputType::Select(ref options) => view! {