steel validation in progress

This commit is contained in:
filipriec
2025-03-08 10:20:08 +01:00
parent 66c29d8d65
commit 53ab9ad7d7
8 changed files with 40 additions and 71 deletions

1
Cargo.lock generated
View File

@@ -2363,6 +2363,7 @@ dependencies = [
"serde_json", "serde_json",
"sqlx", "sqlx",
"steel-core", "steel-core",
"steel-derive",
"time", "time",
"tokio", "tokio",
"tonic", "tonic",

View File

@@ -19,6 +19,7 @@ tonic-reflection = "0.12.3"
tracing = "0.1.41" tracing = "0.1.41"
time = { version = "0.3.39", features = ["local-offset"] } time = { version = "0.3.39", features = ["local-offset"] }
steel-core = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-core" } steel-core = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-core" }
steel-derive = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-derive" }
[lib] [lib]
name = "server" name = "server"

View File

@@ -1,6 +1 @@
// src/steel/handlers.rs // src/steel/handlers.rs
pub mod evaluator;
pub mod engine;
pub use engine::validate_steel_script;
pub use evaluator::validate_target_column;

View File

@@ -1,34 +0,0 @@
// src/steel/handlers/engine.rs
use steel_core::{steel_vm::engine::Engine, rvals::SteelVal};
use tonic::Status;
pub fn validate_steel_script(script: &str) -> Result<(), Status> {
let mut engine = Engine::new();
// Basic syntax check
let parsed = engine.compile(script).map_err(|e| {
Status::invalid_argument(format!("Syntax error: {}", e))
})?;
// Validate required function signature
let has_transform = parsed.iter().any(|expr| match expr {
SteelVal::Func(f) => f.name() == Some("transform"),
_ => false,
});
if !has_transform {
return Err(Status::invalid_argument(
"Script must contain a 'transform' function"
));
}
// Simple sandboxed execution test
let test_input = SteelVal::StringV("test_data".into());
engine.call_function("transform", vec![test_input])
.map_err(|e| {
Status::invalid_argument(format!("Runtime validation failed: {}", e))
})?;
Ok(())
}

View File

@@ -1,24 +0,0 @@
// src/steel/handlers/evaluator.rs
use crate::validation::script::validate_script;
use serde_json::Value;
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "firma", "created_at"];
pub fn validate_target_column(
table_name: &str,
target: &str,
table_columns: &Value,
) -> Result<(), String> {
if SYSTEM_COLUMNS.contains(&target) {
return Err(format!("Cannot override system column: {}", target));
}
let columns: Vec<String> = serde_json::from_value(table_columns.clone())
.map_err(|e| format!("Invalid column data: {}", e))?;
if !columns.iter().any(|c| c == target) {
return Err(format!("Target column {} not defined in table {}", target, table_name));
}
Ok(())
}

View File

@@ -0,0 +1,2 @@
// src/steel/validation.rs
pub mod script;

View File

@@ -1,30 +1,37 @@
// src/steel/validation/script.rs // src/steel/validation/script.rs
use steel_core::steel_vm::engine::Engine;
use std::fmt; use std::fmt;
use super::handlers::engine::validate_steel_script;
use tonic::Status;
#[derive(Debug)] #[derive(Debug)]
pub enum ScriptValidationError { pub enum ScriptValidationError {
EmptyScript, EmptyScript,
SteelValidation(String), InvalidSyntax(String),
MissingTransformFunction,
} }
impl fmt::Display for ScriptValidationError { impl fmt::Display for ScriptValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ScriptValidationError::EmptyScript => write!(f, "Script cannot be empty"), Self::EmptyScript => write!(f, "Script cannot be empty"),
ScriptValidationError::SteelValidation(msg) => write!(f, "Steel validation error: {}", msg), Self::InvalidSyntax(msg) => write!(f, "Syntax error: {}", msg),
Self::MissingTransformFunction => write!(f, "Script must define a 'transform' function"),
} }
} }
} }
pub fn validate_script(script: &str) -> Result<(), ScriptValidationError> { pub fn validate_script(script: &str) -> Result<(), ScriptValidationError> {
// Check for empty script
if script.trim().is_empty() { if script.trim().is_empty() {
return Err(ScriptValidationError::EmptyScript); return Err(ScriptValidationError::EmptyScript);
} }
validate_steel_script(script) // Create a new Steel engine
.map_err(|e| ScriptValidationError::SteelValidation(e.message().to_string()))?; let mut engine = Engine::new();
// Check for the presence of a 'transform' function
if engine.extract_value("transform").is_err() {
return Err(ScriptValidationError::MissingTransformFunction);
}
Ok(()) Ok(())
} }

View File

@@ -2,8 +2,29 @@
use tonic::Status; use tonic::Status;
use sqlx::{PgPool, Error as SqlxError}; use sqlx::{PgPool, Error as SqlxError};
use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse}; use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse};
use crate::steel::handlers::evaluator::validate_target_column;
use crate::steel::validation::script::validate_script; use crate::steel::validation::script::validate_script;
use serde_json::Value;
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"];
fn validate_target_column(
table_name: &str,
target: &str,
table_columns: &Value,
) -> Result<(), String> {
if SYSTEM_COLUMNS.contains(&target) {
return Err(format!("Cannot override system column: {}", target));
}
let columns: Vec<String> = serde_json::from_value(table_columns.clone())
.map_err(|e| format!("Invalid column data: {}", e))?;
if !columns.iter().any(|c| c == target) {
return Err(format!("Target column {} not defined in table {}", target, table_name));
}
Ok(())
}
pub async fn post_table_script( pub async fn post_table_script(
db_pool: &PgPool, db_pool: &PgPool,