Use ngx_log_error! macro and fix formatting

- Switched from ngx_log_error_core() to ngx_log_error! macro
- Changed error_log level from debug to info (cleaner output)
- Formatting cleanup across all modules (cargo fmt)
- Removed trailing newlines and fixed indentation

Logging now properly uses nginx's macro system for better
integration with nginx's log handling.
This commit is contained in:
Edward Langley
2025-11-15 17:16:55 -08:00
parent 86b79c0a85
commit 56c6045e3b
14 changed files with 146 additions and 159 deletions

View File

@ -7,4 +7,3 @@ fn main() {
println!("cargo:rustc-link-arg=dynamic_lookup"); println!("cargo:rustc-link-arg=dynamic_lookup");
} }
} }

View File

@ -8,7 +8,7 @@ events {
worker_connections 1024; worker_connections 1024;
} }
error_log logs/error.log debug; error_log logs/error.log info;
http { http {
# Global templates for shared components # Global templates for shared components

View File

@ -41,8 +41,7 @@ impl QueryExecutor for SqliteQueryExecutor {
query: &SqlQuery, query: &SqlQuery,
params: &[(String, String)], params: &[(String, String)],
) -> Result<Vec<HashMap<String, Value>>, String> { ) -> Result<Vec<HashMap<String, Value>>, String> {
query::execute_query(db_path.as_str(), query.as_str(), params) query::execute_query(db_path.as_str(), query.as_str(), params).map_err(|e| e.to_string())
.map_err(|e| e.to_string())
} }
} }
@ -81,7 +80,11 @@ impl TemplateLoader for HandlebarsAdapter {
impl TemplateRenderer for HandlebarsAdapter { impl TemplateRenderer for HandlebarsAdapter {
fn render(&self, template_name: &str, data: &Value) -> Result<String, String> { fn render(&self, template_name: &str, data: &Value) -> Result<String, String> {
unsafe { (*self.registry).render(template_name, data).map_err(|e| e.to_string()) } unsafe {
(*self.registry)
.render(template_name, data)
.map_err(|e| e.to_string())
}
} }
} }
@ -136,9 +139,7 @@ mod tests {
let reg_ptr: *mut Handlebars<'static> = unsafe { std::mem::transmute(&mut reg) }; let reg_ptr: *mut Handlebars<'static> = unsafe { std::mem::transmute(&mut reg) };
let adapter = unsafe { HandlebarsAdapter::new(reg_ptr) }; let adapter = unsafe { HandlebarsAdapter::new(reg_ptr) };
adapter adapter.register_template("test", &template_path).unwrap();
.register_template("test", &template_path)
.unwrap();
let data = serde_json::json!({"name": "World"}); let data = serde_json::json!({"name": "World"});
let rendered = adapter.render("test", &data).unwrap(); let rendered = adapter.render("test", &data).unwrap();
@ -148,4 +149,3 @@ mod tests {
let _ = fs::remove_dir_all(temp_dir); let _ = fs::remove_dir_all(temp_dir);
} }
} }

View File

@ -130,4 +130,3 @@ mod tests {
assert_eq!(config.global_templates_dir, "templates/global"); assert_eq!(config.global_templates_dir, "templates/global");
} }
} }

View File

@ -231,11 +231,9 @@ mod tests {
#[test] #[test]
fn test_resolve_parameters_positional() { fn test_resolve_parameters_positional() {
let bindings = vec![ let bindings = vec![ParameterBinding::Positional {
ParameterBinding::Positional {
variable: NginxVariable::parse("$arg_id").unwrap(), variable: NginxVariable::parse("$arg_id").unwrap(),
}, }];
];
let resolver = MockVariableResolver; let resolver = MockVariableResolver;
let resolved = resolve_parameters(&bindings, &resolver).unwrap(); let resolved = resolve_parameters(&bindings, &resolver).unwrap();
@ -247,12 +245,10 @@ mod tests {
#[test] #[test]
fn test_resolve_parameters_named() { fn test_resolve_parameters_named() {
let bindings = vec![ let bindings = vec![ParameterBinding::Named {
ParameterBinding::Named {
name: ParamName::parse(":book_id").unwrap(), name: ParamName::parse(":book_id").unwrap(),
variable: NginxVariable::parse("$arg_id").unwrap(), variable: NginxVariable::parse("$arg_id").unwrap(),
}, }];
];
let resolver = MockVariableResolver; let resolver = MockVariableResolver;
let resolved = resolve_parameters(&bindings, &resolver).unwrap(); let resolved = resolve_parameters(&bindings, &resolver).unwrap();
@ -264,11 +260,9 @@ mod tests {
#[test] #[test]
fn test_resolve_parameters_literal() { fn test_resolve_parameters_literal() {
let bindings = vec![ let bindings = vec![ParameterBinding::PositionalLiteral {
ParameterBinding::PositionalLiteral {
value: "constant".to_string(), value: "constant".to_string(),
}, }];
];
let resolver = MockVariableResolver; let resolver = MockVariableResolver;
let resolved = resolve_parameters(&bindings, &resolver).unwrap(); let resolved = resolve_parameters(&bindings, &resolver).unwrap();
@ -291,11 +285,8 @@ mod tests {
directory: "templates".to_string(), directory: "templates".to_string(),
}; };
let processor = RequestProcessor::new( let processor =
MockQueryExecutor, RequestProcessor::new(MockQueryExecutor, MockTemplateLoader, MockTemplateRenderer);
MockTemplateLoader,
MockTemplateRenderer,
);
let result = processor.process(&config, &resolved_template, &[], None); let result = processor.process(&config, &resolved_template, &[], None);
@ -303,4 +294,3 @@ mod tests {
assert!(result.unwrap().contains("Rendered")); assert!(result.unwrap().contains("Rendered"));
} }
} }

View File

@ -6,7 +6,7 @@ use crate::domain::{RequestProcessor, ValidatedConfig};
use crate::logging; use crate::logging;
use crate::nginx_helpers::{get_doc_root_and_uri, send_response}; use crate::nginx_helpers::{get_doc_root_and_uri, send_response};
use crate::parsing; use crate::parsing;
use crate::{domain, Module}; use crate::{Module, domain};
use ngx::core::Status; use ngx::core::Status;
use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf}; use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf};
@ -31,15 +31,15 @@ impl<'a> ValidConfigToken<'a> {
/// Process a request with guaranteed valid configuration /// Process a request with guaranteed valid configuration
/// Returns Status directly - no Result needed, types prove correctness /// Returns Status directly - no Result needed, types prove correctness
pub fn process_request( pub fn process_request(request: &mut ngx::http::Request, config: ValidConfigToken) -> Status {
request: &mut ngx::http::Request,
config: ValidConfigToken,
) -> Status {
logging::log( logging::log(
request, request,
logging::LogLevel::Debug, logging::LogLevel::Debug,
"handler", "handler",
&format!("Processing request for {}", request.unparsed_uri().to_str().unwrap_or("unknown")), &format!(
"Processing request for {}",
request.unparsed_uri().to_str().unwrap_or("unknown")
),
); );
// Parse config into validated types // Parse config into validated types
@ -55,13 +55,19 @@ pub fn process_request(
let (doc_root, uri) = match get_doc_root_and_uri(request) { let (doc_root, uri) = match get_doc_root_and_uri(request) {
Ok(paths) => paths, Ok(paths) => paths,
Err(e) => { Err(e) => {
logging::log(request, logging::LogLevel::Error, "nginx", &format!("Path resolution failed: {}", e)); logging::log(
request,
logging::LogLevel::Error,
"nginx",
&format!("Path resolution failed: {}", e),
);
return ngx::http::HTTPStatus::INTERNAL_SERVER_ERROR.into(); return ngx::http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
} }
}; };
// Resolve template path (pure function - cannot fail) // Resolve template path (pure function - cannot fail)
let resolved_template = domain::resolve_template_path(&doc_root, &uri, &validated_config.template_path); let resolved_template =
domain::resolve_template_path(&doc_root, &uri, &validated_config.template_path);
logging::log( logging::log(
request, request,
@ -72,7 +78,8 @@ pub fn process_request(
// Resolve parameters // Resolve parameters
let var_resolver = NginxVariableResolver::new(request); let var_resolver = NginxVariableResolver::new(request);
let resolved_params = match domain::resolve_parameters(&validated_config.parameters, &var_resolver) { let resolved_params =
match domain::resolve_parameters(&validated_config.parameters, &var_resolver) {
Ok(params) => { Ok(params) => {
if !params.is_empty() { if !params.is_empty() {
logging::log( logging::log(
@ -91,7 +98,12 @@ pub fn process_request(
}; };
// Execute and render // Execute and render
let html = execute_with_processor(&validated_config, &resolved_template, &resolved_params, request); let html = execute_with_processor(
&validated_config,
&resolved_template,
&resolved_params,
request,
);
// Send response // Send response
send_response(request, &html) send_response(request, &html)
@ -108,20 +120,11 @@ fn execute_with_processor(
let reg_ptr: *mut handlebars::Handlebars<'static> = unsafe { std::mem::transmute(&mut reg) }; let reg_ptr: *mut handlebars::Handlebars<'static> = unsafe { std::mem::transmute(&mut reg) };
let hbs_adapter = unsafe { HandlebarsAdapter::new(reg_ptr) }; let hbs_adapter = unsafe { HandlebarsAdapter::new(reg_ptr) };
let processor = RequestProcessor::new( let processor = RequestProcessor::new(SqliteQueryExecutor, hbs_adapter, hbs_adapter);
SqliteQueryExecutor,
hbs_adapter,
hbs_adapter,
);
let main_conf = Module::main_conf(request).expect("main config is none"); let main_conf = Module::main_conf(request).expect("main config is none");
let global_dir = if !main_conf.global_templates_dir.is_empty() { let global_dir = if !main_conf.global_templates_dir.is_empty() {
logging::log_template_loading( logging::log_template_loading(request, "global", 0, &main_conf.global_templates_dir);
request,
"global",
0,
&main_conf.global_templates_dir,
);
Some(main_conf.global_templates_dir.as_str()) Some(main_conf.global_templates_dir.as_str())
} else { } else {
None None
@ -137,7 +140,11 @@ fn execute_with_processor(
"success", "success",
&format!( &format!(
"Rendered {} with {} params", "Rendered {} with {} params",
resolved_template.full_path().split('/').last().unwrap_or("template"), resolved_template
.full_path()
.split('/')
.last()
.unwrap_or("template"),
resolved_params.len() resolved_params.len()
), ),
); );
@ -235,4 +242,3 @@ mod tests {
assert!(token.is_none()); assert!(token.is_none());
} }
} }

View File

@ -13,11 +13,11 @@ mod types;
mod variable; mod variable;
use config::{MainConfig, ModuleConfig}; use config::{MainConfig, ModuleConfig};
use handler_types::{process_request, ValidConfigToken}; use handler_types::{ValidConfigToken, process_request};
use ngx::ffi::{ use ngx::ffi::{
NGX_CONF_TAKE1, NGX_CONF_TAKE2, NGX_HTTP_LOC_CONF, NGX_HTTP_MAIN_CONF, NGX_HTTP_MODULE, NGX_CONF_TAKE1, NGX_CONF_TAKE2, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET,
NGX_HTTP_LOC_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, nginx_version, ngx_command_t, ngx_conf_t, NGX_HTTP_MAIN_CONF, NGX_HTTP_MODULE, NGX_RS_MODULE_SIGNATURE, nginx_version, ngx_command_t,
ngx_http_module_t, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t, ngx_conf_t, ngx_http_module_t, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t,
}; };
use ngx::http::{HttpModule, HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule}; use ngx::http::{HttpModule, HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule};
use ngx::{core::Status, http, http_request_handler, ngx_modules, ngx_string}; use ngx::{core::Status, http, http_request_handler, ngx_modules, ngx_string};
@ -209,8 +209,8 @@ extern "C" fn ngx_http_howto_commands_set_template_path(
conf.template_path = (*args.add(1)).to_string(); conf.template_path = (*args.add(1)).to_string();
// Set the content handler for this location // Set the content handler for this location
let clcf = NgxHttpCoreModule::location_conf_mut(&*cf) let clcf =
.expect("failed to get core location conf"); NgxHttpCoreModule::location_conf_mut(&*cf).expect("failed to get core location conf");
clcf.handler = Some(howto_access_handler); clcf.handler = Some(howto_access_handler);
}; };

View File

@ -23,21 +23,13 @@ pub fn log(request: &mut Request, level: LogLevel, module: &str, message: &str)
LogLevel::Debug => 7, // NGX_LOG_DEBUG LogLevel::Debug => 7, // NGX_LOG_DEBUG
}; };
let r: *mut ngx::ffi::ngx_http_request_t = request.into();
unsafe { unsafe {
let connection = (*r).connection; let connection = (*r).connection;
if !connection.is_null() { if !connection.is_null() {
let log_ptr = (*connection).log; let log = (*connection).log;
if !log_ptr.is_null() { if !log.is_null() {
// ngx_log_error! requires a string literal, so we use the C API directly ngx_log_error!(log_level, log, "[sqlite-serve:{}] {}", module, message);
let c_msg = std::ffi::CString::new(format!("[sqlite-serve:{}] {}", module, message))
.unwrap_or_else(|_| std::ffi::CString::new("log message error").unwrap());
ngx::ffi::ngx_log_error_core(
log_level as ngx::ffi::ngx_uint_t,
log_ptr,
0,
c_msg.as_ptr(),
);
} }
} }
} }
@ -128,4 +120,3 @@ mod tests {
assert_eq!(levels.len(), 4); assert_eq!(levels.len(), 4);
} }
} }

View File

@ -28,7 +28,6 @@ pub fn get_doc_root_and_uri(request: &mut Request) -> Result<(String, String), S
/// Create and send nginx response buffer /// Create and send nginx response buffer
pub fn send_response(request: &mut Request, body: &str) -> Status { pub fn send_response(request: &mut Request, body: &str) -> Status {
// Create output buffer // Create output buffer
let mut buf = match request.pool().create_buffer_from_str(body) { let mut buf = match request.pool().create_buffer_from_str(body) {
Some(buf) => buf, Some(buf) => buf,
@ -57,8 +56,12 @@ pub fn send_response(request: &mut Request, body: &str) -> Status {
/// Log and return error status (deprecated - use logging module directly) /// Log and return error status (deprecated - use logging module directly)
#[allow(dead_code)] #[allow(dead_code)]
pub fn log_error(request: &mut Request, context: &str, error: &str, status: http::HTTPStatus) -> Status { pub fn log_error(
request: &mut Request,
context: &str,
error: &str,
status: http::HTTPStatus,
) -> Status {
logging::log(request, logging::LogLevel::Error, context, error); logging::log(request, logging::LogLevel::Error, context, error);
status.into() status.into()
} }

View File

@ -2,15 +2,16 @@
use crate::config::ModuleConfig; use crate::config::ModuleConfig;
use crate::domain::ValidatedConfig; use crate::domain::ValidatedConfig;
use crate::types::{DatabasePath, NginxVariable, ParamName, ParameterBinding, SqlQuery, TemplatePath}; use crate::types::{
DatabasePath, NginxVariable, ParamName, ParameterBinding, SqlQuery, TemplatePath,
};
/// Parse raw configuration into validated domain configuration /// Parse raw configuration into validated domain configuration
pub fn parse_config(config: &ModuleConfig) -> Result<ValidatedConfig, String> { pub fn parse_config(config: &ModuleConfig) -> Result<ValidatedConfig, String> {
let db_path = DatabasePath::parse(&config.db_path) let db_path =
.map_err(|e| format!("invalid db_path: {}", e))?; DatabasePath::parse(&config.db_path).map_err(|e| format!("invalid db_path: {}", e))?;
let query = SqlQuery::parse(&config.query) let query = SqlQuery::parse(&config.query).map_err(|e| format!("invalid query: {}", e))?;
.map_err(|e| format!("invalid query: {}", e))?;
let template_path = TemplatePath::parse(&config.template_path) let template_path = TemplatePath::parse(&config.template_path)
.map_err(|e| format!("invalid template_path: {}", e))?; .map_err(|e| format!("invalid template_path: {}", e))?;
@ -26,9 +27,7 @@ pub fn parse_config(config: &ModuleConfig) -> Result<ValidatedConfig, String> {
} }
/// Parse parameter configuration into typed bindings /// Parse parameter configuration into typed bindings
fn parse_parameter_bindings( fn parse_parameter_bindings(params: &[(String, String)]) -> Result<Vec<ParameterBinding>, String> {
params: &[(String, String)],
) -> Result<Vec<ParameterBinding>, String> {
let mut bindings = Vec::new(); let mut bindings = Vec::new();
for (param_name, var_name) in params { for (param_name, var_name) in params {
@ -169,4 +168,3 @@ mod tests {
} }
} }
} }

View File

@ -31,11 +31,9 @@ pub fn execute_query(
let value: Value = match row.get_ref(i)? { let value: Value = match row.get_ref(i)? {
rusqlite::types::ValueRef::Null => Value::Null, rusqlite::types::ValueRef::Null => Value::Null,
rusqlite::types::ValueRef::Integer(v) => Value::Number(v.into()), rusqlite::types::ValueRef::Integer(v) => Value::Number(v.into()),
rusqlite::types::ValueRef::Real(v) => { rusqlite::types::ValueRef::Real(v) => serde_json::Number::from_f64(v)
serde_json::Number::from_f64(v)
.map(Value::Number) .map(Value::Number)
.unwrap_or(Value::Null) .unwrap_or(Value::Null),
}
rusqlite::types::ValueRef::Text(v) => { rusqlite::types::ValueRef::Text(v) => {
Value::String(String::from_utf8_lossy(v).to_string()) Value::String(String::from_utf8_lossy(v).to_string())
} }
@ -90,10 +88,7 @@ mod tests {
{ {
let conn = Connection::open(temp_path).unwrap(); let conn = Connection::open(temp_path).unwrap();
conn.execute( conn.execute("CREATE TABLE test (id INTEGER, name TEXT, value REAL)", [])
"CREATE TABLE test (id INTEGER, name TEXT, value REAL)",
[],
)
.unwrap(); .unwrap();
conn.execute( conn.execute(
"INSERT INTO test VALUES (1, 'first', 1.5), (2, 'second', 2.5)", "INSERT INTO test VALUES (1, 'first', 1.5), (2, 'second', 2.5)",
@ -104,10 +99,7 @@ mod tests {
let results = execute_query(temp_path, "SELECT * FROM test ORDER BY id", &[]).unwrap(); let results = execute_query(temp_path, "SELECT * FROM test ORDER BY id", &[]).unwrap();
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
assert_eq!( assert_eq!(results[0].get("id").unwrap(), &Value::Number(1.into()));
results[0].get("id").unwrap(),
&Value::Number(1.into())
);
assert_eq!( assert_eq!(
results[0].get("name").unwrap(), results[0].get("name").unwrap(),
&Value::String("first".to_string()) &Value::String("first".to_string())
@ -157,7 +149,10 @@ mod tests {
{ {
let conn = Connection::open(temp_path).unwrap(); let conn = Connection::open(temp_path).unwrap();
conn.execute("CREATE TABLE books (id INTEGER, title TEXT, year INTEGER)", []) conn.execute(
"CREATE TABLE books (id INTEGER, title TEXT, year INTEGER)",
[],
)
.unwrap(); .unwrap();
conn.execute( conn.execute(
"INSERT INTO books VALUES (1, 'Old Book', 2000), (2, 'New Book', 2020), (3, 'Newer Book', 2023)", "INSERT INTO books VALUES (1, 'Old Book', 2000), (2, 'New Book', 2020), (3, 'Newer Book', 2023)",
@ -218,10 +213,7 @@ mod tests {
let row = &results[0]; let row = &results[0];
assert_eq!(row.get("id").unwrap(), &Value::Number(42.into())); assert_eq!(row.get("id").unwrap(), &Value::Number(42.into()));
assert_eq!( assert_eq!(row.get("name").unwrap(), &Value::String("test".to_string()));
row.get("name").unwrap(),
&Value::String("test".to_string())
);
assert_eq!(row.get("price").unwrap().as_f64().unwrap(), 3.14); assert_eq!(row.get("price").unwrap().as_f64().unwrap(), 3.14);
assert_eq!( assert_eq!(
row.get("data").unwrap(), row.get("data").unwrap(),
@ -242,7 +234,10 @@ mod tests {
{ {
let conn = Connection::open(temp_path).unwrap(); let conn = Connection::open(temp_path).unwrap();
conn.execute("CREATE TABLE books (id INTEGER, genre TEXT, rating REAL)", []) conn.execute(
"CREATE TABLE books (id INTEGER, genre TEXT, rating REAL)",
[],
)
.unwrap(); .unwrap();
conn.execute( conn.execute(
"INSERT INTO books VALUES "INSERT INTO books VALUES
@ -324,4 +319,3 @@ mod tests {
let _ = fs::remove_file(temp_path); let _ = fs::remove_file(temp_path);
} }
} }

View File

@ -157,4 +157,3 @@ mod tests {
let _ = fs::remove_dir_all(temp_dir); let _ = fs::remove_dir_all(temp_dir);
} }
} }

View File

@ -157,10 +157,20 @@ impl ParamName {
/// A parameter binding (param name + variable or literal) /// A parameter binding (param name + variable or literal)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ParameterBinding { pub enum ParameterBinding {
Positional { variable: NginxVariable }, Positional {
PositionalLiteral { value: String }, variable: NginxVariable,
Named { name: ParamName, variable: NginxVariable }, },
NamedLiteral { name: ParamName, value: String }, PositionalLiteral {
value: String,
},
Named {
name: ParamName,
variable: NginxVariable,
},
NamedLiteral {
name: ParamName,
value: String,
},
} }
impl ParameterBinding { impl ParameterBinding {
@ -300,4 +310,3 @@ mod tests {
assert!(result.unwrap_err().contains(":")); assert!(result.unwrap_err().contains(":"));
} }
} }

View File

@ -103,4 +103,3 @@ mod tests {
} }
} }
} }