From ff7b2494f71be0da666fae9d534c44d7031c142a Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Wed, 10 Sep 2025 15:49:02 +0200 Subject: [PATCH 01/14] implemented logger Component --- src/render.rs | 76 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/src/render.rs b/src/render.rs index 197c0002..049e4077 100644 --- a/src/render.rs +++ b/src/render.rs @@ -649,6 +649,9 @@ pub struct HtmlRenderContext { const DEFAULT_COMPONENT: &str = "table"; const PAGE_SHELL_COMPONENT: &str = "shell"; const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty"; +const LOG_COMPONENT: &str = "log"; +const LOG_MESSAGE_KEY: &str = "message"; +const LOG_PRIORITY_KEY: &str = "priority"; impl HtmlRenderContext { pub async fn new( @@ -721,6 +724,64 @@ impl HtmlRenderContext { component.starts_with(PAGE_SHELL_COMPONENT) } + fn is_log_component(component: &str) -> bool { + component.starts_with(LOG_COMPONENT) + } + + fn string_to_log_level(log_level_string: &str) -> log::Level { + match log_level_string { + "error" => log::Level::Error, + "warn" => log::Level::Warn, + "trace" => log::Level::Trace, + "debug" => log::Level::Debug, + _ => log::Level::Info, + } + } + + async fn handle_component(&mut self, comp_str: &str, data: &JsonValue) -> anyhow::Result<()> { + if Self::is_shell_component(comp_str) { + bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); + } + + log::debug!("{comp_str}"); + + if Self::is_log_component(comp_str) { + log::debug!("{data}"); + let object_map: &serde_json::Map = match data { + JsonValue::Object(object) => object, + _ => { + return Err(anyhow::anyhow!("expected a JsonObject")); + } + }; + + let log_level: log::Level = match object_map.get(LOG_PRIORITY_KEY) { + Some(Value::String(priority)) => { + Self::string_to_log_level(priority.clone().as_str()) + } + _ => log::Level::Info, + }; + + if let Some(value) = object_map.get(LOG_MESSAGE_KEY) { + log::log!(log_level, "{value}"); + } else { + return Err(anyhow::anyhow!("no Message was defined")); + } + + return Ok(()); + } + + match self.open_component_with_data(comp_str, &data).await { + Ok(_) => Ok(()), + Err(err) => match HeaderComponent::try_from(comp_str) { + Ok(_) => bail!("The {comp_str} component cannot be used after data has already been sent to the client's browser. \n\ + This component must be used before any other component. \n\ + To fix this, either move the call to the '{comp_str}' component to the top of the SQL file, \n\ + or create a new SQL file where '{comp_str}' is the first component."), + Err(()) => Err(err), + }, + } + } + pub async fn handle_row(&mut self, data: &JsonValue) -> anyhow::Result<()> { let new_component = get_object_str(data, "component"); let current_component = self @@ -728,20 +789,7 @@ impl HtmlRenderContext { .as_ref() .map(SplitTemplateRenderer::name); if let Some(comp_str) = new_component { - if Self::is_shell_component(comp_str) { - bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); - } - - match self.open_component_with_data(comp_str, &data).await { - Ok(_) => (), - Err(err) => match HeaderComponent::try_from(comp_str) { - Ok(_) => bail!("The {comp_str} component cannot be used after data has already been sent to the client's browser. \n\ - This component must be used before any other component. \n\ - To fix this, either move the call to the '{comp_str}' component to the top of the SQL file, \n\ - or create a new SQL file where '{comp_str}' is the first component."), - Err(()) => return Err(err), - }, - } + self.handle_component(comp_str, data).await?; } else if current_component.is_none() { self.open_component_with_data(DEFAULT_COMPONENT, &JsonValue::Null) .await?; From 61aca84ffe8e6def73fb26ebcf87d3e385efbdf7 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 10:42:20 +0200 Subject: [PATCH 02/14] changed target for logger to "sqlpage::logger" and made the log::Level case insensitive --- src/render.rs | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/render.rs b/src/render.rs index 049e4077..ad6f271b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -652,6 +652,8 @@ const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty"; const LOG_COMPONENT: &str = "log"; const LOG_MESSAGE_KEY: &str = "message"; const LOG_PRIORITY_KEY: &str = "priority"; +// without the "sqlpage::" prefix it does not log at all. +const LOG_TARGET: &str = "sqlpage::logger"; impl HtmlRenderContext { pub async fn new( @@ -738,36 +740,37 @@ impl HtmlRenderContext { } } + fn handle_log_component(data: &JsonValue) -> anyhow::Result<()> { + let object_map: &serde_json::Map = match data { + JsonValue::Object(object) => object, + _ => { + return Err(anyhow::anyhow!("expected a JsonObject")); + } + }; + + let log_level: log::Level = match object_map.get(LOG_PRIORITY_KEY) { + Some(Value::String(priority)) => { + Self::string_to_log_level(priority.clone().to_lowercase().as_str()) + } + _ => log::Level::Info, + }; + + if let Some(value) = object_map.get(LOG_MESSAGE_KEY) { + log::log!(target: LOG_TARGET, log_level, "{value}"); + } else { + return Err(anyhow::anyhow!("no message was defined")); + } + + Ok(()) + } + async fn handle_component(&mut self, comp_str: &str, data: &JsonValue) -> anyhow::Result<()> { if Self::is_shell_component(comp_str) { bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); } - log::debug!("{comp_str}"); - if Self::is_log_component(comp_str) { - log::debug!("{data}"); - let object_map: &serde_json::Map = match data { - JsonValue::Object(object) => object, - _ => { - return Err(anyhow::anyhow!("expected a JsonObject")); - } - }; - - let log_level: log::Level = match object_map.get(LOG_PRIORITY_KEY) { - Some(Value::String(priority)) => { - Self::string_to_log_level(priority.clone().as_str()) - } - _ => log::Level::Info, - }; - - if let Some(value) = object_map.get(LOG_MESSAGE_KEY) { - log::log!(log_level, "{value}"); - } else { - return Err(anyhow::anyhow!("no Message was defined")); - } - - return Ok(()); + return Self::handle_log_component(data); } match self.open_component_with_data(comp_str, &data).await { From 7144f5f3844cf13e21c705bb2e783c5fd2b8a3b0 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 10:45:03 +0200 Subject: [PATCH 03/14] changed error message on missing message key, to be more precise --- src/render.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render.rs b/src/render.rs index ad6f271b..6a4bbbf3 100644 --- a/src/render.rs +++ b/src/render.rs @@ -758,7 +758,7 @@ impl HtmlRenderContext { if let Some(value) = object_map.get(LOG_MESSAGE_KEY) { log::log!(target: LOG_TARGET, log_level, "{value}"); } else { - return Err(anyhow::anyhow!("no message was defined")); + return Err(anyhow::anyhow!("no message was defined for log component")); } Ok(()) From 4e0c80c8c1135fb20f89574c6df9f48e4dc5c1d3 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 10:58:40 +0200 Subject: [PATCH 04/14] changed custom method for String to log::Level conversion to builtin method log::Level::from_str() --- src/render.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/render.rs b/src/render.rs index 6a4bbbf3..a00655a8 100644 --- a/src/render.rs +++ b/src/render.rs @@ -58,6 +58,7 @@ use serde_json::{json, Value}; use std::borrow::Cow; use std::convert::TryFrom; use std::io::Write; +use std::str::FromStr; use std::sync::Arc; pub enum PageContext { @@ -730,16 +731,6 @@ impl HtmlRenderContext { component.starts_with(LOG_COMPONENT) } - fn string_to_log_level(log_level_string: &str) -> log::Level { - match log_level_string { - "error" => log::Level::Error, - "warn" => log::Level::Warn, - "trace" => log::Level::Trace, - "debug" => log::Level::Debug, - _ => log::Level::Info, - } - } - fn handle_log_component(data: &JsonValue) -> anyhow::Result<()> { let object_map: &serde_json::Map = match data { JsonValue::Object(object) => object, @@ -750,7 +741,11 @@ impl HtmlRenderContext { let log_level: log::Level = match object_map.get(LOG_PRIORITY_KEY) { Some(Value::String(priority)) => { - Self::string_to_log_level(priority.clone().to_lowercase().as_str()) + if let Ok(level) = log::Level::from_str(&priority.clone()) { + level + } else { + log::Level::Info + } } _ => log::Level::Info, }; From e18eabb75117ad9eb7401bfcb70476c0b7fe70f5 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 17:12:04 +0200 Subject: [PATCH 05/14] switched to utility method get_object_str and inlined constants --- src/render.rs | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/render.rs b/src/render.rs index a00655a8..b83abd50 100644 --- a/src/render.rs +++ b/src/render.rs @@ -650,11 +650,6 @@ pub struct HtmlRenderContext { const DEFAULT_COMPONENT: &str = "table"; const PAGE_SHELL_COMPONENT: &str = "shell"; const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty"; -const LOG_COMPONENT: &str = "log"; -const LOG_MESSAGE_KEY: &str = "message"; -const LOG_PRIORITY_KEY: &str = "priority"; -// without the "sqlpage::" prefix it does not log at all. -const LOG_TARGET: &str = "sqlpage::logger"; impl HtmlRenderContext { pub async fn new( @@ -727,31 +722,18 @@ impl HtmlRenderContext { component.starts_with(PAGE_SHELL_COMPONENT) } - fn is_log_component(component: &str) -> bool { - component.starts_with(LOG_COMPONENT) - } - - fn handle_log_component(data: &JsonValue) -> anyhow::Result<()> { - let object_map: &serde_json::Map = match data { - JsonValue::Object(object) => object, - _ => { - return Err(anyhow::anyhow!("expected a JsonObject")); - } - }; - - let log_level: log::Level = match object_map.get(LOG_PRIORITY_KEY) { - Some(Value::String(priority)) => { - if let Ok(level) = log::Level::from_str(&priority.clone()) { - level - } else { - log::Level::Info + fn handle_log_component(&self, data: &JsonValue) -> anyhow::Result<()> { + let mut log_level = log::Level::Info; + if let Some(priority) = get_object_str(data, "priority") { + if let Ok(level) = log::Level::from_str(priority) { + log_level = level; } } _ => log::Level::Info, }; - if let Some(value) = object_map.get(LOG_MESSAGE_KEY) { - log::log!(target: LOG_TARGET, log_level, "{value}"); + if let Some(message) = get_object_str(data, "message") { + log::log!(target: &target, log_level, "{message}"); } else { return Err(anyhow::anyhow!("no message was defined for log component")); } @@ -764,8 +746,8 @@ impl HtmlRenderContext { bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); } - if Self::is_log_component(comp_str) { - return Self::handle_log_component(data); + if comp_str == "log" { + return self.handle_log_component(data); } match self.open_component_with_data(comp_str, &data).await { From 39e0f21f46255d17ff6c91991deecb372f7f312d Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 17:12:40 +0200 Subject: [PATCH 06/14] dynamically target based on file and statement --- src/render.rs | 16 ++++++++++++---- src/webserver/database/sql.rs | 2 +- src/webserver/http.rs | 3 +++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/render.rs b/src/render.rs index b83abd50..d62096f8 100644 --- a/src/render.rs +++ b/src/render.rs @@ -727,15 +727,23 @@ impl HtmlRenderContext { if let Some(priority) = get_object_str(data, "priority") { if let Ok(level) = log::Level::from_str(priority) { log_level = level; - } } - _ => log::Level::Info, - }; + } + + let target = format!( + "sqlpage::log from file \"{}\" in statement {}", + self.request_context.source_path.display(), + self.current_statement + ); if let Some(message) = get_object_str(data, "message") { log::log!(target: &target, log_level, "{message}"); } else { - return Err(anyhow::anyhow!("no message was defined for log component")); + return Err(anyhow::anyhow!( + "message undefined for log in \"{}\" in statement {}", + self.request_context.source_path.display(), + self.current_statement + )); } Ok(()) diff --git a/src/webserver/database/sql.rs b/src/webserver/database/sql.rs index 1417aa4b..97631b02 100644 --- a/src/webserver/database/sql.rs +++ b/src/webserver/database/sql.rs @@ -25,7 +25,7 @@ use std::str::FromStr; #[derive(Default)] pub struct ParsedSqlFile { pub(super) statements: Vec, - pub(super) source_path: PathBuf, + pub source_path: PathBuf, } impl ParsedSqlFile { diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 7ebce97c..1bfb0dae 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -44,6 +44,7 @@ use tokio::sync::mpsc; #[derive(Clone)] pub struct RequestContext { pub is_embedded: bool, + pub source_path: PathBuf, pub content_security_policy: ContentSecurityPolicy, } @@ -174,9 +175,11 @@ async fn render_sql( log::debug!("Received a request with the following parameters: {req_param:?}"); let (resp_send, resp_recv) = tokio::sync::oneshot::channel::(); + let source_path: PathBuf = sql_file.source_path.clone(); actix_web::rt::spawn(async move { let request_context = RequestContext { is_embedded: req_param.get_variables.contains_key("_sqlpage_embed"), + source_path, content_security_policy: ContentSecurityPolicy::with_random_nonce(), }; let mut conn = None; From fe4d6ccb850e382ea362a7388e0a3edcfb841539 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 17:24:26 +0200 Subject: [PATCH 07/14] disabled ci error (large difference in enum variants) for ResponseWithWriter --- src/webserver/http.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 1bfb0dae..f5543fff 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -148,6 +148,7 @@ async fn build_response_header_and_stream>( Ok(ResponseWithWriter::FinishedResponse { http_response }) } +#[allow(clippy::large_enum_variant)] enum ResponseWithWriter { RenderStream { http_response: HttpResponse, From 398476f96b27f9edcc3ffe7233da7cca358d0606 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Thu, 11 Sep 2025 20:43:13 +0200 Subject: [PATCH 08/14] added functionality to work in Header context --- src/render.rs | 79 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/src/render.rs b/src/render.rs index d62096f8..93082b0e 100644 --- a/src/render.rs +++ b/src/render.rs @@ -58,6 +58,7 @@ use serde_json::{json, Value}; use std::borrow::Cow; use std::convert::TryFrom; use std::io::Write; +use std::path::Path; use std::str::FromStr; use std::sync::Arc; @@ -120,6 +121,7 @@ impl HeaderContext { Some(HeaderComponent::Cookie) => self.add_cookie(&data).map(PageContext::Header), Some(HeaderComponent::Authentication) => self.authentication(data).await, Some(HeaderComponent::Download) => self.download(&data), + Some(HeaderComponent::Log) => self.log(&data), None => self.start_body(data).await, } } @@ -361,6 +363,11 @@ impl HeaderContext { )) } + fn log(self, data: &JsonValue) -> anyhow::Result { + handle_log_component(&self.request_context.source_path, Option::None, data)?; + Ok(PageContext::Header(self)) + } + async fn start_body(self, data: JsonValue) -> anyhow::Result { let html_renderer = HtmlRenderContext::new(self.app_state, self.request_context, self.writer, data) @@ -722,40 +729,17 @@ impl HtmlRenderContext { component.starts_with(PAGE_SHELL_COMPONENT) } - fn handle_log_component(&self, data: &JsonValue) -> anyhow::Result<()> { - let mut log_level = log::Level::Info; - if let Some(priority) = get_object_str(data, "priority") { - if let Ok(level) = log::Level::from_str(priority) { - log_level = level; - } - } - - let target = format!( - "sqlpage::log from file \"{}\" in statement {}", - self.request_context.source_path.display(), - self.current_statement - ); - - if let Some(message) = get_object_str(data, "message") { - log::log!(target: &target, log_level, "{message}"); - } else { - return Err(anyhow::anyhow!( - "message undefined for log in \"{}\" in statement {}", - self.request_context.source_path.display(), - self.current_statement - )); - } - - Ok(()) - } - async fn handle_component(&mut self, comp_str: &str, data: &JsonValue) -> anyhow::Result<()> { if Self::is_shell_component(comp_str) { bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); } if comp_str == "log" { - return self.handle_log_component(data); + return handle_log_component( + &self.request_context.source_path, + Some(self.current_statement), + data, + ); } match self.open_component_with_data(comp_str, &data).await { @@ -921,6 +905,43 @@ impl HtmlRenderContext { } } +fn handle_log_component( + source_path: &Path, + current_statement: Option, + data: &JsonValue, +) -> anyhow::Result<()> { + let mut log_level = log::Level::Info; + if let Some(priority) = get_object_str(data, "priority") { + if let Ok(level) = log::Level::from_str(priority) { + log_level = level; + } + } + + let current_statement_string = if let Some(option) = current_statement { + &format!("statement {option}") + } else { + "header" + }; + + let target = format!( + "sqlpage::log from file \"{}\" in {}", + source_path.display(), + current_statement_string + ); + + if let Some(message) = get_object_str(data, "message") { + log::log!(target: &target, log_level, "{message}"); + } else { + return Err(anyhow::anyhow!( + "message undefined for log in \"{}\" in {}", + source_path.display(), + current_statement_string + )); + } + + Ok(()) +} + pub(super) fn get_backtrace_as_strings(error: &anyhow::Error) -> Vec { let mut backtrace = vec![]; let mut source = error.source(); @@ -1144,6 +1165,7 @@ enum HeaderComponent { Cookie, Authentication, Download, + Log, } impl TryFrom<&str> for HeaderComponent { @@ -1158,6 +1180,7 @@ impl TryFrom<&str> for HeaderComponent { "cookie" => Ok(Self::Cookie), "authentication" => Ok(Self::Authentication), "download" => Ok(Self::Download), + "log" => Ok(Self::Log), _ => Err(()), } } From 380838064ffde4f3b22e059988bc0348a428f7c0 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Thu, 11 Sep 2025 22:08:50 +0200 Subject: [PATCH 09/14] Refactor log component to use compact error handling --- src/render.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/render.rs b/src/render.rs index 93082b0e..b2d5cabc 100644 --- a/src/render.rs +++ b/src/render.rs @@ -57,6 +57,7 @@ use serde::Serialize; use serde_json::{json, Value}; use std::borrow::Cow; use std::convert::TryFrom; +use std::fmt::Write as _; use std::io::Write; use std::path::Path; use std::str::FromStr; @@ -910,35 +911,16 @@ fn handle_log_component( current_statement: Option, data: &JsonValue, ) -> anyhow::Result<()> { - let mut log_level = log::Level::Info; - if let Some(priority) = get_object_str(data, "priority") { - if let Ok(level) = log::Level::from_str(priority) { - log_level = level; - } - } + let priority = get_object_str(data, "priority").unwrap_or("info"); + let log_level = log::Level::from_str(priority).with_context(|| "Invalid log priority value")?; - let current_statement_string = if let Some(option) = current_statement { - &format!("statement {option}") - } else { - "header" - }; - - let target = format!( - "sqlpage::log from file \"{}\" in {}", - source_path.display(), - current_statement_string - ); - - if let Some(message) = get_object_str(data, "message") { - log::log!(target: &target, log_level, "{message}"); - } else { - return Err(anyhow::anyhow!( - "message undefined for log in \"{}\" in {}", - source_path.display(), - current_statement_string - )); + let mut target = format!("sqlpage::log from \"{}\"", source_path.display()); + if let Some(current_statement) = current_statement { + write!(&mut target, " statement {current_statement}")?; } + let message = get_object_str(data, "message").context("log: missing property 'message'")?; + log::log!(target: &target, log_level, "{message}"); Ok(()) } From b4ad8dbaad0ae8f4e87ebb55295c7d9e5152a433 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Thu, 11 Sep 2025 22:10:51 +0200 Subject: [PATCH 10/14] Rename comp_str variable to component_name --- src/render.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/render.rs b/src/render.rs index b2d5cabc..0040ac86 100644 --- a/src/render.rs +++ b/src/render.rs @@ -730,12 +730,16 @@ impl HtmlRenderContext { component.starts_with(PAGE_SHELL_COMPONENT) } - async fn handle_component(&mut self, comp_str: &str, data: &JsonValue) -> anyhow::Result<()> { - if Self::is_shell_component(comp_str) { - bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", comp_str); + async fn handle_component( + &mut self, + component_name: &str, + data: &JsonValue, + ) -> anyhow::Result<()> { + if Self::is_shell_component(component_name) { + bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", component_name); } - if comp_str == "log" { + if component_name == "log" { return handle_log_component( &self.request_context.source_path, Some(self.current_statement), @@ -743,13 +747,13 @@ impl HtmlRenderContext { ); } - match self.open_component_with_data(comp_str, &data).await { + match self.open_component_with_data(component_name, &data).await { Ok(_) => Ok(()), - Err(err) => match HeaderComponent::try_from(comp_str) { - Ok(_) => bail!("The {comp_str} component cannot be used after data has already been sent to the client's browser. \n\ + Err(err) => match HeaderComponent::try_from(component_name) { + Ok(_) => bail!("The {component_name} component cannot be used after data has already been sent to the client's browser. \n\ This component must be used before any other component. \n\ - To fix this, either move the call to the '{comp_str}' component to the top of the SQL file, \n\ - or create a new SQL file where '{comp_str}' is the first component."), + To fix this, either move the call to the '{component_name}' component to the top of the SQL file, \n\ + or create a new SQL file where '{component_name}' is the first component."), Err(()) => Err(err), }, } @@ -761,8 +765,8 @@ impl HtmlRenderContext { .current_component .as_ref() .map(SplitTemplateRenderer::name); - if let Some(comp_str) = new_component { - self.handle_component(comp_str, data).await?; + if let Some(component_name) = new_component { + self.handle_component(component_name, data).await?; } else if current_component.is_none() { self.open_component_with_data(DEFAULT_COMPONENT, &JsonValue::Null) .await?; From 30ccf2c9a367bf82f11893150ee71ac10e6e966e Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Fri, 12 Sep 2025 10:38:11 +0200 Subject: [PATCH 11/14] Documented log component --- .../sqlpage/migrations/66_log_component.sql | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/official-site/sqlpage/migrations/66_log_component.sql diff --git a/examples/official-site/sqlpage/migrations/66_log_component.sql b/examples/official-site/sqlpage/migrations/66_log_component.sql new file mode 100644 index 00000000..46665b5d --- /dev/null +++ b/examples/official-site/sqlpage/migrations/66_log_component.sql @@ -0,0 +1,58 @@ +INSERT INTO components(name, icon, description) VALUES +('log', 'logs', 'A Component to log a message to the Servers STDOUT or Log file on page load') + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'log', * FROM ( + -- item level + ('message', 'The message that needs to be logged', 'ANY', FALSE, FALSE), + ('priority', 'The priority which the message should be logged with. Possible values are [''trace'', ''debug'', ''info'', ''warn'', ''error''] and are not case sensitive. If this value is missing or not matching any possible values, the default priority will be ''info''.', 'TEXT', FALSE, TRUE) +) x; + +INSERT INTO example(component, description) VALUES +('log', ' +### Hello World + +Log a simple ''Hello, World!'' message on page load. + +```sql +SELECT ''log'' as component, + ''Hello, World!'' as message +``` + +Output example: + +``` +[2025-09-12T08:33:48.228Z INFO sqlpage::log from file "index.sql" in statement 3] Hello, World! +``` + +### Priority + +Change the priority to error. + +```sql +SELECT ''log'' as component, + ''This is a error message'' as message, + ''error'' as priority +``` + +Output example: + +``` +[2025-09-12T08:33:48.228Z ERROR sqlpage::log from file "index.sql" in header] This is a error message +``` + +### Retrieve user data + +```sql +set username = ''user'' -- (retrieve username from somewhere) + +select ''log'' as component, + ''403 - failed for '' || coalesce($username, ''None'') as output, + ''error'' as priority; +``` + +Output example: + +``` +[2025-09-12T08:33:48.228Z ERROR sqlpage::log from file "403.sql" in statement 7] 403 - failed for user +``` +') \ No newline at end of file From 9c30390e28ea0013bdf903024d2d5c1a7891c122 Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Fri, 12 Sep 2025 10:44:11 +0200 Subject: [PATCH 12/14] fixxed missing values statement --- examples/official-site/sqlpage/migrations/66_log_component.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/official-site/sqlpage/migrations/66_log_component.sql b/examples/official-site/sqlpage/migrations/66_log_component.sql index 46665b5d..6a12b73c 100644 --- a/examples/official-site/sqlpage/migrations/66_log_component.sql +++ b/examples/official-site/sqlpage/migrations/66_log_component.sql @@ -1,7 +1,7 @@ INSERT INTO components(name, icon, description) VALUES ('log', 'logs', 'A Component to log a message to the Servers STDOUT or Log file on page load') -INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'log', * FROM ( +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'log', * FROM (VALUES -- item level ('message', 'The message that needs to be logged', 'ANY', FALSE, FALSE), ('priority', 'The priority which the message should be logged with. Possible values are [''trace'', ''debug'', ''info'', ''warn'', ''error''] and are not case sensitive. If this value is missing or not matching any possible values, the default priority will be ''info''.', 'TEXT', FALSE, TRUE) From 33957ae923dfc9365ff9a4e601c4fc86f37b5e9d Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Fri, 12 Sep 2025 10:58:39 +0200 Subject: [PATCH 13/14] fixxed pipeline errors --- .../sqlpage/migrations/66_log_component.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/66_log_component.sql b/examples/official-site/sqlpage/migrations/66_log_component.sql index 6a12b73c..8df38618 100644 --- a/examples/official-site/sqlpage/migrations/66_log_component.sql +++ b/examples/official-site/sqlpage/migrations/66_log_component.sql @@ -1,10 +1,10 @@ -INSERT INTO components(name, icon, description) VALUES -('log', 'logs', 'A Component to log a message to the Servers STDOUT or Log file on page load') +INSERT INTO component(name, icon, description) VALUES +('log', 'logs', 'A Component to log a message to the Servers STDOUT or Log file on page load'); INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'log', * FROM (VALUES - -- item level - ('message', 'The message that needs to be logged', 'ANY', FALSE, FALSE), - ('priority', 'The priority which the message should be logged with. Possible values are [''trace'', ''debug'', ''info'', ''warn'', ''error''] and are not case sensitive. If this value is missing or not matching any possible values, the default priority will be ''info''.', 'TEXT', FALSE, TRUE) + -- top level + ('message', 'The message that needs to be logged', 'TEXT', TRUE, FALSE), + ('priority', 'The priority which the message should be logged with. Possible values are [''trace'', ''debug'', ''info'', ''warn'', ''error''] and are not case sensitive. If this value is missing or not matching any possible values, the default priority will be ''info''.', 'TEXT', TRUE, TRUE) ) x; INSERT INTO example(component, description) VALUES From 431419a5e09e922b2c4c504cd8cb1b343c13cf8e Mon Sep 17 00:00:00 2001 From: Lenardt Gerhardts Date: Fri, 12 Sep 2025 11:14:12 +0200 Subject: [PATCH 14/14] very simple test case for logger --- tests/sql_test_files/it_works_log.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/sql_test_files/it_works_log.sql diff --git a/tests/sql_test_files/it_works_log.sql b/tests/sql_test_files/it_works_log.sql new file mode 100644 index 00000000..4d3794c7 --- /dev/null +++ b/tests/sql_test_files/it_works_log.sql @@ -0,0 +1,6 @@ +select 'log' as component, + 'Hello, World!' as message, + 'info' as priority; + +select 'text' as component, + 'It works !' as contents; \ No newline at end of file