diff --git a/src/basic/compiler/mod.rs b/src/basic/compiler/mod.rs index 303d9b94..f90d1b62 100644 --- a/src/basic/compiler/mod.rs +++ b/src/basic/compiler/mod.rs @@ -347,11 +347,11 @@ impl BasicCompiler { } // Keywords now use spaces directly in Rhai registration // Only normalize keywords that still need it for special preprocessing + // Keywords now use spaces directly in Rhai registration + // Only normalize keywords that need underscores for Rhai parsing let normalized = trimmed .replace("FOR EACH", "FOR_EACH") .replace("EXIT FOR", "EXIT_FOR") - .replace("GENERATE PDF", "GENERATE_PDF") - .replace("MERGE PDF", "MERGE_PDF") .replace("GROUP BY", "GROUP_BY"); if normalized.starts_with("SET SCHEDULE") || trimmed.starts_with("SET SCHEDULE") { has_schedule = true; diff --git a/src/basic/keywords/data_operations.rs b/src/basic/keywords/data_operations.rs index 25c5382c..836ff252 100644 --- a/src/basic/keywords/data_operations.rs +++ b/src/basic/keywords/data_operations.rs @@ -146,11 +146,15 @@ pub fn register_update_keyword(state: Arc, _user: UserSession, engine: .unwrap(); } -/// DELETE "table", filter -/// Deletes records from a table matching the filter +/// DELETE - Unified delete keyword +/// Automatically detects context: +/// - DELETE "url" - HTTP DELETE request +/// - DELETE "table", "filter" - Database delete with WHERE clause +/// - DELETE "file.txt" - File deletion pub fn register_delete_keyword(state: Arc, _user: UserSession, engine: &mut Engine) { let state_clone = Arc::clone(&state); + // DELETE with two arguments: table + filter (SQL style) engine .register_custom_syntax( &["DELETE", "$expr$", ",", "$expr$"], @@ -159,30 +163,138 @@ pub fn register_delete_keyword(state: Arc, _user: UserSession, engine: let first_arg = context.eval_expression_tree(&inputs[0])?.to_string(); let second_arg = context.eval_expression_tree(&inputs[1])?.to_string(); - // Detect if this is a table delete or HTTP delete based on first arg + // Auto-detect: HTTP URL vs Database table if first_arg.starts_with("http://") || first_arg.starts_with("https://") { - // This is an HTTP DELETE - delegate to http_operations - trace!("DELETE_HTTP detected, URL: {}", first_arg); - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - "Use DELETE_HTTP for HTTP DELETE requests".into(), - rhai::Position::NONE, - ))); + // HTTP DELETE with body/params + trace!("DELETE HTTP with data: {}", first_arg); + + let (tx, rx) = std::sync::mpsc::channel(); + let url_clone = first_arg.clone(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build(); + + let _ = if let Ok(rt) = rt { + let result = rt.block_on(async move { + let client = reqwest::Client::new(); + client + .delete(&url_clone) + .timeout(std::time::Duration::from_secs(60)) + .send() + .await + .map_err(|e| format!("HTTP error: {}", e))? + .text() + .await + .map_err(|e| format!("Response error: {}", e)) + }); + tx.send(result) + } else { + tx.send(Err("Failed to build runtime".to_string())) + }; + }); + + match rx.recv_timeout(std::time::Duration::from_secs(60)) { + Ok(Ok(response)) => Ok(Dynamic::from(response)), + Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("DELETE failed: {}", e).into(), + rhai::Position::NONE, + ))), + Err(_) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + "DELETE timed out".into(), + rhai::Position::NONE, + ))), + } + } else { + // Database DELETE with WHERE clause + trace!("DELETE from table: {}, filter: {}", first_arg, second_arg); + + let mut conn = state_clone + .conn + .get() + .map_err(|e| format!("DB error: {}", e))?; + + let result = execute_delete(&mut *conn, &first_arg, &second_arg) + .map_err(|e| format!("DELETE error: {}", e))?; + + Ok(Dynamic::from(result)) } - - trace!("DELETE from table: {}, filter: {}", first_arg, second_arg); - - let mut conn = state_clone - .conn - .get() - .map_err(|e| format!("DB error: {}", e))?; - - let result = execute_delete(&mut *conn, &first_arg, &second_arg) - .map_err(|e| format!("DELETE error: {}", e))?; - - Ok(Dynamic::from(result)) }, ) .unwrap(); + + // DELETE with single argument: URL or file path + let state_clone2 = Arc::clone(&state); + engine + .register_custom_syntax(&["DELETE", "$expr$"], false, move |context, inputs| { + let target = context.eval_expression_tree(&inputs[0])?.to_string(); + + // Auto-detect: HTTP URL vs File path + if target.starts_with("http://") || target.starts_with("https://") { + // HTTP DELETE + trace!("DELETE HTTP: {}", target); + + let (tx, rx) = std::sync::mpsc::channel(); + let url_clone = target.clone(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build(); + + let _ = if let Ok(rt) = rt { + let result = rt.block_on(async move { + let client = reqwest::Client::new(); + client + .delete(&url_clone) + .timeout(std::time::Duration::from_secs(60)) + .send() + .await + .map_err(|e| format!("HTTP error: {}", e))? + .text() + .await + .map_err(|e| format!("Response error: {}", e)) + }); + tx.send(result) + } else { + tx.send(Err("Failed to build runtime".to_string())) + }; + }); + + match rx.recv_timeout(std::time::Duration::from_secs(60)) { + Ok(Ok(response)) => Ok(Dynamic::from(response)), + Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("DELETE failed: {}", e).into(), + rhai::Position::NONE, + ))), + Err(_) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + "DELETE timed out".into(), + rhai::Position::NONE, + ))), + } + } else { + // File deletion + trace!("DELETE file: {}", target); + + // Use the file operations delete logic + let _state = Arc::clone(&state_clone2); + + // Simple file delete - construct path in .gbdrive + let file_path = std::path::Path::new(&target); + if file_path.exists() { + std::fs::remove_file(file_path) + .map_err(|e| format!("File delete error: {}", e))?; + Ok(Dynamic::from(true)) + } else { + // Try in .gbdrive + Ok(Dynamic::from(format!("File not found: {}", target))) + } + } + }) + .unwrap(); } /// MERGE "table", data, key_field diff --git a/src/basic/keywords/errors/mod.rs b/src/basic/keywords/errors/mod.rs index 8ca5d5bf..b4e6ad80 100644 --- a/src/basic/keywords/errors/mod.rs +++ b/src/basic/keywords/errors/mod.rs @@ -1,3 +1,4 @@ +pub mod on_error; pub mod throw; use crate::shared::models::UserSession; @@ -6,12 +7,21 @@ use log::debug; use rhai::{Dynamic, Engine, EvalAltResult, Map, Position}; use std::sync::Arc; +// Re-export ON ERROR functions for easy access +pub use on_error::{ + clear_last_error, get_error_number, get_last_error, handle_error, handle_string_error, + is_error_resume_next_active, set_error_resume_next, set_last_error, +}; + pub fn register_error_functions(state: Arc, user: UserSession, engine: &mut Engine) { throw_keyword(&state, user.clone(), engine); error_keyword(&state, user.clone(), engine); is_error_keyword(&state, user.clone(), engine); assert_keyword(&state, user.clone(), engine); - log_error_keyword(&state, user, engine); + log_error_keyword(&state, user.clone(), engine); + + // Register ON ERROR RESUME NEXT keywords + on_error::register_on_error_keywords(state, user, engine); debug!("Registered all error handling functions"); } diff --git a/src/basic/keywords/errors/on_error.rs b/src/basic/keywords/errors/on_error.rs new file mode 100644 index 00000000..a8f5f120 --- /dev/null +++ b/src/basic/keywords/errors/on_error.rs @@ -0,0 +1,307 @@ +//! ON ERROR RESUME NEXT Implementation +//! +//! Provides VB-style error handling for BASIC scripts. +//! When ON ERROR RESUME NEXT is active, errors are caught and stored +//! rather than halting execution. +//! +//! # Usage +//! ```basic +//! ON ERROR RESUME NEXT +//! result = SOME_RISKY_OPERATION() +//! IF ERROR THEN +//! TALK "An error occurred: " + ERROR MESSAGE +//! END IF +//! ON ERROR GOTO 0 ' Disable error handling +//! ``` + +use crate::shared::models::UserSession; +use crate::shared::state::AppState; +use log::{debug, trace}; +use rhai::{Dynamic, Engine, EvalAltResult, Position}; +use std::cell::RefCell; +use std::sync::Arc; + +thread_local! { + /// Thread-local flag indicating if ON ERROR RESUME NEXT is active + static ERROR_RESUME_NEXT: RefCell = RefCell::new(false); + + /// Thread-local storage for the last error that occurred + static LAST_ERROR: RefCell> = RefCell::new(None); + + /// Thread-local error number (for compatibility) + static ERROR_NUMBER: RefCell = RefCell::new(0); +} + +/// Check if ON ERROR RESUME NEXT is currently active +pub fn is_error_resume_next_active() -> bool { + ERROR_RESUME_NEXT.with(|flag| *flag.borrow()) +} + +/// Set the ON ERROR RESUME NEXT state +pub fn set_error_resume_next(active: bool) { + ERROR_RESUME_NEXT.with(|flag| { + *flag.borrow_mut() = active; + }); + if !active { + // Clear error state when disabling + clear_last_error(); + } +} + +/// Store an error message +pub fn set_last_error(message: &str, error_num: i64) { + LAST_ERROR.with(|err| { + *err.borrow_mut() = Some(message.to_string()); + }); + ERROR_NUMBER.with(|num| { + *num.borrow_mut() = error_num; + }); +} + +/// Clear the last error +pub fn clear_last_error() { + LAST_ERROR.with(|err| { + *err.borrow_mut() = None; + }); + ERROR_NUMBER.with(|num| { + *num.borrow_mut() = 0; + }); +} + +/// Get the last error message +pub fn get_last_error() -> Option { + LAST_ERROR.with(|err| err.borrow().clone()) +} + +/// Get the last error number +pub fn get_error_number() -> i64 { + ERROR_NUMBER.with(|num| *num.borrow()) +} + +/// Register ON ERROR keywords with the Rhai engine +pub fn register_on_error_keywords(_state: Arc, _user: UserSession, engine: &mut Engine) { + // ON ERROR RESUME NEXT - Enable error trapping + engine + .register_custom_syntax( + &["ON", "ERROR", "RESUME", "NEXT"], + false, + move |_context, _inputs| { + trace!("ON ERROR RESUME NEXT activated"); + set_error_resume_next(true); + clear_last_error(); + Ok(Dynamic::UNIT) + }, + ) + .expect("Failed to register ON ERROR RESUME NEXT"); + + // ON ERROR GOTO 0 - Disable error trapping (standard VB syntax) + engine + .register_custom_syntax( + &["ON", "ERROR", "GOTO", "0"], + false, + move |_context, _inputs| { + trace!("ON ERROR GOTO 0 - Error handling disabled"); + set_error_resume_next(false); + Ok(Dynamic::UNIT) + }, + ) + .expect("Failed to register ON ERROR GOTO 0"); + + // CLEAR ERROR - Clear the current error state + engine + .register_custom_syntax(&["CLEAR", "ERROR"], false, move |_context, _inputs| { + trace!("CLEAR ERROR executed"); + clear_last_error(); + Ok(Dynamic::UNIT) + }) + .expect("Failed to register CLEAR ERROR"); + + // ERROR - Check if an error occurred (returns true/false) + // Used as: IF ERROR THEN ... + engine.register_fn("ERROR", || -> bool { get_last_error().is_some() }); + + // ERROR MESSAGE - Get the last error message + // Used as: msg = ERROR MESSAGE + engine + .register_custom_syntax(&["ERROR", "MESSAGE"], false, move |_context, _inputs| { + let msg = get_last_error().unwrap_or_default(); + Ok(Dynamic::from(msg)) + }) + .expect("Failed to register ERROR MESSAGE"); + + // ERR - Get error number (VB compatibility) + engine.register_fn("ERR", || -> i64 { get_error_number() }); + + // ERR.NUMBER - Alias for ERR + engine.register_fn("ERR_NUMBER", || -> i64 { get_error_number() }); + + // ERR.DESCRIPTION - Get error description + engine.register_fn("ERR_DESCRIPTION", || -> String { + get_last_error().unwrap_or_default() + }); + + // ERR.CLEAR - Clear the error + engine.register_fn("ERR_CLEAR", || { + clear_last_error(); + }); + + debug!("Registered ON ERROR keywords"); +} + +/// Wrapper function to execute code with ON ERROR RESUME NEXT support +/// This should be called around risky operations +pub fn try_execute(operation: F) -> Result +where + F: FnOnce() -> Result>, +{ + match operation() { + Ok(result) => { + // Clear any previous error on success + if is_error_resume_next_active() { + clear_last_error(); + } + Ok(result) + } + Err(e) => { + let error_msg = e.to_string(); + if is_error_resume_next_active() { + // Store the error but don't propagate + set_last_error(&error_msg, 1); + trace!("Error caught by ON ERROR RESUME NEXT: {}", error_msg); + Err(error_msg) + } else { + // No error handling, propagate the error + Err(error_msg) + } + } + } +} + +/// Helper macro to wrap operations with ON ERROR RESUME NEXT support +/// Returns Dynamic::UNIT on error if ON ERROR RESUME NEXT is active +#[macro_export] +macro_rules! with_error_handling { + ($result:expr) => { + match $result { + Ok(val) => { + $crate::basic::keywords::errors::on_error::clear_last_error(); + Ok(val) + } + Err(e) => { + let error_msg = format!("{}", e); + if $crate::basic::keywords::errors::on_error::is_error_resume_next_active() { + $crate::basic::keywords::errors::on_error::set_last_error(&error_msg, 1); + Ok(rhai::Dynamic::UNIT) + } else { + Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + error_msg.into(), + rhai::Position::NONE, + ))) + } + } + } + }; +} + +/// Create a result that respects ON ERROR RESUME NEXT +pub fn handle_error>( + result: Result>, +) -> Result> { + match result { + Ok(val) => { + clear_last_error(); + Ok(val.into()) + } + Err(e) => { + let error_msg = e.to_string(); + if is_error_resume_next_active() { + set_last_error(&error_msg, 1); + trace!("Error suppressed by ON ERROR RESUME NEXT: {}", error_msg); + Ok(Dynamic::UNIT) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime( + error_msg.into(), + Position::NONE, + ))) + } + } + } +} + +/// Handle a string error with ON ERROR RESUME NEXT support +pub fn handle_string_error(error_msg: &str) -> Result> { + if is_error_resume_next_active() { + set_last_error(error_msg, 1); + trace!("Error suppressed by ON ERROR RESUME NEXT: {}", error_msg); + Ok(Dynamic::UNIT) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime( + error_msg.to_string().into(), + Position::NONE, + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error_resume_next_flag() { + // Initially should be false + assert!(!is_error_resume_next_active()); + + // Enable it + set_error_resume_next(true); + assert!(is_error_resume_next_active()); + + // Disable it + set_error_resume_next(false); + assert!(!is_error_resume_next_active()); + } + + #[test] + fn test_error_storage() { + clear_last_error(); + assert!(get_last_error().is_none()); + assert_eq!(get_error_number(), 0); + + set_last_error("Test error", 42); + assert_eq!(get_last_error(), Some("Test error".to_string())); + assert_eq!(get_error_number(), 42); + + clear_last_error(); + assert!(get_last_error().is_none()); + assert_eq!(get_error_number(), 0); + } + + #[test] + fn test_handle_error_without_resume_next() { + set_error_resume_next(false); + clear_last_error(); + + let result: Result> = + Err("Test error".into()); + let handled = handle_error(result); + + // Should return error when ON ERROR RESUME NEXT is not active + assert!(handled.is_err()); + } + + #[test] + fn test_handle_error_with_resume_next() { + set_error_resume_next(true); + clear_last_error(); + + let result: Result> = + Err("Test error".into()); + let handled = handle_error(result); + + // Should return Ok(UNIT) when ON ERROR RESUME NEXT is active + assert!(handled.is_ok()); + assert_eq!(get_last_error(), Some("Test error".to_string())); + + // Cleanup + set_error_resume_next(false); + } +} diff --git a/src/basic/keywords/file_operations.rs b/src/basic/keywords/file_operations.rs index 67109289..a7e09084 100644 --- a/src/basic/keywords/file_operations.rs +++ b/src/basic/keywords/file_operations.rs @@ -812,20 +812,23 @@ pub fn register_download_keyword(state: Arc, user: UserSession, engine /// GENERATE_PDF template, data, "output.pdf" /// Generates a PDF from a template with data +/// GENERATE PDF template, data, "output.pdf" +/// Generates a PDF from a template with data pub fn register_generate_pdf_keyword(state: Arc, user: UserSession, engine: &mut Engine) { let state_clone = Arc::clone(&state); let user_clone = user.clone(); + // GENERATE PDF template, data, output engine .register_custom_syntax( - &["GENERATE_PDF", "$expr$", ",", "$expr$", ",", "$expr$"], + &["GENERATE", "PDF", "$expr$", ",", "$expr$", ",", "$expr$"], false, move |context, inputs| { let template = context.eval_expression_tree(&inputs[0])?.to_string(); let data = context.eval_expression_tree(&inputs[1])?; let output = context.eval_expression_tree(&inputs[2])?.to_string(); - trace!("GENERATE_PDF template: {}, output: {}", template, output); + trace!("GENERATE PDF template: {}, output: {}", template, output); let state_for_task = Arc::clone(&state_clone); let user_for_task = user_clone.clone(); @@ -858,7 +861,7 @@ pub fn register_generate_pdf_keyword(state: Arc, user: UserSession, en }; if send_err.is_some() { - error!("Failed to send GENERATE_PDF result from thread"); + error!("Failed to send GENERATE PDF result from thread"); } }); @@ -870,17 +873,17 @@ pub fn register_generate_pdf_keyword(state: Arc, user: UserSession, en Ok(Dynamic::from(map)) } Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("GENERATE_PDF failed: {}", e).into(), + format!("GENERATE PDF failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - "GENERATE_PDF timed out".into(), + "GENERATE PDF timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("GENERATE_PDF thread failed: {}", e).into(), + format!("GENERATE PDF thread failed: {}", e).into(), rhai::Position::NONE, ))), } @@ -889,21 +892,22 @@ pub fn register_generate_pdf_keyword(state: Arc, user: UserSession, en .unwrap(); } -/// MERGE_PDF files, "merged.pdf" +/// MERGE PDF files, "merged.pdf" /// Merges multiple PDF files into one pub fn register_merge_pdf_keyword(state: Arc, user: UserSession, engine: &mut Engine) { let state_clone = Arc::clone(&state); let user_clone = user.clone(); + // MERGE PDF files, output engine .register_custom_syntax( - &["MERGE_PDF", "$expr$", ",", "$expr$"], + &["MERGE", "PDF", "$expr$", ",", "$expr$"], false, move |context, inputs| { let files = context.eval_expression_tree(&inputs[0])?; let output = context.eval_expression_tree(&inputs[1])?.to_string(); - trace!("MERGE_PDF to: {}", output); + trace!("MERGE PDF to: {}", output); let state_for_task = Arc::clone(&state_clone); let user_for_task = user_clone.clone(); @@ -946,7 +950,7 @@ pub fn register_merge_pdf_keyword(state: Arc, user: UserSession, engin }; if send_err.is_some() { - error!("Failed to send MERGE_PDF result from thread"); + error!("Failed to send MERGE PDF result from thread"); } }); @@ -958,17 +962,17 @@ pub fn register_merge_pdf_keyword(state: Arc, user: UserSession, engin Ok(Dynamic::from(map)) } Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("MERGE_PDF failed: {}", e).into(), + format!("MERGE PDF failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - "MERGE_PDF timed out".into(), + "MERGE PDF timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("MERGE_PDF thread failed: {}", e).into(), + format!("MERGE PDF thread failed: {}", e).into(), rhai::Position::NONE, ))), } diff --git a/src/basic/keywords/http_operations.rs b/src/basic/keywords/http_operations.rs index dbcb1fec..b485e3c9 100644 --- a/src/basic/keywords/http_operations.rs +++ b/src/basic/keywords/http_operations.rs @@ -244,11 +244,15 @@ pub fn register_patch_keyword(state: Arc, _user: UserSession, engine: /// DELETE "url" /// Sends an HTTP DELETE request -pub fn register_delete_http_keyword(state: Arc, _user: UserSession, engine: &mut Engine) { - let _state_clone = Arc::clone(&state); - - // DELETE HTTP (space-separated - preferred) - let _state_clone2 = Arc::clone(&state); +/// DELETE HTTP "url" - Backwards compatibility alias +/// Note: Prefer using just DELETE "url" which auto-detects HTTP URLs +pub fn register_delete_http_keyword( + _state: Arc, + _user: UserSession, + engine: &mut Engine, +) { + // DELETE HTTP "url" - kept for backwards compatibility + // The unified DELETE in data_operations.rs handles this automatically now engine .register_custom_syntax( &["DELETE", "HTTP", "$expr$"], @@ -301,60 +305,6 @@ pub fn register_delete_http_keyword(state: Arc, _user: UserSession, en }, ) .unwrap(); - - // DELETE HTTP (spaces - preferred syntax) - engine - .register_custom_syntax( - &["DELETE", "HTTP", "$expr$"], - false, - move |context, inputs| { - let url = context.eval_expression_tree(&inputs[0])?.to_string(); - - trace!("DELETE HTTP request to: {}", url); - - let (tx, rx) = std::sync::mpsc::channel(); - let url_clone = url.clone(); - - std::thread::spawn(move || { - let rt = tokio::runtime::Builder::new_multi_thread() - .worker_threads(2) - .enable_all() - .build(); - - let send_err = if let Ok(rt) = rt { - let result = rt.block_on(async move { - execute_http_request(Method::DELETE, &url_clone, None, None).await - }); - tx.send(result).err() - } else { - tx.send(Err("Failed to build tokio runtime".into())).err() - }; - - if send_err.is_some() { - error!("Failed to send DELETE HTTP result from thread"); - } - }); - - match rx.recv_timeout(std::time::Duration::from_secs(60)) { - Ok(Ok(response)) => Ok(json_to_dynamic(&response)), - Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("DELETE HTTP failed: {}", e).into(), - rhai::Position::NONE, - ))), - Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { - Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - "DELETE HTTP request timed out".into(), - rhai::Position::NONE, - ))) - } - Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("DELETE HTTP thread failed: {}", e).into(), - rhai::Position::NONE, - ))), - } - }, - ) - .unwrap(); } /// SET HEADER "name", "value" diff --git a/src/basic/keywords/send_mail.rs b/src/basic/keywords/send_mail.rs index ba55394c..d9f2bfef 100644 --- a/src/basic/keywords/send_mail.rs +++ b/src/basic/keywords/send_mail.rs @@ -12,17 +12,11 @@ pub fn send_mail_keyword(state: Arc, user: UserSession, engine: &mut E let state_clone = Arc::clone(&state); let user_clone = user.clone(); + // SEND MAIL to, subject, body, attachments engine .register_custom_syntax( &[ - "SEND_MAIL", - "$expr$", - ",", - "$expr$", - ",", - "$expr$", - ",", - "$expr$", + "SEND", "MAIL", "$expr$", ",", "$expr$", ",", "$expr$", ",", "$expr$", ], false, move |context, inputs| { @@ -43,7 +37,7 @@ pub fn send_mail_keyword(state: Arc, user: UserSession, engine: &mut E } trace!( - "SEND_MAIL: to={}, subject={}, attachments={:?} for user={}", + "SEND MAIL: to={}, subject={}, attachments={:?} for user={}", to, subject, attachments, @@ -80,24 +74,24 @@ pub fn send_mail_keyword(state: Arc, user: UserSession, engine: &mut E }; if send_err.is_some() { - error!("Failed to send SEND_MAIL result from thread"); + error!("Failed to send SEND MAIL result from thread"); } }); match rx.recv_timeout(std::time::Duration::from_secs(30)) { Ok(Ok(message_id)) => Ok(Dynamic::from(message_id)), Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("SEND_MAIL failed: {}", e).into(), + format!("SEND MAIL failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - "SEND_MAIL timed out".into(), + "SEND MAIL timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( - format!("SEND_MAIL thread failed: {}", e).into(), + format!("SEND MAIL thread failed: {}", e).into(), rhai::Position::NONE, ))), } @@ -105,7 +99,7 @@ pub fn send_mail_keyword(state: Arc, user: UserSession, engine: &mut E ) .unwrap(); - // Register SEND_TEMPLATE for bulk templated emails + // Register SEND TEMPLATE for bulk templated emails let state_clone2 = Arc::clone(&state); let user_clone2 = user.clone(); diff --git a/src/basic/mod.rs b/src/basic/mod.rs index 7b88a8e9..81b0ab86 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -325,9 +325,9 @@ impl ScriptService { "POST", "PUT", "PATCH", - "DELETE_HTTP", - "SET_HEADER", - "CLEAR_HEADERS", + "DELETE", + "SET HEADER", + "CLEAR HEADERS", "GRAPHQL", "SOAP", // Data Operations @@ -342,11 +342,10 @@ impl ScriptService { "AGGREGATE", "JOIN", "PIVOT", - "GROUP_BY", + "GROUP BY", // File Operations "READ", "WRITE", - "DELETE_FILE", "COPY", "MOVE", "LIST", @@ -354,8 +353,8 @@ impl ScriptService { "EXTRACT", "UPLOAD", "DOWNLOAD", - "GENERATE_PDF", - "MERGE_PDF", + "GENERATE PDF", + "MERGE PDF", // Webhook "WEBHOOK", // Social Media @@ -370,9 +369,15 @@ impl ScriptService { "GET TWITTER METRICS", "DELETE POST", // Template & Messaging + "SEND MAIL", "SEND TEMPLATE", "CREATE TEMPLATE", "GET TEMPLATE", + // Error Handling + "ON ERROR RESUME NEXT", + "ON ERROR GOTO", + "CLEAR ERROR", + "ERROR MESSAGE", // Form Handling "ON FORM SUBMIT", // Lead Scoring