rhai implementation added
This commit is contained in:
119
server/src/table_script/handlers/post_table_script.rs
Normal file
119
server/src/table_script/handlers/post_table_script.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// src/table_script/handlers/post_table_script.rs
|
||||
use tonic::Status;
|
||||
use sqlx::{PgPool, Error as SqlxError};
|
||||
use rhai::Engine;
|
||||
use time::OffsetDateTime;
|
||||
use bincode::serialize;
|
||||
use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse};
|
||||
|
||||
pub async fn post_table_script(
|
||||
db_pool: &PgPool,
|
||||
request: PostTableScriptRequest,
|
||||
) -> Result<TableScriptResponse, Status> {
|
||||
// Fetch table definition
|
||||
let table_def = sqlx::query!(
|
||||
r#"SELECT table_name, columns, linked_table_id
|
||||
FROM table_definitions WHERE id = $1"#,
|
||||
request.table_definition_id
|
||||
)
|
||||
.fetch_optional(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Database error: {}", e)))?
|
||||
.ok_or_else(|| Status::not_found("Table definition not found"))?;
|
||||
|
||||
// Validate target column exists
|
||||
let columns: Vec<String> = serde_json::from_value(table_def.columns.clone())
|
||||
.map_err(|e| Status::invalid_argument(format!("Invalid columns format: {}", e)))?;
|
||||
|
||||
let column_names: Vec<&str> = columns.iter()
|
||||
.filter_map(|col| col.split_whitespace().next())
|
||||
.map(|name| name.trim_matches('"'))
|
||||
.collect();
|
||||
|
||||
if !column_names.contains(&request.target_column.as_str()) {
|
||||
return Err(Status::invalid_argument("Target column not found in table definition"));
|
||||
}
|
||||
|
||||
// Check for existing data
|
||||
let row_count: i64 = sqlx::query_scalar(&format!(
|
||||
"SELECT COUNT(*) FROM {}",
|
||||
sanitize_identifier(&table_def.table_name)
|
||||
))
|
||||
.fetch_one(db_pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("Data check failed: {}", e)))?;
|
||||
|
||||
if row_count > 0 {
|
||||
return Err(Status::failed_precondition(
|
||||
"Cannot add scripts to tables with existing data"
|
||||
));
|
||||
}
|
||||
|
||||
// Compile Rhai script
|
||||
let (ast, warnings) = compile_rhai_script(&request.rhai_script)
|
||||
.map_err(|e| Status::invalid_argument(format!("Script error: {}", e)))?;
|
||||
|
||||
// Store script in database
|
||||
let script_record = sqlx::query!(
|
||||
r#"INSERT INTO table_scripts
|
||||
(table_definitions_id, target_column, rhai_script, compiled_script)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, created_at"#,
|
||||
request.table_definition_id,
|
||||
request.target_column,
|
||||
request.rhai_script,
|
||||
bincode::serialize(&ast).map_err(|e| Status::internal(format!("Serialization failed: {}", e)))?,
|
||||
)
|
||||
.fetch_one(db_pool)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
SqlxError::Database(db_err) if db_err.constraint() == Some("table_scripts_table_definitions_id_target_column_key") => {
|
||||
Status::already_exists("Script already exists for this column")
|
||||
}
|
||||
_ => Status::internal(format!("Database error: {}", e)),
|
||||
})?;
|
||||
|
||||
Ok(TableScriptResponse {
|
||||
id: script_record.id,
|
||||
created_at: Some(script_record.created_at.into()),
|
||||
warnings,
|
||||
})
|
||||
Ok(TableScriptResponse {
|
||||
id: script_record.id,
|
||||
created_at: script_record.created_at.to_string(),
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
|
||||
fn sanitize_identifier(s: &str) -> String {
|
||||
s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "")
|
||||
.trim()
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
fn compile_rhai_script(script: &str) -> Result<(AST, String), String> {
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add custom API bindings
|
||||
engine.register_fn("get_column", |_: &str, _: &str| Ok(()));
|
||||
engine.register_fn("set_result", |_: &mut Scope, _: rhai::Dynamic| Ok(()));
|
||||
|
||||
let ast = engine.compile(script).map_err(|e| e.to_string())?;
|
||||
|
||||
// Validate script structure
|
||||
if !script.contains("set_result") {
|
||||
return Err("Script must call set_result()".into());
|
||||
}
|
||||
|
||||
let warnings = engine.gen_ast_clear_comments(&ast)
|
||||
.iter()
|
||||
.filter_map(|(_, err)| match err {
|
||||
rhai::ParseError(warn, _) => Some(warn.to_string()),
|
||||
_ => None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
Ok((ast, warnings))
|
||||
}
|
||||
Reference in New Issue
Block a user