2025-12-05 09:55:13 -03:00
|
|
|
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! {
|
2025-12-23 18:40:58 -03:00
|
|
|
|
2025-12-26 08:59:25 -03:00
|
|
|
static ERROR_RESUME_NEXT: RefCell<bool> = const { RefCell::new(false) };
|
2025-12-05 09:55:13 -03:00
|
|
|
|
2025-12-23 18:40:58 -03:00
|
|
|
|
2025-12-26 08:59:25 -03:00
|
|
|
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
|
2025-12-05 09:55:13 -03:00
|
|
|
|
2025-12-23 18:40:58 -03:00
|
|
|
|
2025-12-26 08:59:25 -03:00
|
|
|
static ERROR_NUMBER: RefCell<i64> = const { RefCell::new(0) };
|
2025-12-05 09:55:13 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_error_resume_next_active() -> bool {
|
|
|
|
|
ERROR_RESUME_NEXT.with(|flag| *flag.borrow())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_error_resume_next(active: bool) {
|
|
|
|
|
ERROR_RESUME_NEXT.with(|flag| {
|
|
|
|
|
*flag.borrow_mut() = active;
|
|
|
|
|
});
|
|
|
|
|
if !active {
|
|
|
|
|
clear_last_error();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn clear_last_error() {
|
|
|
|
|
LAST_ERROR.with(|err| {
|
|
|
|
|
*err.borrow_mut() = None;
|
|
|
|
|
});
|
|
|
|
|
ERROR_NUMBER.with(|num| {
|
|
|
|
|
*num.borrow_mut() = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_last_error() -> Option<String> {
|
|
|
|
|
LAST_ERROR.with(|err| err.borrow().clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_error_number() -> i64 {
|
|
|
|
|
ERROR_NUMBER.with(|num| *num.borrow())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn register_on_error_keywords(_state: Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
|
|
|
engine
|
|
|
|
|
.register_custom_syntax(
|
2025-12-26 08:59:25 -03:00
|
|
|
["ON", "ERROR", "RESUME", "NEXT"],
|
2025-12-05 09:55:13 -03:00
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
engine
|
|
|
|
|
.register_custom_syntax(
|
2025-12-26 08:59:25 -03:00
|
|
|
["ON", "ERROR", "GOTO", "0"],
|
2025-12-05 09:55:13 -03:00
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
engine
|
2025-12-26 08:59:25 -03:00
|
|
|
.register_custom_syntax(["CLEAR", "ERROR"], false, move |_context, _inputs| {
|
2025-12-05 09:55:13 -03:00
|
|
|
trace!("CLEAR ERROR executed");
|
|
|
|
|
clear_last_error();
|
|
|
|
|
Ok(Dynamic::UNIT)
|
|
|
|
|
})
|
|
|
|
|
.expect("Failed to register CLEAR ERROR");
|
|
|
|
|
|
|
|
|
|
engine.register_fn("ERROR", || -> bool { get_last_error().is_some() });
|
|
|
|
|
|
|
|
|
|
engine
|
2025-12-26 08:59:25 -03:00
|
|
|
.register_custom_syntax(["ERROR", "MESSAGE"], false, move |_context, _inputs| {
|
2025-12-05 09:55:13 -03:00
|
|
|
let msg = get_last_error().unwrap_or_default();
|
|
|
|
|
Ok(Dynamic::from(msg))
|
|
|
|
|
})
|
|
|
|
|
.expect("Failed to register ERROR MESSAGE");
|
|
|
|
|
|
|
|
|
|
engine.register_fn("ERR", || -> i64 { get_error_number() });
|
|
|
|
|
|
|
|
|
|
engine.register_fn("ERR_NUMBER", || -> i64 { get_error_number() });
|
|
|
|
|
|
|
|
|
|
engine.register_fn("ERR_DESCRIPTION", || -> String {
|
|
|
|
|
get_last_error().unwrap_or_default()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
engine.register_fn("ERR_CLEAR", || {
|
|
|
|
|
clear_last_error();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
debug!("Registered ON ERROR keywords");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn try_execute<F, T>(operation: F) -> Result<T, String>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce() -> Result<T, Box<dyn std::error::Error + Send + Sync>>,
|
|
|
|
|
{
|
|
|
|
|
match operation() {
|
|
|
|
|
Ok(result) => {
|
|
|
|
|
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() {
|
|
|
|
|
set_last_error(&error_msg, 1);
|
|
|
|
|
trace!("Error caught by ON ERROR RESUME NEXT: {}", error_msg);
|
|
|
|
|
}
|
2025-12-26 08:59:25 -03:00
|
|
|
Err(error_msg)
|
2025-12-05 09:55:13 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn handle_error<T: Into<Dynamic>>(
|
|
|
|
|
result: Result<T, Box<dyn std::error::Error + Send + Sync>>,
|
|
|
|
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
|
|
|
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,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn handle_string_error(error_msg: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
|
|
|
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,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-26 08:59:25 -03:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_error_resume_next_flag() {
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
assert!(!is_error_resume_next_active());
|
|
|
|
|
|
|
|
|
|
set_error_resume_next(true);
|
|
|
|
|
assert!(is_error_resume_next_active());
|
|
|
|
|
|
|
|
|
|
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<String, Box<dyn std::error::Error + Send + Sync>> =
|
|
|
|
|
Err("Test error".into());
|
|
|
|
|
let handled = handle_error(result);
|
|
|
|
|
|
|
|
|
|
assert!(handled.is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_handle_error_with_resume_next() {
|
|
|
|
|
set_error_resume_next(true);
|
|
|
|
|
clear_last_error();
|
|
|
|
|
|
|
|
|
|
let result: Result<String, Box<dyn std::error::Error + Send + Sync>> =
|
|
|
|
|
Err("Test error".into());
|
|
|
|
|
let handled = handle_error(result);
|
|
|
|
|
|
|
|
|
|
assert!(handled.is_ok());
|
|
|
|
|
assert_eq!(get_last_error(), Some("Test error".to_string()));
|
|
|
|
|
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_handle_string_error_without_resume_next() {
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
clear_last_error();
|
|
|
|
|
|
|
|
|
|
let handled = handle_string_error("String error");
|
|
|
|
|
assert!(handled.is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_handle_string_error_with_resume_next() {
|
|
|
|
|
set_error_resume_next(true);
|
|
|
|
|
clear_last_error();
|
|
|
|
|
|
|
|
|
|
let handled = handle_string_error("String error");
|
|
|
|
|
assert!(handled.is_ok());
|
|
|
|
|
assert_eq!(get_last_error(), Some("String error".to_string()));
|
|
|
|
|
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_try_execute_success() {
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
clear_last_error();
|
|
|
|
|
|
|
|
|
|
let result = try_execute(|| Ok::<_, Box<dyn std::error::Error + Send + Sync>>("success"));
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
assert_eq!(result.unwrap(), "success");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_try_execute_error() {
|
|
|
|
|
set_error_resume_next(false);
|
|
|
|
|
clear_last_error();
|
|
|
|
|
|
|
|
|
|
let result = try_execute(|| {
|
feat(autotask): Implement AutoTask system with intent classification and app generation
- Add IntentClassifier with 7 intent types (APP_CREATE, TODO, MONITOR, ACTION, SCHEDULE, GOAL, TOOL)
- Add AppGenerator with LLM-powered app structure analysis
- Add DesignerAI for modifying apps through conversation
- Add app_server for serving generated apps with clean URLs
- Add db_api for CRUD operations on bot database tables
- Add ask_later keyword for pending info collection
- Add migration 6.1.1 with tables: pending_info, auto_tasks, execution_plans, task_approvals, task_decisions, safety_audit_log, generated_apps, intent_classifications, designer_changes
- Write apps to S3 drive and sync to SITE_ROOT for serving
- Clean URL structure: /apps/{app_name}/
- Integrate with DriveMonitor for file sync
Based on Chapter 17 - Autonomous Tasks specification
2025-12-27 21:10:09 -03:00
|
|
|
Err::<String, _>(Box::new(std::io::Error::other("test error"))
|
|
|
|
|
as Box<dyn std::error::Error + Send + Sync>)
|
2025-12-26 08:59:25 -03:00
|
|
|
});
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
}
|
|
|
|
|
}
|