compiled, needs further progress
This commit is contained in:
@@ -1 +1,4 @@
|
|||||||
// src/steel/handlers.rs
|
// src/steel/handlers.rs
|
||||||
|
pub mod execution;
|
||||||
|
|
||||||
|
pub use execution::*;
|
||||||
|
|||||||
66
server/src/steel/handlers/execution.rs
Normal file
66
server/src/steel/handlers/execution.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// src/steel/execution.rs
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ScriptOperation {
|
||||||
|
SetToColumn { source: String },
|
||||||
|
// Future operations can be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ScriptExecutionError {
|
||||||
|
ParseError(String),
|
||||||
|
MissingSourceColumn(String),
|
||||||
|
Mismatch { expected: String, actual: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ScriptExecutionError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
|
||||||
|
Self::MissingSourceColumn(col) => write!(f, "Missing source column: {}", col),
|
||||||
|
Self::Mismatch { expected, actual } => write!(
|
||||||
|
f,
|
||||||
|
"Value does not match script expectation. Expected: {}, Actual: {}",
|
||||||
|
expected, actual
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_script(script: &str, expected_target: &str) -> Result<ScriptOperation, ScriptExecutionError> {
|
||||||
|
let script = script.trim();
|
||||||
|
|
||||||
|
if !script.starts_with("(set! ") || !script.ends_with(')') {
|
||||||
|
return Err(ScriptExecutionError::ParseError(
|
||||||
|
"Script must be in the form (set! target source)".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = &script[6..script.len() - 1].trim();
|
||||||
|
let mut parts = content.split_whitespace();
|
||||||
|
|
||||||
|
let target = parts.next().ok_or_else(|| {
|
||||||
|
ScriptExecutionError::ParseError("Missing target in set! expression".to_string())
|
||||||
|
})?;
|
||||||
|
let source = parts.next().ok_or_else(|| {
|
||||||
|
ScriptExecutionError::ParseError("Missing source in set! expression".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if parts.next().is_some() {
|
||||||
|
return Err(ScriptExecutionError::ParseError(
|
||||||
|
"Too many arguments in set! expression".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if target != expected_target {
|
||||||
|
return Err(ScriptExecutionError::ParseError(format!(
|
||||||
|
"Script target '{}' does not match expected '{}'",
|
||||||
|
target, expected_target
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ScriptOperation::SetToColumn {
|
||||||
|
source: source.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScript
|
|||||||
use crate::steel::validation::script::validate_script;
|
use crate::steel::validation::script::validate_script;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
// Add these imports for the execution module and ScriptOperation
|
||||||
|
use crate::steel::handlers::execution;
|
||||||
|
use crate::steel::handlers::ScriptOperation;
|
||||||
|
|
||||||
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"];
|
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"];
|
||||||
|
|
||||||
fn validate_target_column(
|
fn validate_target_column(
|
||||||
@@ -51,6 +55,15 @@ pub async fn post_table_script(
|
|||||||
.map_err(|e| Status::internal(format!("Database error: {}", e)))?
|
.map_err(|e| Status::internal(format!("Database error: {}", e)))?
|
||||||
.ok_or_else(|| Status::not_found("Table definition not found"))?;
|
.ok_or_else(|| Status::not_found("Table definition not found"))?;
|
||||||
|
|
||||||
|
// Use the full path to parse_script
|
||||||
|
let operation = execution::parse_script(&request.script, &request.target_column)
|
||||||
|
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
||||||
|
|
||||||
|
// Ensure the operation is valid (additional checks if needed)
|
||||||
|
match operation {
|
||||||
|
ScriptOperation::SetToColumn { .. } => {}, // Use directly without 'execution::'
|
||||||
|
}
|
||||||
|
|
||||||
// Call validation functions
|
// Call validation functions
|
||||||
validate_script(&request.script)
|
validate_script(&request.script)
|
||||||
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use sqlx::{PgPool, Arguments};
|
|||||||
use sqlx::postgres::PgArguments;
|
use sqlx::postgres::PgArguments;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
||||||
|
use crate::steel::handlers::execution::{self, ScriptOperation};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub async fn post_table_data(
|
pub async fn post_table_data(
|
||||||
@@ -100,6 +101,56 @@ pub async fn post_table_data(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Validate Steel scripts
|
||||||
|
let scripts = sqlx::query!(
|
||||||
|
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
|
||||||
|
table_def.id
|
||||||
|
)
|
||||||
|
.fetch_all(db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;
|
||||||
|
|
||||||
|
for script_record in scripts {
|
||||||
|
let target_column = script_record.target_column;
|
||||||
|
|
||||||
|
// Check if target column is present in data
|
||||||
|
if !data.contains_key(&target_column) {
|
||||||
|
return Err(Status::invalid_argument(
|
||||||
|
format!("Column '{}' is required due to an associated script", target_column)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the script
|
||||||
|
let operation = execution::parse_script(&script_record.script, &target_column)
|
||||||
|
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
||||||
|
|
||||||
|
// Get source column from operation
|
||||||
|
let source_column = match operation {
|
||||||
|
ScriptOperation::SetToColumn { source } => source,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check source column presence
|
||||||
|
let source_value = data.get(&source_column)
|
||||||
|
.ok_or_else(|| Status::invalid_argument(
|
||||||
|
format!("Source column '{}' required by script for '{}' is missing", source_column, target_column)
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Get target value
|
||||||
|
let target_value = data.get(&target_column)
|
||||||
|
.ok_or_else(|| Status::invalid_argument(
|
||||||
|
format!("Target column '{}' is missing in data", target_column)
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Validate value match
|
||||||
|
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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare SQL parameters
|
// Prepare SQL parameters
|
||||||
let mut params = PgArguments::default();
|
let mut params = PgArguments::default();
|
||||||
let mut columns_list = Vec::new();
|
let mut columns_list = Vec::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user