From 90016ea3735f6938c7a6350a1ca9c21586071453 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sun, 20 Jul 2025 15:01:18 -0300 Subject: [PATCH] - GET keyword added. --- src/main.rs | 6 +- src/prompts/business/data-enrichment.bas | 4 +- src/services/keywords/get.rs | 50 +++++--- src/services/utils.rs | 138 ++++++++++------------- 4 files changed, 102 insertions(+), 96 deletions(-) diff --git a/src/main.rs b/src/main.rs index 526d7db..8ef871f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,8 @@ mod services; async fn main() -> std::io::Result<()> { dotenv().ok(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let config = AppConfig::from_env(); let db_url = config.database_url(); let db_custom_url = config.database_custom_url(); @@ -36,7 +38,7 @@ async fn main() -> std::io::Result<()> { let script_service = ScriptService::new(&app_state.clone()); - const TEXT : &str = include_str!("prompts/business/data-enrichment.bas"); + const TEXT: &str = include_str!("prompts/business/data-enrichment.bas"); match script_service.compile(TEXT) { Ok(ast) => match script_service.run(&ast) { @@ -48,10 +50,8 @@ async fn main() -> std::io::Result<()> { // Start HTTP server HttpServer::new(move || { - let cors = Cors::default() .send_wildcard() - .allowed_origin("*") .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE) diff --git a/src/prompts/business/data-enrichment.bas b/src/prompts/business/data-enrichment.bas index 27fd0b5..d4bfb62 100644 --- a/src/prompts/business/data-enrichment.bas +++ b/src/prompts/business/data-enrichment.bas @@ -1,5 +1,5 @@ let items = FIND "gb.rob", "ACTION=EMUL1" FOR EACH item IN items - let text = GET "example.com" - PRINT item.name + let text = GET "https://pragmatismo.com.br" + PRINT item.company NEXT item \ No newline at end of file diff --git a/src/services/keywords/get.rs b/src/services/keywords/get.rs index 1f981a9..bc512a3 100644 --- a/src/services/keywords/get.rs +++ b/src/services/keywords/get.rs @@ -1,20 +1,40 @@ -use rhai::Dynamic; -use rhai::Engine; - +use rhai::{Dynamic, Engine}; +use reqwest; use crate::services::state::AppState; +use std::error::Error; pub fn get_keyword(_state: &AppState, engine: &mut Engine) { - engine - .register_custom_syntax( - &["GET", "$expr$"], - false, // Expression, not statement - |context, inputs| { - let url = context.eval_expression_tree(&inputs[0])?; - let url_str = url.to_string(); + engine.register_custom_syntax( + &["GET", "$expr$"], + false, // Expression, not statement + move |context, inputs| { + let url = context.eval_expression_tree(&inputs[0])?; + let url_str = url.to_string(); - println!("GET executed: {}", url_str.to_string()); - Ok(format!("Content from {}", url_str).into()) - }, - ) - .unwrap(); + if url_str.starts_with("https") { + println!("HTTPS GET request: {}", url_str); + + // Use the same pattern as find_keyword + let fut = execute_get(&url_str); + let result = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(fut) + }).map_err(|e| format!("HTTP request failed: {}", e))?; + + Ok(Dynamic::from(result)) + } else { + println!("GET executed: {}", url_str); + Ok(Dynamic::from(format!("Content from {}", url_str))) + } + } + ).unwrap(); } + +pub async fn execute_get(url: &str) -> Result> { + println!("Starting execute_get with URL: {}", url); + + let response = reqwest::get(url).await?; + let content = response.text().await?; + + println!("GET request successful, got {} bytes", content.len()); + Ok(format!("Secure content fetched: {}", content)) +} \ No newline at end of file diff --git a/src/services/utils.rs b/src/services/utils.rs index 10fe0b3..181956c 100644 --- a/src/services/utils.rs +++ b/src/services/utils.rs @@ -1,104 +1,90 @@ -use smartstring::SmartString; +use log::{debug, warn}; use rhai::{Array, Dynamic}; use serde_json::{json, Value}; +use smartstring::SmartString; use sqlx::Column; // Required for .name() method use sqlx::TypeInfo; // Required for .type_info() method use sqlx::{postgres::PgRow, Row}; - use std::error::Error; +use sqlx::{Decode, Type}; pub fn row_to_json(row: PgRow) -> Result> { let mut result = serde_json::Map::new(); let columns = row.columns(); - println!("Processing {} columns", columns.len()); + debug!("Converting row with {} columns", columns.len()); for (i, column) in columns.iter().enumerate() { let column_name = column.name(); let type_name = column.type_info().name(); - println!( - "Processing column {}: {} (type: {})", - i, column_name, type_name - ); - let value: Value = match type_name { - "INT4" | "INT8" | "int4" | "int8" => match row.try_get::(i) { - Ok(v) => { - println!("Got int64 value: {}", v); - json!(v) - } - Err(e) => { - println!("Failed to get int64, trying i32: {}", e); - match row.try_get::(i) { - Ok(v) => json!(v as i64), - Err(_) => Value::Null, - } - } - }, - "FLOAT4" | "FLOAT8" | "float4" | "float8" => match row.try_get::(i) { - Ok(v) => { - println!("Got float64 value: {}", v); - json!(v) - } - Err(e) => { - println!("Failed to get float64, trying f32: {}", e); - match row.try_get::(i) { - Ok(v) => json!(v as f64), - Err(_) => Value::Null, - } - } - }, - "TEXT" | "VARCHAR" | "text" | "varchar" => match row.try_get::(i) { - Ok(v) => { - println!("Got string value: {}", v); - json!(v) - } - Err(e) => { - println!("Failed to get string: {}", e); - Value::Null - } - }, - "BOOL" | "bool" => match row.try_get::(i) { - Ok(v) => { - println!("Got bool value: {}", v); - json!(v) - } - Err(e) => { - println!("Failed to get bool: {}", e); - Value::Null - } - }, - "JSON" | "JSONB" | "json" | "jsonb" => match row.try_get::(i) { - Ok(v) => { - println!("Got JSON value: {:?}", v); - v - } - Err(e) => { - println!("Failed to get JSON, trying as string: {}", e); - match row.try_get::(i) { - Ok(s) => match serde_json::from_str(&s) { - Ok(v) => v, - Err(_) => json!(s), - }, - Err(_) => Value::Null, - } - } - }, + let value = match type_name { + "INT4" | "int4" => handle_nullable_type::(&row, i, column_name), + "INT8" | "int8" => handle_nullable_type::(&row, i, column_name), + "FLOAT4" | "float4" => handle_nullable_type::(&row, i, column_name), + "FLOAT8" | "float8" => handle_nullable_type::(&row, i, column_name), + "TEXT" | "VARCHAR" | "text" | "varchar" => handle_nullable_type::(&row, i, column_name), + "BOOL" | "bool" => handle_nullable_type::(&row, i, column_name), + "JSON" | "JSONB" | "json" | "jsonb" => handle_json(&row, i, column_name), _ => { - println!("Unknown type {}, trying as string", type_name); - match row.try_get::(i) { - Ok(v) => json!(v), - Err(_) => Value::Null, - } + warn!("Unknown type {} for column {}", type_name, column_name); + handle_nullable_type::(&row, i, column_name) } }; + result.insert(column_name.to_string(), value); } - println!("Finished processing row, got {} fields", result.len()); + Ok(Value::Object(result)) } +fn handle_nullable_type<'r, T>(row: &'r PgRow, idx: usize, col_name: &str) -> Value +where + T: Type + Decode<'r, sqlx::Postgres> + serde::Serialize + std::fmt::Debug, +{ + match row.try_get::, _>(idx) { + Ok(Some(val)) => { + debug!("Successfully read column {} as {:?}", col_name, val); + json!(val) + } + Ok(None) => { + debug!("Column {} is NULL", col_name); + Value::Null + } + Err(e) => { + warn!("Failed to read column {}: {}", col_name, e); + Value::Null + } + } +} +fn handle_json(row: &PgRow, idx: usize, col_name: &str) -> Value { + // First try to get as Option + match row.try_get::, _>(idx) { + Ok(Some(val)) => { + debug!("Successfully read JSON column {} as Value", col_name); + return val; + } + Ok(None) => return Value::Null, + Err(_) => (), // Fall through to other attempts + } + + // Try as Option that might contain JSON + match row.try_get::, _>(idx) { + Ok(Some(s)) => match serde_json::from_str(&s) { + Ok(val) => val, + Err(_) => { + debug!("Column {} contains string that's not JSON", col_name); + json!(s) + } + }, + Ok(None) => Value::Null, + Err(e) => { + warn!("Failed to read JSON column {}: {}", col_name, e); + Value::Null + } + } +} pub fn json_value_to_dynamic(value: &Value) -> Dynamic { match value { Value::Null => Dynamic::UNIT,