From 0cbfe778ecfa5eb77cb3a02b4d093d94473ee906 Mon Sep 17 00:00:00 2001 From: 81reap Date: Thu, 20 Aug 2026 22:37:02 -0400 Subject: [PATCH] fix(rust): enforce lint on all files + fix lint issues --- Cargo.toml | 13 +++++ build.rs | 38 +++++++------- src/app_config.rs | 6 +-- src/filesystem.rs | 8 +-- src/lib.rs | 5 +- src/main.rs | 2 +- src/render.rs | 32 +++++------- src/telemetry.rs | 4 +- src/template_helpers.rs | 28 +++++----- src/templates.rs | 2 +- src/webserver/database/connect.rs | 11 ++-- src/webserver/database/csv_import.rs | 8 +-- src/webserver/database/error_highlighting.rs | 10 ++-- src/webserver/database/execute_queries.rs | 12 ++--- src/webserver/database/sql.rs | 2 +- src/webserver/database/sql/rewrite.rs | 2 +- src/webserver/database/sql/statement.rs | 2 +- src/webserver/database/sql_to_json.rs | 20 +++---- .../sqlpage_functions/function_traits.rs | 2 +- .../sqlpage_functions/functions/send_mail.rs | 4 +- .../sqlpage_functions/functions/user_info.rs | 4 +- .../sqlpage_functions/http_fetch_request.rs | 2 +- .../sqlpage_functions/url_parameters.rs | 12 ++--- src/webserver/error.rs | 2 +- src/webserver/http.rs | 20 +++---- src/webserver/http_request_info.rs | 14 +++-- src/webserver/https.rs | 2 +- src/webserver/lambda_http.rs | 4 +- src/webserver/oidc.rs | 17 +++--- src/webserver/response_writer.rs | 6 +-- src/webserver/routing.rs | 3 +- src/webserver/static_content.rs | 10 ++-- tests/common/mod.rs | 35 +++++++------ tests/core/mod.rs | 10 ++-- tests/data_formats/mod.rs | 4 +- tests/errors/mod.rs | 14 ++--- tests/oidc/mod.rs | 52 +++++++++---------- tests/requests/mod.rs | 10 ++-- tests/sql_test_files/mod.rs | 32 +++++------- tests/uploads/mod.rs | 4 +- 40 files changed, 226 insertions(+), 242 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3119c089a..5e4e8025a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,19 @@ repository = "https://github.com/sqlpage/SQLPage" documentation = "https://docs.rs/sqlpage" include = ["/src", "/README.md", "/build.rs", "/sqlpage"] +[lints.rust] +elided_lifetimes_in_paths = "deny" +unreachable_pub = "deny" +unused_qualifications = "deny" + +[lints.clippy] +pedantic = { level = "deny", priority = -1 } +missing_errors_doc = "allow" +missing_panics_doc = "allow" +dbg_macro = "deny" +todo = "deny" +unimplemented = "deny" + [profile.superoptimized] inherits = "release" strip = "debuginfo" diff --git a/build.rs b/build.rs index 6f9f4a963..d472bf611 100644 --- a/build.rs +++ b/build.rs @@ -84,11 +84,7 @@ async fn process_input_file(client: &awc::Client, path_out: &Path, original: Fil .expect("Unable to write compressed frontend asset"); } -async fn copy_url_to_opened_file( - client: &awc::Client, - url: &str, - outfile: &mut impl std::io::Write, -) { +async fn copy_url_to_opened_file(client: &awc::Client, url: &str, outfile: &mut impl Write) { // If the file has been downloaded manually, use it let cached_file_path = make_url_path(url); if !cached_file_path.exists() { @@ -98,9 +94,9 @@ async fn copy_url_to_opened_file( copy_cached_to_opened_file(&cached_file_path, outfile); } -fn copy_cached_to_opened_file(source: &Path, outfile: &mut impl std::io::Write) { - let reader = std::fs::File::open(source).unwrap(); - let mut buf = std::io::BufReader::new(reader); +fn copy_cached_to_opened_file(source: &Path, outfile: &mut impl Write) { + let reader = File::open(source).unwrap(); + let mut buf = BufReader::new(reader); // Not async, but performance should not really matter here std::io::copy(&mut buf, outfile).unwrap(); } @@ -112,9 +108,12 @@ async fn download_url_to_path(client: &awc::Client, url: &str, path: &Path) { loop { match client.get(url).send().await { Ok(mut resp) => { - if resp.status() != 200 { - panic!("Received {} status code from {}", resp.status(), url); - } + assert!( + resp.status() == 200, + "Received {} status code from {}", + resp.status(), + url + ); let bytes = resp.body().limit(128 * 1024 * 1024).await.unwrap(); std::fs::write(path, &bytes) .expect("Failed to write external frontend dependency to local file"); @@ -122,7 +121,7 @@ async fn download_url_to_path(client: &awc::Client, url: &str, path: &Path) { } Err(err) => { if attempt >= max_attempts { - let path = make_url_path(url); + let path = make_url_path(url).display().to_string(); panic!( "We need to download external frontend dependencies to build the static frontend. \n\ Could not download static asset after {max_attempts} attempts. You can manually download the file with: \n\ @@ -187,15 +186,16 @@ async fn download_tabler_icons(client: Rc, sprite_url: &str) { file.write_all(b"]").unwrap(); } +fn take_between<'a>(s: &mut &'a str, start: &str, end: &str) -> Option<&'a str> { + let start_index = s.find(start)?; + let end_index = s[start_index + start.len()..].find(end)?; + let result = &s[start_index + start.len()..][..end_index]; + *s = &s[start_index + start.len() + end_index + end.len()..]; + Some(result) +} + fn extract_icons_from_sprite(sprite_content: &[u8], mut callback: impl FnMut(&str, &str)) { let mut sprite_str = std::str::from_utf8(sprite_content).unwrap(); - fn take_between<'a>(s: &mut &'a str, start: &str, end: &str) -> Option<&'a str> { - let start_index = s.find(start)?; - let end_index = s[start_index + start.len()..].find(end)?; - let result = &s[start_index + start.len()..][..end_index]; - *s = &s[start_index + start.len() + end_index + end.len()..]; - Some(result) - } while let Some(mut symbol_tag) = take_between(&mut sprite_str, "") { let id = take_between(&mut symbol_tag, "id=\"tabler-", "\"").expect("id not found"); let content_start = symbol_tag.find('>').unwrap() + 1; diff --git a/src/app_config.rs b/src/app_config.rs index 7569a0186..ccf4f553c 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -460,7 +460,7 @@ fn configuration_directory() -> PathBuf { PathBuf::from("./sqlpage") } -fn cannonicalize_if_possible(path: &std::path::Path) -> PathBuf { +fn cannonicalize_if_possible(path: &Path) -> PathBuf { path.canonicalize().unwrap_or_else(|_| path.to_owned()) } @@ -674,7 +674,7 @@ fn create_default_database(configuration_directory: &Path) -> String { #[cfg(any(test, not(feature = "lambda-web")))] fn encode_uri(path: &Path) -> std::borrow::Cow<'_, str> { - const ASCII_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC + const ASCII_SET: &AsciiSet = &percent_encoding::NON_ALPHANUMERIC .remove(b'-') .remove(b'_') .remove(b'.') @@ -1000,7 +1000,7 @@ mod test { let _lock = ENV_LOCK .lock() .expect("Another test panicked while holding the lock"); - let temp_dir = std::env::temp_dir().join("sqlpage_test"); + let temp_dir = env::temp_dir().join("sqlpage_test"); std::fs::create_dir_all(&temp_dir).unwrap(); let config_file_path = temp_dir.join("sqlpage.json"); let config_web_dir = temp_dir.join("config/web"); diff --git a/src/filesystem.rs b/src/filesystem.rs index 42284dc41..4b29483f3 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -49,7 +49,7 @@ pub(crate) struct FileSystem { } impl FileSystem { - pub async fn init(local_root: impl Into, db: &Database) -> Self { + pub(crate) async fn init(local_root: impl Into, db: &Database) -> Self { Self { local_root: local_root.into(), db_fs_queries: match DbFsQueries::init(db).await { @@ -68,7 +68,7 @@ impl FileSystem { } } - pub async fn modified_since( + pub(crate) async fn modified_since( &self, app_state: &AppState, access: FileAccess<'_>, @@ -99,7 +99,7 @@ impl FileSystem { } } - pub async fn read_to_string( + pub(crate) async fn read_to_string( &self, app_state: &AppState, access: FileAccess<'_>, @@ -127,7 +127,7 @@ impl FileSystem { }) } - pub async fn read_file( + pub(crate) async fn read_file( &self, app_state: &AppState, access: FileAccess<'_>, diff --git a/src/lib.rs b/src/lib.rs index 3b4d869f8..ba58d5f1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -#![deny(clippy::pedantic)] -#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] - //! [SQLPage](https://sql-page.com) is a high-performance web server that converts SQL queries //! into dynamic web applications by rendering [handlebars templates](https://sql-page.com/custom_components.sql) //! with data coming from SQL queries declared in `.sql` files. @@ -135,7 +132,7 @@ impl AppState { ), ); - let oidc_state = crate::webserver::oidc::initialize_oidc_state(config).await?; + let oidc_state = webserver::oidc::initialize_oidc_state(config).await?; let telemetry_metrics = TelemetryMetrics::new(&db.connection, db.info.database_type.otel_name()); diff --git a/src/main.rs b/src/main.rs index ec7d4bf38..b73369228 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ fn init_logging() -> anyhow::Result<()> { let otel_active = telemetry::init_telemetry()?; match load_env { - Ok(path) => log::info!("Loaded environment variables from {path:?}"), + Ok(path) => log::info!("Loaded environment variables from {}", path.display()), Err(dotenvy::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => log::debug!( "No .env file found, using only environment variables and configuration files" ), diff --git a/src/render.rs b/src/render.rs index 49a1efd1d..7cd861efe 100644 --- a/src/render.rs +++ b/src/render.rs @@ -367,7 +367,7 @@ impl HeaderContext { } fn log(self, data: &JsonValue) -> anyhow::Result { - handle_log_component(&self.request_context.source_path, Option::None, data)?; + handle_log_component(&self.request_context.source_path, None, data)?; Ok(PageContext::Header(self)) } @@ -555,7 +555,7 @@ impl AnyRenderBodyContext { } } -pub struct JsonBodyRenderer { +pub struct JsonBodyRenderer { writer: W, is_first: bool, prefix: &'static [u8], @@ -563,7 +563,7 @@ pub struct JsonBodyRenderer { separator: &'static [u8], } -impl JsonBodyRenderer { +impl JsonBodyRenderer { pub fn new_array(writer: W) -> JsonBodyRenderer { let mut renderer = Self { writer, @@ -741,7 +741,7 @@ impl CsvBodyRenderer { } #[allow(clippy::module_name_repetitions)] -pub struct HtmlRenderContext { +pub struct HtmlRenderContext { app_state: Arc, pub writer: W, current_component: Option, @@ -754,7 +754,7 @@ const DEFAULT_COMPONENT: &str = "table"; const PAGE_SHELL_COMPONENT: &str = "shell"; const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty"; -impl HtmlRenderContext { +impl HtmlRenderContext { pub async fn new( app_state: Arc, request_context: RequestContext, @@ -1023,11 +1023,11 @@ fn handle_log_component( Ok(()) } -struct HandlebarWriterOutput(W); +struct HandlebarWriterOutput(W); -impl handlebars::Output for HandlebarWriterOutput { +impl handlebars::Output for HandlebarWriterOutput { fn write(&mut self, seg: &str) -> std::io::Result<()> { - std::io::Write::write_all(&mut self.0, seg.as_bytes()) + Write::write_all(&mut self.0, seg.as_bytes()) } } @@ -1043,7 +1043,7 @@ pub struct SplitTemplateRenderer { } const _: () = assert!( - std::mem::size_of::() <= 64, + size_of::() <= 64, "SplitTemplateRenderer should be small enough to be allocated on the stack" ); @@ -1072,11 +1072,7 @@ impl SplitTemplateRenderer { .unwrap_or_default() } - fn render_start( - &mut self, - writer: W, - data: JsonValue, - ) -> Result<(), RenderError> { + fn render_start(&mut self, writer: W, data: JsonValue) -> Result<(), RenderError> { log::trace!( "Starting rendering of a template{} with the following top-level parameters: {data}", self.split_template @@ -1108,11 +1104,7 @@ impl SplitTemplateRenderer { Ok(()) } - fn render_item( - &mut self, - writer: W, - data: JsonValue, - ) -> Result<(), RenderError> { + fn render_item(&mut self, writer: W, data: JsonValue) -> Result<(), RenderError> { log::trace!("Rendering a new item in the page: {data:?}"); if let Some(local_vars) = self.local_vars.take() { let mut render_context = handlebars::RenderContext::new(None); @@ -1144,7 +1136,7 @@ impl SplitTemplateRenderer { Ok(()) } - fn render_end(&mut self, writer: W) -> Result<(), RenderError> { + fn render_end(&mut self, writer: W) -> Result<(), RenderError> { log::trace!( "Closing a template {}", self.split_template diff --git a/src/telemetry.rs b/src/telemetry.rs index 8c83b2615..5c898db83 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -450,7 +450,7 @@ mod logfmt { } impl LogfmtLayer { - pub fn new() -> Self { + pub(super) fn new() -> Self { Self { stdout_colors: io::stdout().is_terminal(), stderr_colors: io::stderr().is_terminal(), @@ -458,7 +458,7 @@ mod logfmt { } } - pub fn test_writer() -> Self { + pub(super) fn test_writer() -> Self { Self { stdout_colors: false, stderr_colors: false, diff --git a/src/template_helpers.rs b/src/template_helpers.rs index 24ec944e7..d584c0c18 100644 --- a/src/template_helpers.rs +++ b/src/template_helpers.rs @@ -168,7 +168,7 @@ fn to_array_helper(v: &JsonValue) -> JsonValue { struct StaticPathHelper(String); impl CanHelp for StaticPathHelper { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { let static_file = match args { [v] => v.value(), _ => return Err("expected one argument".to_string()), @@ -192,7 +192,7 @@ impl CanHelp for StaticPathHelper { struct AppConfigHelper(AppConfig); impl CanHelp for AppConfigHelper { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { let static_file = match args { [v] => v.value(), _ => return Err("expected one argument".to_string()), @@ -223,7 +223,7 @@ impl HelperDef for IconImgHelper { _rc: &mut handlebars::RenderContext<'reg, 'rc>, writer: &mut dyn handlebars::Output, ) -> handlebars::HelperResult { - let null = handlebars::JsonValue::Null; + let null = JsonValue::Null; let [name, size] = [0, 1].map(|i| helper.params().get(i).map_or(&null, PathAndJson::value)); let size = size.as_u64().unwrap_or(24); let content = name.as_str().and_then(|name| ICON_MAP.get(name)); @@ -315,7 +315,7 @@ impl MarkdownHelper { } impl CanHelp for MarkdownHelper { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { let (markdown_src_value, preset_name) = match args { [v] => (v.value(), "default"), [v, preset] => { @@ -502,7 +502,7 @@ fn loose_eq_helper(a: &JsonValue, b: &JsonValue) -> JsonValue { pub struct HelperCheckTruthy(bool); impl CanHelp for HelperCheckTruthy { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { for arg in args { if arg.value().is_truthy(false) == self.0 { return Ok(arg.value().clone()); @@ -517,11 +517,11 @@ impl CanHelp for HelperCheckTruthy { } trait CanHelp: Send + Sync + 'static { - fn call(&self, v: &[PathAndJson]) -> Result; + fn call(&self, v: &[PathAndJson<'_>]) -> Result; } impl CanHelp for H0 { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { match args { [] => Ok(self()), _ => Err("expected no arguments".to_string()), @@ -530,7 +530,7 @@ impl CanHelp for H0 { } impl CanHelp for H { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { match args { [v] => Ok(self(v.value())), _ => Err("expected one argument".to_string()), @@ -539,7 +539,7 @@ impl CanHelp for H { } impl CanHelp for EH { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { match args { [v] => self(v.value()).map_err(|e| e.to_string()), _ => Err("expected one argument".to_string()), @@ -548,7 +548,7 @@ impl CanHelp for EH { } impl CanHelp for HH { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { match args { [a, b] => Ok(self(a.value(), b.value())), _ => Err("expected two arguments".to_string()), @@ -557,7 +557,7 @@ impl CanHelp for HH { } impl CanHelp for HHH { - fn call(&self, args: &[PathAndJson]) -> Result { + fn call(&self, args: &[PathAndJson<'_>]) -> Result { match args { [a, b, c] => Ok(self(a.value(), b.value(), c.value())), _ => Err("expected three arguments".to_string()), @@ -569,14 +569,14 @@ struct JFun { name: &'static str, fun: F, } -impl handlebars::HelperDef for JFun { +impl HelperDef for JFun { fn call_inner<'reg: 'rc, 'rc>( &self, helper: &handlebars::Helper<'rc>, _r: &'reg Handlebars<'reg>, _: &'rc Context, _rc: &mut handlebars::RenderContext<'reg, 'rc>, - ) -> Result, RenderError> { + ) -> Result, RenderError> { let result = self .fun .call(helper.params().as_slice()) @@ -585,7 +585,7 @@ impl handlebars::HelperDef for JFun { } } -fn register_helper(h: &mut Handlebars, name: &'static str, fun: impl CanHelp) { +fn register_helper(h: &mut Handlebars<'_>, name: &'static str, fun: impl CanHelp) { h.register_helper(name, Box::new(JFun { name, fun })); } diff --git a/src/templates.rs b/src/templates.rs index af001f758..3c36506fb 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -78,7 +78,7 @@ pub struct AllTemplates { split_templates: FileCache, } -const STATIC_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/sqlpage/templates"); +const STATIC_TEMPLATES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/sqlpage/templates"); impl AllTemplates { pub fn init(config: &AppConfig) -> anyhow::Result { diff --git a/src/webserver/database/connect.rs b/src/webserver/database/connect.rs index 4c7177686..dacfcec54 100644 --- a/src/webserver/database/connect.rs +++ b/src/webserver/database/connect.rs @@ -27,10 +27,7 @@ impl Database { set_database_password(&mut connect_options, password); } connect_options.log_statements(log::LevelFilter::Trace); - connect_options.log_slow_statements( - log::LevelFilter::Warn, - std::time::Duration::from_millis(250), - ); + connect_options.log_slow_statements(log::LevelFilter::Warn, Duration::from_millis(250)); log::debug!( "Connecting to a {:?} database on {}", connect_options.kind(), @@ -147,7 +144,7 @@ fn add_on_return_to_pool(config: &AppConfig, pool_options: PoolOptions) -> } fn on_return_to_pool( - conn: &mut sqlx::any::AnyConnection, + conn: &mut AnyConnection, meta: sqlx::pool::PoolConnectionMetadata, sql: std::sync::Arc, ) -> BoxFuture<'_, Result> { @@ -229,9 +226,9 @@ fn set_custom_connect_options_sqlite( ) { for extension_name in &config.sqlite_extensions { log::info!("Loading SQLite extension: {extension_name}"); - *sqlite_options = std::mem::take(sqlite_options).extension(extension_name.clone()); + *sqlite_options = take(sqlite_options).extension(extension_name.clone()); } - *sqlite_options = std::mem::take(sqlite_options) + *sqlite_options = take(sqlite_options) .collation("NOCASE", |a, b| a.to_lowercase().cmp(&b.to_lowercase())) .function(make_sqlite_fun("upper", str::to_uppercase)) .function(make_sqlite_fun("lower", str::to_lowercase)); diff --git a/src/webserver/database/csv_import.rs b/src/webserver/database/csv_import.rs index c1c25e264..d113695c2 100644 --- a/src/webserver/database/csv_import.rs +++ b/src/webserver/database/csv_import.rs @@ -34,9 +34,9 @@ pub(super) struct CsvImport { } enum CopyCsvOption<'a> { - Legacy(&'a sqlparser::ast::CopyLegacyOption), - CopyLegacyCsvOption(&'a sqlparser::ast::CopyLegacyCsvOption), - New(&'a sqlparser::ast::CopyOption), + Legacy(&'a CopyLegacyOption), + CopyLegacyCsvOption(&'a CopyLegacyCsvOption), + New(&'a CopyOption), } impl CopyCsvOption<'_> { @@ -105,7 +105,7 @@ pub(super) fn extract_csv_copy_statement(stmt: &mut Statement) -> Option = legacy_options + let all_options: Vec> = legacy_options .iter() .flat_map(|o| match o { CopyLegacyOption::Csv(o) => { diff --git a/src/webserver/database/error_highlighting.rs b/src/webserver/database/error_highlighting.rs index 57d1356ff..0080607d3 100644 --- a/src/webserver/database/error_highlighting.rs +++ b/src/webserver/database/error_highlighting.rs @@ -97,7 +97,7 @@ impl std::error::Error for NicePositionedError { /// Display a database error without any position information #[must_use] -pub fn display_db_error( +pub(super) fn display_db_error( source_file: &Path, query: &str, db_err: sqlx::error::Error, @@ -112,7 +112,7 @@ pub fn display_db_error( /// Display a database error with a highlighted line and character offset. #[must_use] -pub fn display_stmt_db_error( +pub(super) fn display_stmt_db_error( source_file: &Path, query: &str, query_position: SourceSpan, @@ -127,7 +127,7 @@ pub fn display_stmt_db_error( } #[must_use] -pub fn display_stmt_error( +pub(super) fn display_stmt_error( source_file: &Path, query_position: SourceSpan, error: anyhow::Error, @@ -140,14 +140,14 @@ pub fn display_stmt_error( } /// Highlight a line with a character offset. -pub fn highlight_line_offset(msg: &mut W, line: &str, offset: usize) { +pub(super) fn highlight_line_offset(msg: &mut W, line: &str, offset: usize) { writeln!(msg, "{line}").unwrap(); writeln!(msg, "{}⬆️", " ".repeat(offset)).unwrap(); } /// Highlight an error given a line and a character offset /// line and `col_num` are 1-based -pub fn quote_source_with_highlight(source: &str, line_num: u64, col_num: u64) -> String { +pub(super) fn quote_source_with_highlight(source: &str, line_num: u64, col_num: u64) -> String { let mut msg = String::new(); let col_num_usize = usize::try_from(col_num) .unwrap_or_default() diff --git a/src/webserver/database/execute_queries.rs b/src/webserver/database/execute_queries.rs index 0212c7c6e..2bf6f6474 100644 --- a/src/webserver/database/execute_queries.rs +++ b/src/webserver/database/execute_queries.rs @@ -298,7 +298,7 @@ pub fn stream_query_results_with_conn<'a>( fn with_stmt_position( source_file: &Path, - query_position: super::sql::SourceSpan, + query_position: SourceSpan, error: anyhow::Error, ) -> anyhow::Error { if error.downcast_ref::().is_some() { @@ -335,7 +335,7 @@ async fn execute_single_row( query: &SingleRowQuery, req: &ExecutionContext, db_connection: &mut DbConn, -) -> anyhow::Result { +) -> anyhow::Result { let mut map = serde_json::Map::with_capacity(query.columns.len()); let mut inputs = NoInputs; for column in &query.columns { @@ -346,7 +346,7 @@ async fn execute_single_row( .into_json(); map = add_value_to_map(map, (column.name.clone(), value)); } - Ok(serde_json::Value::Object(map)) + Ok(Value::Object(map)) } async fn try_rollback_transaction(db_connection: &mut AnyConnection) { @@ -732,7 +732,7 @@ async fn evaluate_computed_columns( result: &mut QueryResult, db_connection: &mut DbConn, ) -> anyhow::Result<()> { - if let DbItem::Row(serde_json::Value::Object(results)) = &mut result.item { + if let DbItem::Row(Value::Object(results)) = &mut result.item { for column in columns { let value = column .value @@ -1011,7 +1011,7 @@ mod tests { sqlpage.exception.details = tracing::field::Empty, db.response.returned_rows = tracing::field::Empty, ); - let metrics = crate::telemetry_metrics::TelemetryMetrics::default(); + let metrics = TelemetryMetrics::default(); let query_metrics = DbQueryMetricsContext::new(span.clone(), "SELECT".to_string(), "sqlite", &metrics); query_metrics.record_success(3); @@ -1035,7 +1035,7 @@ mod tests { db.response.returned_rows = tracing::field::Empty, ); let error = anyhow!("query failed").context("while executing SELECT 1"); - let metrics = crate::telemetry_metrics::TelemetryMetrics::default(); + let metrics = TelemetryMetrics::default(); let query_metrics = DbQueryMetricsContext::new(span.clone(), "SELECT".to_string(), "sqlite", &metrics); query_metrics.record_error(2, &error); diff --git a/src/webserver/database/sql.rs b/src/webserver/database/sql.rs index 40f40d3c2..3ceeb0509 100644 --- a/src/webserver/database/sql.rs +++ b/src/webserver/database/sql.rs @@ -186,7 +186,7 @@ fn extract_set_variable(statement: &mut Statement, database: &DbInfo) -> Option< ) } -fn syntax_error(error: ParserError, parser: &Parser, sql: &str) -> FileStatement { +fn syntax_error(error: ParserError, parser: &Parser<'_>, sql: &str) -> FileStatement { let Span { start: Location { line: start_line, diff --git a/src/webserver/database/sql/rewrite.rs b/src/webserver/database/sql/rewrite.rs index 9ff49ea74..5b090f84b 100644 --- a/src/webserver/database/sql/rewrite.rs +++ b/src/webserver/database/sql/rewrite.rs @@ -443,7 +443,7 @@ fn has_single_row_shape(statement: &SqlStatement) -> bool { && select.from.is_empty() && select.lateral_views.is_empty() && select.selection.is_none() - && select.group_by == sqlparser::ast::GroupByExpr::Expressions(vec![], vec![]) + && select.group_by == GroupByExpr::Expressions(vec![], vec![]) && select.cluster_by.is_empty() && select.distribute_by.is_empty() && select.sort_by.is_empty() diff --git a/src/webserver/database/sql/statement.rs b/src/webserver/database/sql/statement.rs index cb83ba7c0..169a3cce4 100644 --- a/src/webserver/database/sql/statement.rs +++ b/src/webserver/database/sql/statement.rs @@ -55,7 +55,7 @@ pub(in crate::webserver::database) struct DatabaseQuery { impl DatabaseQuery { /// Whether row evaluation needs the request's existing connection and /// must therefore wait until the database stream is closed. - pub fn must_buffer_rows(&self) -> bool { + pub(crate) fn must_buffer_rows(&self) -> bool { self.computed_columns .iter() .any(|column| column.value.contains_function(SqlPageFunctionName::run_sql)) diff --git a/src/webserver/database/sql_to_json.rs b/src/webserver/database/sql_to_json.rs index 5d34fa187..359471d2a 100644 --- a/src/webserver/database/sql_to_json.rs +++ b/src/webserver/database/sql_to_json.rs @@ -31,7 +31,7 @@ use sqlx::types::Type; use sqlx::value::ValueRef; #[cfg(test)] -pub fn row_to_json(row: &AnyRow) -> Value { +pub(super) fn row_to_json(row: &AnyRow) -> Value { use Value::Object; let columns = row.columns(); @@ -48,7 +48,7 @@ pub fn row_to_json(row: &AnyRow) -> Value { /// /// Every SQL value is decoded exactly once. Private values are addressed by /// ordinal, so their generated SQL aliases cannot collide with user columns. -pub fn row_to_json_with_inputs( +pub(super) fn row_to_json_with_inputs( row: &AnyRow, input_count: usize, ) -> anyhow::Result<(Value, Vec)> { @@ -84,7 +84,7 @@ fn canonical_col_name(col: &AnyColumn) -> String { } } -pub fn sql_to_json(row: &AnyRow, col: &sqlx::any::AnyColumn) -> Value { +pub(super) fn sql_to_json(row: &AnyRow, col: &AnyColumn) -> Value { let raw_value_result = row.try_get_raw(col.ordinal()); match raw_value_result { Ok(raw_value) if !raw_value.is_null() => { @@ -122,13 +122,13 @@ fn decode_pg_range<'r, T>(raw_value: sqlx::any::AnyValueRef<'r>) -> Value where T: std::fmt::Display + Type - + for<'a> sqlx::decode::Decode<'a, sqlx::postgres::Postgres>, + + for<'a> Decode<'a, sqlx::postgres::Postgres>, { let Ok(pg_val): Result, _> = raw_value.try_into() else { log::error!("Only postgres range values are supported"); return Value::Null; }; - match as sqlx::decode::Decode<'r, sqlx::postgres::Postgres>>::decode(pg_val) { + match as Decode<'r, sqlx::postgres::Postgres>>::decode(pg_val) { Ok(pg_range) => pg_range.to_string().into(), Err(e) => { log::error!("Failed to decode postgres range value: {e}"); @@ -144,7 +144,9 @@ fn decimal_to_json(decimal: &BigDecimal) -> Value { )) } -pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueRef<'r>) -> Value { +pub(super) fn sql_nonnull_to_json<'r>( + mut get_ref: impl FnMut() -> sqlx::any::AnyValueRef<'r>, +) -> Value { use AnyTypeInfoKind::{Mssql, MySql}; let raw_value = get_ref(); let type_info = raw_value.type_info(); @@ -168,9 +170,7 @@ pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueR decode_raw::(raw_value).into() } "BIT" if matches!(db_type, MySql(_)) => decode_raw::(raw_value).into(), - "DATE" => decode_raw::(raw_value) - .to_string() - .into(), + "DATE" => decode_raw::(raw_value).to_string().into(), "TIME" | "TIMETZ" => decode_raw::(raw_value) .to_string() .into(), @@ -727,7 +727,7 @@ line2' as multiline_string let expected_json = serde_json::json!({ "null_col": null, - "empty_string": if empty_str_is_null { serde_json::Value::Null } else { serde_json::Value::String(String::new()) }, + "empty_string": if empty_str_is_null { Value::Null } else { Value::String(String::new()) }, "zero_value": 0, "negative_int": -42, "my_float": 1.23456, diff --git a/src/webserver/database/sqlpage_functions/function_traits.rs b/src/webserver/database/sqlpage_functions/function_traits.rs index 7a18a9b99..af850a06d 100644 --- a/src/webserver/database/sqlpage_functions/function_traits.rs +++ b/src/webserver/database/sqlpage_functions/function_traits.rs @@ -233,7 +233,7 @@ macro_rules! sqlpage_functions { /// One variant per built-in `sqlpage.*` function. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[allow(non_camel_case_types)] - pub enum SqlPageFunctionName { + pub(crate) enum SqlPageFunctionName { $($func),* } diff --git a/src/webserver/database/sqlpage_functions/functions/send_mail.rs b/src/webserver/database/sqlpage_functions/functions/send_mail.rs index 0f4860ca6..7799bc2ee 100644 --- a/src/webserver/database/sqlpage_functions/functions/send_mail.rs +++ b/src/webserver/database/sqlpage_functions/functions/send_mail.rs @@ -308,14 +308,14 @@ fn resolve_bodies( })?, ) } else { - body_html.map(std::string::ToString::to_string) + body_html.map(ToString::to_string) }; // If body is provided it takes precedence; otherwise the raw markdown is used. let text_body = match body { Some(body) => body.into_owned(), None => body_md - .map(std::string::ToString::to_string) + .map(ToString::to_string) .expect("body_md is present when body is None"), }; Ok((text_body, html_body)) diff --git a/src/webserver/database/sqlpage_functions/functions/user_info.rs b/src/webserver/database/sqlpage_functions/functions/user_info.rs index 7da9470e1..687295749 100644 --- a/src/webserver/database/sqlpage_functions/functions/user_info.rs +++ b/src/webserver/database/sqlpage_functions/functions/user_info.rs @@ -64,7 +64,7 @@ pub(super) async fn user_info<'a>( "gender" => claims.gender().map(|g| g.to_string()), // Assumes GenderClaim impls ToString "birthdate" => claims.birthdate().map(|b| b.to_string()), // Assumes Birthdate impls ToString "zoneinfo" => claims.zoneinfo().map(|z| z.to_string()), // Assumes ZoneInfo impls ToString - "locale" => claims.locale().map(std::string::ToString::to_string), // Assumes Locale impls ToString + "locale" => claims.locale().map(ToString::to_string), // Assumes Locale impls ToString "updated_at" => claims.updated_at().map(|t| t.timestamp().to_string()), // Standard Claims (Email Scope) @@ -78,7 +78,7 @@ pub(super) async fn user_info<'a>( .additional_claims() .0 .get(additional_claim) - .map(std::string::ToString::to_string), + .map(ToString::to_string), }; Ok(claim_value_str) diff --git a/src/webserver/database/sqlpage_functions/http_fetch_request.rs b/src/webserver/database/sqlpage_functions/http_fetch_request.rs index 965342a8b..97bd2dcad 100644 --- a/src/webserver/database/sqlpage_functions/http_fetch_request.rs +++ b/src/webserver/database/sqlpage_functions/http_fetch_request.rs @@ -49,7 +49,7 @@ fn deserialize_map_to_vec_pairs<'de, D: serde::Deserializer<'de>>( impl<'de> serde::de::Visitor<'de> for Visitor { type Value = Vec<(Cow<'de, str>, Cow<'de, str>)>; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter.write_str("a map") } diff --git a/src/webserver/database/sqlpage_functions/url_parameters.rs b/src/webserver/database/sqlpage_functions/url_parameters.rs index eec0ed6c5..8ef0290e0 100644 --- a/src/webserver/database/sqlpage_functions/url_parameters.rs +++ b/src/webserver/database/sqlpage_functions/url_parameters.rs @@ -14,7 +14,7 @@ impl URLParameters { } fn encode_and_push(&mut self, v: &str) { - let val: Cow = percent_encode(v.as_bytes(), NON_ALPHANUMERIC).into(); + let val: Cow<'_, str> = percent_encode(v.as_bytes(), NON_ALPHANUMERIC).into(); self.0.push_str(&val); } @@ -59,7 +59,7 @@ impl URLParameters { } fn add_from_json(&mut self, key: &str, raw_json_value: &str) { - if let Ok(str_val) = serde_json::from_str::>>(raw_json_value) { + if let Ok(str_val) = serde_json::from_str::>>(raw_json_value) { if let Some(str_val) = str_val { self.push_kv(key, &str_val); } @@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for URLParameters { impl<'de> serde::de::Visitor<'de> for URLParametersVisitor { type Value = URLParameters; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a sequence") } @@ -108,7 +108,7 @@ impl<'de> Deserialize<'de> for URLParameters { { let mut out = URLParameters(String::new()); while let Some((key, value)) = - map.next_entry::, Cow>()? + map.next_entry::, Cow<'_, serde_json::value::RawValue>>()? { out.add_from_json(&key, value.get()); } @@ -121,8 +121,8 @@ impl<'de> Deserialize<'de> for URLParameters { } } -impl std::fmt::Display for URLParameters { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for URLParameters { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } diff --git a/src/webserver/error.rs b/src/webserver/error.rs index f22ec1b99..a1ea8954f 100644 --- a/src/webserver/error.rs +++ b/src/webserver/error.rs @@ -240,7 +240,7 @@ pub(super) fn handle_form_error( _req: &HttpRequest, ) -> actix_web::Error { match decode_err { - actix_web::error::UrlencodedError::Overflow { size, limit } => { + UrlencodedError::Overflow { size, limit } => { actix_web::error::ErrorPayloadTooLarge(format!( "The submitted form data size ({size} bytes) exceeds the maximum allowed upload size ({limit} bytes). \ You can increase this limit by setting max_uploaded_file_size in the configuration file.", diff --git a/src/webserver/http.rs b/src/webserver/http.rs index d03dd4bd0..88e67ae9f 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -177,7 +177,7 @@ async fn build_response_header_and_stream>( renderer, } => { let body_stream = tokio_stream::wrappers::ReceiverStream::new(receiver); - let result_stream = body_stream.map(Ok::<_, actix_web::Error>); + let result_stream = body_stream.map(Ok::<_, Error>); let http_response = http_response.streaming(result_stream); return Ok(ResponseWithWriter::RenderStream { http_response, @@ -262,7 +262,7 @@ async fn render_sql( otel.name = %sql_execution_span_name(&source_path), { otel::CODE_FILE_PATH } = %source_path.display(), ); - actix_web::rt::spawn(tracing::Instrument::instrument( + actix_web::rt::spawn(Instrument::instrument( async move { let request_info = exec_ctx.request(); let request_context = RequestContext { @@ -291,7 +291,7 @@ async fn render_sql( resp_send .send(http_response) .unwrap_or_else(|e| log::error!("could not send headers {e:?}")); - tracing::Instrument::instrument( + Instrument::instrument( stream_response(database_entries_stream, renderer), tracing::info_span!("render"), ) @@ -326,7 +326,7 @@ fn sql_execution_span_name(source_path: &std::path::Path) -> String { format!("SQL {}", source_path.display()) } -struct RequestHeaderCarrier<'a>(&'a actix_web::http::header::HeaderMap); +struct RequestHeaderCarrier<'a>(&'a header::HeaderMap); impl opentelemetry::propagation::Extractor for RequestHeaderCarrier<'_> { fn get(&self, key: &str) -> Option<&str> { @@ -334,10 +334,7 @@ impl opentelemetry::propagation::Extractor for RequestHeaderCarrier<'_> { } fn keys(&self) -> Vec<&str> { - self.0 - .keys() - .map(actix_web::http::header::HeaderName::as_str) - .collect() + self.0.keys().map(header::HeaderName::as_str).collect() } } @@ -386,7 +383,7 @@ impl RootSpanBuilder for SqlPageRootSpanBuilder { { otel::EXCEPTION_MESSAGE } = tracing::field::Empty, "sqlpage.exception.details" = tracing::field::Empty, ); - std::mem::drop(connection_info); + drop(connection_info); set_otel_parent(request, &span); span } @@ -502,8 +499,7 @@ pub async fn main_handler( }; match routing_action { NotFound => { - let accept_header = - header::Accept::parse(&service_request).unwrap_or(header::Accept::star()); + let accept_header = Accept::parse(&service_request).unwrap_or(Accept::star()); let prefers_html = accept_header.iter().any(|h| h.item.subtype() == "html"); if prefers_html { @@ -566,7 +562,7 @@ pub fn create_app( Response = ServiceResponse< impl MessageBody, >, - Error = actix_web::Error, + Error = Error, InitError = (), >, > { diff --git a/src/webserver/http_request_info.rs b/src/webserver/http_request_info.rs index 589603e92..d969120f5 100644 --- a/src/webserver/http_request_info.rs +++ b/src/webserver/http_request_info.rs @@ -45,7 +45,7 @@ pub struct RequestInfo { pub app_state: Arc, pub raw_body: Option>, pub oidc_claims: Option, - pub server_timing: Arc, + pub server_timing: Arc, } #[derive(Debug)] @@ -161,7 +161,7 @@ pub(crate) async fn extract_request_info( } async fn extract_post_data( - http_req: &mut actix_web::HttpRequest, + http_req: &mut HttpRequest, payload: &mut actix_web::dev::Payload, config: &crate::app_config::AppConfig, ) -> anyhow::Result<( @@ -181,7 +181,7 @@ async fn extract_post_data( let (vars, files) = extract_multipart_post_data(http_req, payload, config).await?; Ok((vars, files, None)) } else { - let body = actix_web::web::Bytes::from_request(http_req, payload) + let body = web::Bytes::from_request(http_req, payload) .await .with_actix_error_status() .context("could not read the request body")?; @@ -194,7 +194,7 @@ async fn extract_post_data( } async fn extract_urlencoded_post_variables( - http_req: &mut actix_web::HttpRequest, + http_req: &mut HttpRequest, payload: &mut actix_web::dev::Payload, ) -> anyhow::Result> { Form::>::from_request(http_req, payload) @@ -205,7 +205,7 @@ async fn extract_urlencoded_post_variables( } async fn extract_multipart_post_data( - http_req: &mut actix_web::HttpRequest, + http_req: &mut HttpRequest, payload: &mut actix_web::dev::Payload, config: &crate::app_config::AppConfig, ) -> anyhow::Result<(Vec<(String, String)>, Vec<(String, TempFile)>)> { @@ -297,9 +297,7 @@ async fn extract_file( /// file upload form fields that are left blank result in the browser sending an empty file, with a mime type of application/octet-stream. /// We don't want to treat this the same as actual empty files, so we check for this case. -async fn is_file_field_empty( - uploaded_file: &actix_multipart::form::tempfile::TempFile, -) -> anyhow::Result { +async fn is_file_field_empty(uploaded_file: &TempFile) -> anyhow::Result { Ok( uploaded_file.content_type == Some(mime_guess::mime::APPLICATION_OCTET_STREAM) && uploaded_file.file_name.as_deref().is_none_or(str::is_empty) diff --git a/src/webserver/https.rs b/src/webserver/https.rs index f880373ba..a9a14d0fd 100644 --- a/src/webserver/https.rs +++ b/src/webserver/https.rs @@ -3,7 +3,7 @@ use tokio_stream::StreamExt; use crate::app_config::AppConfig; -pub fn make_auto_rustls_config(domain: &str, config: &AppConfig) -> ServerConfig { +pub(super) fn make_auto_rustls_config(domain: &str, config: &AppConfig) -> ServerConfig { log::info!("Starting HTTPS configuration for {domain}"); let mut state = AcmeConfig::new([domain]) .contact([if let Some(email) = &config.https_certificate_email { diff --git a/src/webserver/lambda_http.rs b/src/webserver/lambda_http.rs index 6d3b9d2ab..2c63679c4 100644 --- a/src/webserver/lambda_http.rs +++ b/src/webserver/lambda_http.rs @@ -10,11 +10,11 @@ type LambdaResponse = Response>; type LambdaResult = Result; type LambdaRequest = (Request, oneshot::Sender); -pub fn is_running_on_lambda() -> bool { +pub(super) fn is_running_on_lambda() -> bool { std::env::var_os("AWS_LAMBDA_RUNTIME_API").is_some() } -pub async fn run(factory: F) -> Result<(), lambda_http::Error> +pub(super) async fn run(factory: F) -> Result<(), lambda_http::Error> where F: Fn() -> I + Send + Clone + 'static, I: IntoServiceFactory, diff --git a/src/webserver/oidc.rs b/src/webserver/oidc.rs index 195fe9e15..1aa136249 100644 --- a/src/webserver/oidc.rs +++ b/src/webserver/oidc.rs @@ -66,12 +66,11 @@ pub struct OidcAdditionalClaims(pub(crate) serde_json::Map; -pub type OidcClaims = - openidconnect::IdTokenClaims; +pub type OidcClaims = openidconnect::IdTokenClaims; #[derive(Clone, Debug)] pub struct OidcConfig { @@ -395,7 +394,7 @@ impl OidcMiddleware { } async fn discover_provider_metadata( - http_client: &awc::Client, + http_client: &Client, issuer_url: IssuerUrl, ) -> anyhow::Result { log::debug!("Discovering provider metadata for {issuer_url}"); @@ -800,7 +799,7 @@ async fn process_oidc_callback( async fn exchange_code_for_token( oidc_client: &OidcClient, - http_client: &awc::Client, + http_client: &Client, oidc_callback_params: OidcCallbackParams, ) -> anyhow::Result { let span = tracing::info_span!( @@ -936,12 +935,12 @@ fn get_authenticated_user_info( } pub struct AwcHttpClient<'c> { - client: &'c awc::Client, + client: &'c Client, } impl<'c> AwcHttpClient<'c> { #[must_use] - pub fn from_client(client: &'c awc::Client) -> Self { + pub fn from_client(client: &'c Client) -> Self { Self { client } } } diff --git a/src/webserver/response_writer.rs b/src/webserver/response_writer.rs index 1f506a74b..d30c5956a 100644 --- a/src/webserver/response_writer.rs +++ b/src/webserver/response_writer.rs @@ -50,7 +50,7 @@ impl ResponseWriter { .reserve() .await .map_err(|_| std::io::ErrorKind::WouldBlock)?; - sender.send(std::mem::take(&mut self.buffer).into()); + sender.send(mem::take(&mut self.buffer).into()); Ok(()) } } @@ -122,7 +122,7 @@ impl tokio::io::AsyncWrite for AsyncResponseWriter { } = self.get_mut(); match poll_sender.poll_reserve(cx) { std::task::Poll::Ready(Ok(())) => { - let res = poll_sender.send_item(std::mem::take(&mut writer.buffer).into()); + let res = poll_sender.send_item(mem::take(&mut writer.buffer).into()); std::task::Poll::Ready(res.map_err(|_| std::io::ErrorKind::BrokenPipe.into())) } std::task::Poll::Pending => std::task::Poll::Pending, @@ -142,7 +142,7 @@ impl tokio::io::AsyncWrite for AsyncResponseWriter { impl Drop for ResponseWriter { fn drop(&mut self) { - if let Err(e) = std::io::Write::flush(self) { + if let Err(e) = Write::flush(self) { log::debug!("Could not flush data to client: {e}"); } } diff --git a/src/webserver/routing.rs b/src/webserver/routing.rs index 25c644a63..99c189d0c 100644 --- a/src/webserver/routing.rs +++ b/src/webserver/routing.rs @@ -115,7 +115,7 @@ pub(crate) struct AppFileStore<'a> { } impl<'a> AppFileStore<'a> { - pub fn new( + pub(crate) fn new( cache: &'a FileCache, filesystem: &'a FileSystem, app_state: &'a AppState, @@ -637,7 +637,6 @@ mod tests { fn contains(&self, path: &str) -> bool { let normalized_path = path.replace('\\', "/"); - dbg!(&normalized_path, &self.contents); self.contents.contains(&normalized_path) } diff --git a/src/webserver/static_content.rs b/src/webserver/static_content.rs index 61bb4ab90..7be7ed28b 100644 --- a/src/webserver/static_content.rs +++ b/src/webserver/static_content.rs @@ -32,26 +32,26 @@ macro_rules! static_file_endpoint { } #[must_use] -pub fn js() -> Resource { +pub(super) fn js() -> Resource { static_file_endpoint!("sqlpage", "js", "application/javascript") } #[must_use] -pub fn apexcharts_js() -> Resource { +pub(super) fn apexcharts_js() -> Resource { static_file_endpoint!("apexcharts", "js", "application/javascript") } #[must_use] -pub fn tomselect_js() -> Resource { +pub(super) fn tomselect_js() -> Resource { static_file_endpoint!("tomselect", "js", "application/javascript") } #[must_use] -pub fn css() -> Resource { +pub(super) fn css() -> Resource { static_file_endpoint!("sqlpage", "css", "text/css") } #[must_use] -pub fn favicon() -> Resource { +pub(super) fn favicon() -> Resource { static_file_endpoint!("favicon", "svg", "image/svg+xml") } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 31823de82..b1754f26c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,4 @@ +use std::fmt::Write as _; use std::time::Duration; use actix_web::{ @@ -5,7 +6,7 @@ use actix_web::{ dev::{ServiceRequest, fn_service}, http::header, http::header::ContentType, - test::{self, TestRequest}, + test::TestRequest, web, web::Data, }; @@ -18,11 +19,11 @@ use sqlpage::{ use tokio::sync::oneshot; use tokio::task::JoinHandle; -pub async fn get_request_to_with_data( +pub(crate) async fn get_request_to_with_data( path: &str, data: Data, ) -> actix_web::Result { - Ok(test::TestRequest::get() + Ok(TestRequest::get() .uri(path) .insert_header(ContentType::plaintext()) .insert_header(header::Accept::html()) @@ -31,23 +32,23 @@ pub async fn get_request_to_with_data( .app_data(data)) } -pub async fn get_request_to(path: &str) -> actix_web::Result { +pub(crate) async fn get_request_to(path: &str) -> actix_web::Result { let data = make_app_data().await; get_request_to_with_data(path, data).await } -pub async fn make_app_data_from_config(config: AppConfig) -> Data { +pub(crate) async fn make_app_data_from_config(config: AppConfig) -> Data { let state = AppState::init(&config).await.unwrap(); Data::new(state) } -pub async fn make_app_data() -> Data { +pub(crate) async fn make_app_data() -> Data { init_log(); let config = test_config(); make_app_data_from_config(config).await } -pub async fn req_path( +pub(crate) async fn req_path( path: impl AsRef, ) -> Result { let req = get_request_to(path.as_ref()).await?.to_srv_request(); @@ -55,14 +56,14 @@ pub async fn req_path( } const REQ_TIMEOUT: Duration = Duration::from_secs(8); -pub async fn req_path_with_app_data( +pub(crate) async fn req_path_with_app_data( path: impl AsRef, app_data: Data, ) -> anyhow::Result { req_path_with_app_data_and_accept(path, app_data, header::Accept::html()).await } -pub async fn req_path_with_app_data_json( +pub(crate) async fn req_path_with_app_data_json( path: impl AsRef, app_data: Data, ) -> anyhow::Result { @@ -75,7 +76,7 @@ async fn req_path_with_app_data_and_accept( accept: header::Accept, ) -> anyhow::Result { let path = path.as_ref(); - let req = test::TestRequest::get() + let req = TestRequest::get() .uri(path) .app_data(app_data) .insert_header(("cookie", "test_cook=123")) @@ -94,7 +95,7 @@ async fn req_path_with_app_data_and_accept( Ok(resp) } -pub fn test_config() -> AppConfig { +pub(crate) fn test_config() -> AppConfig { let db_url = test_database_url(); serde_json::from_str::(&format!( r#"{{ @@ -111,7 +112,7 @@ pub fn test_config() -> AppConfig { .unwrap() } -pub fn init_log() { +pub(crate) fn init_log() { telemetry::init_test_logging(); } @@ -123,7 +124,7 @@ fn format_request_line_and_headers(req: &ServiceRequest) -> String { if k.as_str().eq_ignore_ascii_case("date") { continue; } - out.push_str(&format!("|{k}: {}", v.to_str().unwrap_or("?"))); + write!(out, "|{k}: {}", v.to_str().unwrap_or("?")).unwrap(); } out } @@ -135,24 +136,24 @@ async fn format_body(req: &mut ServiceRequest) -> Vec { .unwrap_or_default() } -fn build_echo_response(body: Vec, meta: String) -> HttpResponse { +fn build_echo_response(body: &[u8], meta: String) -> HttpResponse { let mut resp = meta.into_bytes(); resp.push(b'|'); - resp.extend_from_slice(&body); + resp.extend_from_slice(body); HttpResponse::Ok() .insert_header((header::DATE, "Mon, 24 Feb 2025 12:00:00 GMT")) .insert_header((header::CONTENT_TYPE, "text/plain")) .body(resp) } -pub fn start_echo_server(shutdown: oneshot::Receiver<()>) -> (JoinHandle<()>, u16) { +pub(crate) fn start_echo_server(shutdown: oneshot::Receiver<()>) -> (JoinHandle<()>, u16) { let listener = std::net::TcpListener::bind("localhost:0").unwrap(); let port = listener.local_addr().unwrap().port(); let server = HttpServer::new(|| { App::new().default_service(fn_service(|mut req: ServiceRequest| async move { let meta = format_request_line_and_headers(&req); let body = format_body(&mut req).await; - let resp = build_echo_response(body, meta); + let resp = build_echo_response(&body, meta); Ok(req.into_response(resp)) })) }) diff --git a/tests/core/mod.rs b/tests/core/mod.rs index 395df28f2..75e9c849d 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -23,7 +23,7 @@ async fn test_concurrent_requests() { }) .collect::>(); let results = futures_util::future::join_all(reqs).await; - for result in results.into_iter() { + for result in results { let resp = result.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = test::read_body(resp).await; @@ -52,7 +52,7 @@ async fn test_routing_with_db_fs() { if matches!( state.db.info.database_type, - sqlpage::webserver::database::SupportedDatabase::Oracle + webserver::database::SupportedDatabase::Oracle ) { return; } @@ -116,7 +116,7 @@ async fn test_non_unicode_static_path_returns_bad_request_with_db_fs() { (&mut *conn) .execute(sqlpage::filesystem::DbFsQueries::get_create_table_sql( - sqlpage::webserver::database::SupportedDatabase::Sqlite, + webserver::database::SupportedDatabase::Sqlite, )) .await .unwrap(); @@ -140,7 +140,7 @@ async fn test_non_unicode_static_path_returns_bad_request_with_db_fs() { .app_data(app_data) .to_srv_request(); - let err = sqlpage::webserver::http::main_handler(req) + let err = webserver::http::main_handler(req) .await .expect_err("non-unicode path should not panic and must return bad request"); assert_eq!( @@ -213,7 +213,7 @@ async fn test_hidden_files() { ); let resp = resp_result.unwrap_err().error_response(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); - let srv_resp = actix_web::test::TestRequest::default().to_srv_response(resp); + let srv_resp = test::TestRequest::default().to_srv_response(resp); let body = test::read_body(srv_resp).await; assert!( String::from_utf8_lossy(&body) diff --git a/tests/data_formats/mod.rs b/tests/data_formats/mod.rs index 44a553307..7c6c3d2f1 100644 --- a/tests/data_formats/mod.rs +++ b/tests/data_formats/mod.rs @@ -109,7 +109,7 @@ async fn test_csv_filename_header_injection() -> actix_web::Result<()> { #[actix_web::test] async fn test_json_columns() { - let app_data = crate::common::make_app_data().await; + let app_data = make_app_data().await; if !matches!( app_data.db.to_string().to_lowercase().as_str(), "postgres" | "sqlite" @@ -135,7 +135,7 @@ async fn test_json_columns() { "the json should have been parsed, not returned as a string, in: {body_html_escaped}" ); assert!( - !body_html_escaped.contains("{"), + !body_html_escaped.contains('{'), "the json should have been parsed, not returned as a string, in: {body_html_escaped}" ); } diff --git a/tests/errors/mod.rs b/tests/errors/mod.rs index e59230c86..fc66d22b9 100644 --- a/tests/errors/mod.rs +++ b/tests/errors/mod.rs @@ -14,7 +14,7 @@ async fn direct_request_status(path: &str, app_data: actix_web::web::Data resp.status(), @@ -72,7 +72,7 @@ async fn test_privileged_paths_are_not_accessible() { ); let resp = resp_result.unwrap_err().error_response(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); - let srv_resp = actix_web::test::TestRequest::default().to_srv_response(resp); + let srv_resp = test::TestRequest::default().to_srv_response(resp); let body = test::read_body(srv_resp).await; assert!( String::from_utf8_lossy(&body) @@ -90,7 +90,7 @@ async fn test_404_fallback() { ] { let resp_result = req_path(f).await; let resp = resp_result.unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK, "{f} isnt 200"); + assert_eq!(resp.status(), StatusCode::OK, "{f} isnt 200"); let body = test::read_body(resp).await; assert!(body.starts_with(b"")); @@ -113,7 +113,7 @@ async fn test_default_404() { let resp = resp_result.unwrap(); assert_eq!( resp.status(), - http::StatusCode::NOT_FOUND, + StatusCode::NOT_FOUND, "{f} should return 404" ); @@ -135,7 +135,7 @@ async fn test_default_404_with_redirect() { let resp = resp_result.unwrap(); assert_eq!( resp.status(), - http::StatusCode::NOT_FOUND, + StatusCode::NOT_FOUND, "/i-do-not-exist should return 404" ); @@ -143,7 +143,7 @@ async fn test_default_404_with_redirect() { let resp = resp_result.unwrap(); assert_eq!( resp.status(), - http::StatusCode::NOT_FOUND, + StatusCode::NOT_FOUND, "/i-do-not-exist/ should return 404" ); @@ -164,7 +164,7 @@ async fn test_default_404_when_request_path_descends_into_file() { let resp = resp_result.unwrap(); assert_eq!( resp.status(), - http::StatusCode::NOT_FOUND, + StatusCode::NOT_FOUND, "descending into a file path should behave like a missing resource" ); diff --git a/tests/oidc/mod.rs b/tests/oidc/mod.rs index 7e605353d..ea02a83d4 100644 --- a/tests/oidc/mod.rs +++ b/tests/oidc/mod.rs @@ -19,7 +19,7 @@ fn base64url_encode(data: &[u8]) -> String { base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data) } -pub fn make_jwt(claims: &serde_json::Value, secret: &str) -> String { +pub(crate) fn make_jwt(claims: &serde_json::Value, secret: &str) -> String { use hmac::{Hmac, KeyInit, Mac}; use sha2::Sha256; @@ -32,7 +32,7 @@ pub fn make_jwt(claims: &serde_json::Value, secret: &str) -> String { let header_b64 = base64url_encode(header.to_string().as_bytes()); let payload_b64 = base64url_encode(claims.to_string().as_bytes()); - let message = format!("{}.{}", header_b64, payload_b64); + let message = format!("{header_b64}.{payload_b64}"); let mut mac = Hmac::::new_from_slice(secret.as_bytes()).expect("HMAC accepts any key size"); @@ -40,7 +40,7 @@ pub fn make_jwt(claims: &serde_json::Value, secret: &str) -> String { let signature = mac.finalize().into_bytes(); let signature_b64 = base64url_encode(&signature); - format!("{}.{}.{}", header_b64, payload_b64, signature_b64) + format!("{header_b64}.{payload_b64}.{signature_b64}") } type JwtCustomizer<'a> = dyn Fn(serde_json::Value, &str) -> String + Send + Sync + 'a; @@ -141,11 +141,10 @@ async fn token_endpoint( "nonce": nonce, }); - let id_token = state - .jwt_customizer - .take() - .map(|customizer| customizer(claims.clone(), &state.secret)) - .unwrap_or_else(|| make_jwt(&claims, &state.secret)); + let id_token = state.jwt_customizer.take().map_or_else( + || make_jwt(&claims, &state.secret), + |customizer| customizer(claims.clone(), &state.secret), + ); let delay = state.token_endpoint_delay; drop(state); @@ -167,7 +166,7 @@ async fn token_endpoint( .streaming(body) } -pub struct FakeOidcProvider { +pub(crate) struct FakeOidcProvider { pub issuer_url: String, pub client_id: String, pub client_secret: String, @@ -185,10 +184,10 @@ fn extract_set_cookies(headers: &header::HeaderMap) -> Vec> { } impl FakeOidcProvider { - pub async fn new() -> Self { + pub(crate) fn new() -> Self { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); - let issuer_url = format!("http://127.0.0.1:{}", port); + let issuer_url = format!("http://127.0.0.1:{port}"); let client_id = "test_client".to_string(); let client_secret = "test_secret".to_string(); @@ -236,20 +235,20 @@ impl FakeOidcProvider { } } - fn with_state_mut(&self, f: impl FnOnce(&mut ProviderState) -> R) -> R { + fn with_state_mut(&self, f: impl FnOnce(&mut ProviderState<'_>) -> R) -> R { let mut state = self.state.lock().unwrap(); f(&mut state) } - pub fn set_token_endpoint_delay(&self, delay: Duration) { + pub(crate) fn set_token_endpoint_delay(&self, delay: Duration) { self.with_state_mut(|s| s.token_endpoint_delay = delay); } - pub fn discovery_count(&self) -> usize { + pub(crate) fn discovery_count(&self) -> usize { self.state.lock().unwrap().discovery_count } - pub fn store_auth_code(&self, code: String, nonce: String) { + pub(crate) fn store_auth_code(&self, code: String, nonce: String) { self.with_state_mut(|s| { s.auth_codes.insert(code, nonce); }); @@ -283,7 +282,7 @@ macro_rules! request_with_cookies { } let resp = test::call_service(&$app, req.to_request()).await; for new_cookie in extract_set_cookies(resp.headers()) { - $cookies.retain(|c: &Cookie| c.name() != new_cookie.name()); + $cookies.retain(|c: &Cookie<'_>| c.name() != new_cookie.name()); if !new_cookie.value().is_empty() { $cookies.push(new_cookie); } @@ -293,7 +292,7 @@ macro_rules! request_with_cookies { } async fn setup_oidc_test( - provider_mutator: impl FnOnce(&mut ProviderState), + provider_mutator: impl FnOnce(&mut ProviderState<'_>), ) -> ( impl actix_web::dev::Service< actix_http::Request, @@ -307,7 +306,7 @@ async fn setup_oidc_test( app_config::{AppConfig, test_database_url}, }; crate::common::init_log(); - let provider = FakeOidcProvider::new().await; + let provider = FakeOidcProvider::new(); provider.with_state_mut(provider_mutator); let db_url = test_database_url(); @@ -436,7 +435,7 @@ async fn test_oidc_happy_path() { } async fn assert_oidc_login_fails( - provider_mutator: impl FnOnce(&mut ProviderState), + provider_mutator: impl FnOnce(&mut ProviderState<'_>), state_override: Option, ) { let (app, provider) = setup_oidc_test(provider_mutator).await; @@ -545,7 +544,7 @@ async fn test_oidc_expired_token_is_rejected() { } async fn setup_oidc_test_with_prefix( - provider_mutator: impl FnOnce(&mut ProviderState), + provider_mutator: impl FnOnce(&mut ProviderState<'_>), site_prefix: &str, ) -> ( impl actix_web::dev::Service< @@ -560,7 +559,7 @@ async fn setup_oidc_test_with_prefix( app_config::{AppConfig, test_database_url}, }; crate::common::init_log(); - let provider = FakeOidcProvider::new().await; + let provider = FakeOidcProvider::new(); provider.with_state_mut(provider_mutator); let db_url = test_database_url(); @@ -596,8 +595,7 @@ async fn test_oidc_with_site_prefix() { let redirect_uri = get_query_param(&auth_url, "redirect_uri"); assert!( redirect_uri.contains("/my-app/sqlpage/oidc_callback"), - "Redirect URI should contain site prefix. Got: {}", - redirect_uri + "Redirect URI should contain site prefix. Got: {redirect_uri}" ); } @@ -609,7 +607,7 @@ async fn test_oidc_logout_uses_correct_scheme() { }; crate::common::init_log(); - let provider = FakeOidcProvider::new().await; + let provider = FakeOidcProvider::new(); let db_url = test_database_url(); let config_json = format!( @@ -718,7 +716,7 @@ async fn test_slow_token_endpoint_does_not_freeze_server() { let handle = tokio::task::spawn_local(async move { let mut req = test::TestRequest::get().uri(&callback_uri); - for cookie in cookies.iter() { + for cookie in &cookies { req = req.cookie(cookie.clone()); } test::call_service(&app, req.to_request()).await @@ -728,7 +726,7 @@ async fn test_slow_token_endpoint_does_not_freeze_server() { // then advance past the body-read timeout. tokio::task::yield_now().await; tokio::time::pause(); - tokio::time::advance(Duration::from_secs(60)).await; + tokio::time::advance(Duration::from_mins(1)).await; let resp = tokio::time::timeout(Duration::from_secs(1), handle) .await @@ -749,7 +747,7 @@ async fn test_oidc_logout_is_session_bound() { }; crate::common::init_log(); - let provider = FakeOidcProvider::new().await; + let provider = FakeOidcProvider::new(); let db_url = test_database_url(); let config_json = format!( diff --git a/tests/requests/mod.rs b/tests/requests/mod.rs index e7883b74b..bd308f96a 100644 --- a/tests/requests/mod.rs +++ b/tests/requests/mod.rs @@ -112,7 +112,7 @@ async fn test_download_data_url() -> actix_web::Result<()> { #[actix_web::test] async fn test_large_form_field_roundtrip() -> actix_web::Result<()> { - let long_string = "a".repeat(123454); + let long_string = "a".repeat(123_454); let req = get_request_to("/tests/components/display_form_field.sql") .await? .insert_header(("content-type", "application/x-www-form-urlencoded")) @@ -193,7 +193,7 @@ async fn test_variables_function() -> actix_web::Result<()> { assert_eq!( actual_decoded, expected_value, "step {i}: {key} mismatch: {actual_decoded:#} != {expected_value:#}" - ) + ); } } @@ -223,8 +223,7 @@ async fn test_invalid_utf8_multipart_text_field_returns_bad_request() -> actix_w assert_eq!( status, StatusCode::BAD_REQUEST, - "assertion error, expected 400 bad request on invalid utf8 payload, got {}", - status + "assertion error, expected 400 bad request on invalid utf8 payload, got {status}" ); Ok(()) @@ -252,8 +251,7 @@ async fn test_missing_multipart_content_disposition_returns_bad_request() -> act assert_eq!( status, StatusCode::BAD_REQUEST, - "expected 400 bad request on malformed multipart payload, got {}", - status + "expected 400 bad request on malformed multipart payload, got {status}" ); Ok(()) diff --git a/tests/sql_test_files/mod.rs b/tests/sql_test_files/mod.rs index f4b32540d..3335f967b 100644 --- a/tests/sql_test_files/mod.rs +++ b/tests/sql_test_files/mod.rs @@ -1,5 +1,6 @@ use actix_web::test; use sqlpage::AppState; +use std::fmt::Write as _; use std::time::Duration; use tokio::sync::oneshot; use tokio::task::JoinHandle; @@ -93,7 +94,7 @@ async fn run_sql_test( let mut query_params = "x=1".to_string(); if test_file_path.contains("fetch") { - query_params.push_str(&format!("&echo_port={port}")); + write!(query_params, "&echo_port={port}").unwrap(); } let req_str = format!("/{test_file_path}?{query_params}"); @@ -137,9 +138,8 @@ fn assert_json_test(body: &str, test_file: &std::path::Path) { ); for row in rows { - let obj = match row.as_object() { - Some(o) => o, - None => continue, + let Some(obj) = row.as_object() else { + continue; }; if let Some(err) = format_error(obj) { @@ -172,13 +172,12 @@ fn assert_json_test(body: &str, test_file: &std::path::Path) { }) .unwrap_or_default(); - if expected.is_empty() && expected_contains.is_empty() { - panic!( - "{}: No `expected` column returned: \n{:#}", - test_file.display(), - row - ); - } + assert!( + !(expected.is_empty() && expected_contains.is_empty()), + "{}: No `expected` column returned: \n{:#}", + test_file.display(), + row + ); let exact_ok = expected.is_empty() || expected.iter().any(|e| e == &actual); let contains_ok = expected_contains.is_empty() @@ -187,16 +186,13 @@ fn assert_json_test(body: &str, test_file: &std::path::Path) { if !exact_ok || !contains_ok { let mut msg = format!("Test failed: {}\n", test_file.display()); if !expected.is_empty() { - let expected_strs: Vec = expected.iter().map(|d| d.to_string()).collect(); - msg.push_str(&format!("Expected: {}\n", expected_strs.join(" or "))); + let expected_strs: Vec = expected.iter().map(ToString::to_string).collect(); + writeln!(msg, "Expected: {}", expected_strs.join(" or ")).unwrap(); } if !expected_contains.is_empty() { - msg.push_str(&format!( - "Expected to contain: {}\n", - expected_contains.join(", ") - )); + writeln!(msg, "Expected to contain: {}", expected_contains.join(", ")).unwrap(); } - msg.push_str(&format!("Actual: {}\n", actual)); + writeln!(msg, "Actual: {actual}").unwrap(); panic!("{}", msg); } } diff --git a/tests/uploads/mod.rs b/tests/uploads/mod.rs index e21ea5ddd..fe30cd54e 100644 --- a/tests/uploads/mod.rs +++ b/tests/uploads/mod.rs @@ -31,7 +31,7 @@ async fn test_file_upload(target: &str) -> actix_web::Result<()> { #[actix_web::test] async fn test_persist_uploaded_file_mode() -> actix_web::Result<()> { let app_data = crate::common::make_app_data().await; - let req = actix_web::test::TestRequest::get() + let req = test::TestRequest::get() .uri("/tests/uploads/persist_with_mode.sql?mode=644") .app_data(app_data.clone()) .app_data(sqlpage::webserver::http::payload_config(&app_data)) @@ -132,7 +132,7 @@ async fn test_file_upload_too_large() -> actix_web::Result<()> { \r\n\ " .to_string() - + "a".repeat(123457).as_str() + + "a".repeat(123_457).as_str() + "\r\n\ --1234567890--\r\n", )