diff --git a/server/Cargo.toml b/server/Cargo.toml index d643cfb..73cce9b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,6 +20,7 @@ tracing = "0.1.41" time = { version = "0.3.39", features = ["local-offset"] } steel-derive = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-derive" } steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0", features = ["anyhow", "dylibs", "sync"] } +thiserror = "2.0.12" [lib] name = "server" diff --git a/server/src/steel/server/execution.rs b/server/src/steel/server/execution.rs index f8fd1d2..3852581 100644 --- a/server/src/steel/server/execution.rs +++ b/server/src/steel/server/execution.rs @@ -1,4 +1,49 @@ // src/steel/server/execution.rs -use std::fmt; -use std::collections::HashMap; -use sqlx::{PgPool, Row}; +use steel::steel_vm::engine::Engine; +use steel::rvals::SteelVal; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ExecutionError { + #[error("Script execution failed: {0}")] + RuntimeError(String), + #[error("Type conversion error: {0}")] + TypeConversionError(String), + #[error("Unsupported target type: {0}")] + UnsupportedType(String), +} + +#[derive(Debug)] +pub enum Value { + String(String), +} + +// TODO LEAKING MEMORY NEEDS IMIDATE FIX BEFORE PROD +pub fn execute_script( + script: &str, + target_type: &str, +) -> Result { + let mut vm = Engine::new(); + + // Convert to Box then leak to get 'static lifetime + let static_script: &'static str = Box::leak(script.to_string().into_boxed_str()); + + let results = vm.compile_and_run_raw_program(static_script) + .map_err(|e| ExecutionError::RuntimeError(e.to_string()))?; + + let last_result = results.last() + .ok_or_else(|| ExecutionError::TypeConversionError("Script returned no values".to_string()))?; + + match target_type { + "STRING" => { + if let SteelVal::StringV(s) = last_result { + Ok(Value::String(s.to_string())) + } else { + Err(ExecutionError::TypeConversionError( + format!("Expected string, got {:?}", last_result) + )) + } + } + _ => Err(ExecutionError::UnsupportedType(target_type.to_string())), + } +} diff --git a/server/src/steel/server/mod.rs b/server/src/steel/server/mod.rs index 09f4648..6f71416 100644 --- a/server/src/steel/server/mod.rs +++ b/server/src/steel/server/mod.rs @@ -1,4 +1,4 @@ // src/steel/server/mod.rs -// pub mod execution; +pub mod execution; -// pub use execution::*; +pub use execution::*; diff --git a/server/src/tables_data/handlers/post_table_data.rs b/server/src/tables_data/handlers/post_table_data.rs index 0183321..0421f73 100644 --- a/server/src/tables_data/handlers/post_table_data.rs +++ b/server/src/tables_data/handlers/post_table_data.rs @@ -6,6 +6,8 @@ use chrono::{DateTime, Utc}; use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse}; use std::collections::HashMap; +use crate::steel::server::execution::{self, Value}; + pub async fn post_table_data( db_pool: &PgPool, request: PostTableDataRequest, @@ -112,54 +114,31 @@ pub async fn post_table_data( .await .map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?; - // TODO: Re-implement script validation logic - /* + // TODO SCRIPT EXECUTION NEEDS REDESING for script_record in scripts { let target_column = script_record.target_column; - - if !data.contains_key(&target_column) { - return Err(Status::invalid_argument( - format!("Column '{}' is required due to an associated script", target_column) - )); - } - - let operation = execution::parse_script(&script_record.script, &target_column) - .map_err(|e| Status::invalid_argument(e.to_string()))?; - - let source_column = match operation { - ScriptOperation::SetToLocalColumn { source } => source, - ScriptOperation::SetToExternalColumn { table, column } => { - let external_source = format!("@{}.{}", table, column); - let resolved_value = execution::resolve_value( - db_pool, - profile_id, - &table_name, - &data, - &external_source - ).await.map_err(|e| Status::invalid_argument(e.to_string()))?; - - resolved_value - } - }; - - let source_value = data.get(&source_column) + + // Ensure target column exists in submitted data + let user_value = data.get(&target_column) .ok_or_else(|| Status::invalid_argument( - format!("Source column '{}' required by script for '{}' is missing", source_column, target_column) + format!("Script target column '{}' is required", target_column) ))?; - let target_value = data.get(&target_column) - .ok_or_else(|| Status::invalid_argument( - format!("Target column '{}' is missing in data", target_column) + // Execute the script using your existing implementation + let script_result = execution::execute_script(&script_record.script, "STRING") + .map_err(|e| Status::invalid_argument( + format!("Script execution failed for '{}': {}", target_column, e) ))?; - if target_value != source_value { - return Err(Status::invalid_argument( - format!("Value for '{}' must match '{}' as per script. Expected '{}', got '{}'", - target_column, source_column, source_value, target_value) - )); - } + // Compare script result with user input + let Value::String(expected_value) = script_result; + if user_value != &expected_value { + return Err(Status::invalid_argument(format!( + "Validation failed for '{}'. Expected: '{}', Received: '{}'", + target_column, expected_value, user_value + ))); + } } - */ // Prepare SQL parameters let mut params = PgArguments::default();