fix(rust): enforce lint on all files + fix lint issues (#1394)
This commit is contained in:
+13
@@ -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"
|
||||
|
||||
@@ -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<awc::Client>, 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, "<symbol", "</symbol>") {
|
||||
let id = take_between(&mut symbol_tag, "id=\"tabler-", "\"").expect("id not found");
|
||||
let content_start = symbol_tag.find('>').unwrap() + 1;
|
||||
|
||||
+3
-3
@@ -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");
|
||||
|
||||
+4
-4
@@ -49,7 +49,7 @@ pub(crate) struct FileSystem {
|
||||
}
|
||||
|
||||
impl FileSystem {
|
||||
pub async fn init(local_root: impl Into<PathBuf>, db: &Database) -> Self {
|
||||
pub(crate) async fn init(local_root: impl Into<PathBuf>, 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<'_>,
|
||||
|
||||
+1
-4
@@ -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());
|
||||
|
||||
|
||||
+1
-1
@@ -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"
|
||||
),
|
||||
|
||||
+12
-20
@@ -367,7 +367,7 @@ impl HeaderContext {
|
||||
}
|
||||
|
||||
fn log(self, data: &JsonValue) -> anyhow::Result<PageContext> {
|
||||
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<W: std::io::Write> {
|
||||
pub struct JsonBodyRenderer<W: Write> {
|
||||
writer: W,
|
||||
is_first: bool,
|
||||
prefix: &'static [u8],
|
||||
@@ -563,7 +563,7 @@ pub struct JsonBodyRenderer<W: std::io::Write> {
|
||||
separator: &'static [u8],
|
||||
}
|
||||
|
||||
impl<W: std::io::Write> JsonBodyRenderer<W> {
|
||||
impl<W: Write> JsonBodyRenderer<W> {
|
||||
pub fn new_array(writer: W) -> JsonBodyRenderer<W> {
|
||||
let mut renderer = Self {
|
||||
writer,
|
||||
@@ -741,7 +741,7 @@ impl CsvBodyRenderer {
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct HtmlRenderContext<W: std::io::Write> {
|
||||
pub struct HtmlRenderContext<W: Write> {
|
||||
app_state: Arc<AppState>,
|
||||
pub writer: W,
|
||||
current_component: Option<SplitTemplateRenderer>,
|
||||
@@ -754,7 +754,7 @@ const DEFAULT_COMPONENT: &str = "table";
|
||||
const PAGE_SHELL_COMPONENT: &str = "shell";
|
||||
const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty";
|
||||
|
||||
impl<W: std::io::Write> HtmlRenderContext<W> {
|
||||
impl<W: Write> HtmlRenderContext<W> {
|
||||
pub async fn new(
|
||||
app_state: Arc<AppState>,
|
||||
request_context: RequestContext,
|
||||
@@ -1023,11 +1023,11 @@ fn handle_log_component(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct HandlebarWriterOutput<W: std::io::Write>(W);
|
||||
struct HandlebarWriterOutput<W: Write>(W);
|
||||
|
||||
impl<W: std::io::Write> handlebars::Output for HandlebarWriterOutput<W> {
|
||||
impl<W: Write> handlebars::Output for HandlebarWriterOutput<W> {
|
||||
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::<SplitTemplateRenderer>() <= 64,
|
||||
size_of::<SplitTemplateRenderer>() <= 64,
|
||||
"SplitTemplateRenderer should be small enough to be allocated on the stack"
|
||||
);
|
||||
|
||||
@@ -1072,11 +1072,7 @@ impl SplitTemplateRenderer {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn render_start<W: std::io::Write>(
|
||||
&mut self,
|
||||
writer: W,
|
||||
data: JsonValue,
|
||||
) -> Result<(), RenderError> {
|
||||
fn render_start<W: Write>(&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<W: std::io::Write>(
|
||||
&mut self,
|
||||
writer: W,
|
||||
data: JsonValue,
|
||||
) -> Result<(), RenderError> {
|
||||
fn render_item<W: Write>(&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<W: std::io::Write>(&mut self, writer: W) -> Result<(), RenderError> {
|
||||
fn render_end<W: Write>(&mut self, writer: W) -> Result<(), RenderError> {
|
||||
log::trace!(
|
||||
"Closing a template {}",
|
||||
self.split_template
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+14
-14
@@ -168,7 +168,7 @@ fn to_array_helper(v: &JsonValue) -> JsonValue {
|
||||
struct StaticPathHelper(String);
|
||||
|
||||
impl CanHelp for StaticPathHelper {
|
||||
fn call(&self, args: &[PathAndJson]) -> Result<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String>;
|
||||
fn call(&self, v: &[PathAndJson<'_>]) -> Result<JsonValue, String>;
|
||||
}
|
||||
|
||||
impl CanHelp for H0 {
|
||||
fn call(&self, args: &[PathAndJson]) -> Result<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<JsonValue, String> {
|
||||
fn call(&self, args: &[PathAndJson<'_>]) -> Result<JsonValue, String> {
|
||||
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<F: CanHelp> {
|
||||
name: &'static str,
|
||||
fun: F,
|
||||
}
|
||||
impl<F: CanHelp> handlebars::HelperDef for JFun<F> {
|
||||
impl<F: CanHelp> HelperDef for JFun<F> {
|
||||
fn call_inner<'reg: 'rc, 'rc>(
|
||||
&self,
|
||||
helper: &handlebars::Helper<'rc>,
|
||||
_r: &'reg Handlebars<'reg>,
|
||||
_: &'rc Context,
|
||||
_rc: &mut handlebars::RenderContext<'reg, 'rc>,
|
||||
) -> Result<handlebars::ScopedJson<'rc>, RenderError> {
|
||||
) -> Result<ScopedJson<'rc>, RenderError> {
|
||||
let result = self
|
||||
.fun
|
||||
.call(helper.params().as_slice())
|
||||
@@ -585,7 +585,7 @@ impl<F: CanHelp> handlebars::HelperDef for JFun<F> {
|
||||
}
|
||||
}
|
||||
|
||||
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 }));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ pub struct AllTemplates {
|
||||
split_templates: FileCache<SplitTemplate>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
|
||||
@@ -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<Any>) ->
|
||||
}
|
||||
|
||||
fn on_return_to_pool(
|
||||
conn: &mut sqlx::any::AnyConnection,
|
||||
conn: &mut AnyConnection,
|
||||
meta: sqlx::pool::PoolConnectionMetadata,
|
||||
sql: std::sync::Arc<String>,
|
||||
) -> BoxFuture<'_, Result<bool, sqlx::error::Error>> {
|
||||
@@ -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));
|
||||
|
||||
@@ -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<CsvImpo
|
||||
}
|
||||
};
|
||||
|
||||
let all_options: Vec<CopyCsvOption> = legacy_options
|
||||
let all_options: Vec<CopyCsvOption<'_>> = legacy_options
|
||||
.iter()
|
||||
.flat_map(|o| match o {
|
||||
CopyLegacyOption::Csv(o) => {
|
||||
|
||||
@@ -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<W: std::fmt::Write>(msg: &mut W, line: &str, offset: usize) {
|
||||
pub(super) fn highlight_line_offset<W: Write>(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()
|
||||
|
||||
@@ -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::<ErrorWithStatus>().is_some() {
|
||||
@@ -335,7 +335,7 @@ async fn execute_single_row(
|
||||
query: &SingleRowQuery,
|
||||
req: &ExecutionContext,
|
||||
db_connection: &mut DbConn,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
) -> anyhow::Result<Value> {
|
||||
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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<Value>)> {
|
||||
@@ -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<sqlx::postgres::Postgres>
|
||||
+ for<'a> sqlx::decode::Decode<'a, sqlx::postgres::Postgres>,
|
||||
+ for<'a> Decode<'a, sqlx::postgres::Postgres>,
|
||||
{
|
||||
let Ok(pg_val): Result<PgValueRef<'r>, _> = raw_value.try_into() else {
|
||||
log::error!("Only postgres range values are supported");
|
||||
return Value::Null;
|
||||
};
|
||||
match <PgRange<T> as sqlx::decode::Decode<'r, sqlx::postgres::Postgres>>::decode(pg_val) {
|
||||
match <PgRange<T> 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::<bool>(raw_value).into()
|
||||
}
|
||||
"BIT" if matches!(db_type, MySql(_)) => decode_raw::<u64>(raw_value).into(),
|
||||
"DATE" => decode_raw::<chrono::NaiveDate>(raw_value)
|
||||
.to_string()
|
||||
.into(),
|
||||
"DATE" => decode_raw::<NaiveDate>(raw_value).to_string().into(),
|
||||
"TIME" | "TIMETZ" => decode_raw::<chrono::NaiveTime>(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,
|
||||
|
||||
@@ -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),*
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ impl URLParameters {
|
||||
}
|
||||
|
||||
fn encode_and_push(&mut self, v: &str) {
|
||||
let val: Cow<str> = 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::<Option<Cow<str>>>(raw_json_value) {
|
||||
if let Ok(str_val) = serde_json::from_str::<Option<Cow<'_, 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<str>, Cow<serde_json::value::RawValue>>()?
|
||||
map.next_entry::<Cow<'_, str>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
+8
-12
@@ -177,7 +177,7 @@ async fn build_response_header_and_stream<S: Stream<Item = DbItem>>(
|
||||
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 = impl std::fmt::Display + std::fmt::Debug>,
|
||||
>,
|
||||
Error = actix_web::Error,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
|
||||
@@ -45,7 +45,7 @@ pub struct RequestInfo {
|
||||
pub app_state: Arc<AppState>,
|
||||
pub raw_body: Option<Vec<u8>>,
|
||||
pub oidc_claims: Option<OidcClaims>,
|
||||
pub server_timing: Arc<super::server_timing::ServerTiming>,
|
||||
pub server_timing: Arc<ServerTiming>,
|
||||
}
|
||||
|
||||
#[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<Vec<(String, String)>> {
|
||||
Form::<Vec<(String, String)>>::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<bool> {
|
||||
async fn is_file_field_empty(uploaded_file: &TempFile) -> anyhow::Result<bool> {
|
||||
Ok(
|
||||
uploaded_file.content_type == Some(mime_guess::mime::APPLICATION_OCTET_STREAM)
|
||||
&& uploaded_file.file_name.as_deref().is_none_or(str::is_empty)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,11 +10,11 @@ type LambdaResponse = Response<Full<Bytes>>;
|
||||
type LambdaResult = Result<LambdaResponse, lambda_http::Error>;
|
||||
type LambdaRequest = (Request, oneshot::Sender<LambdaResult>);
|
||||
|
||||
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<F, I, S, B>(factory: F) -> Result<(), lambda_http::Error>
|
||||
pub(super) async fn run<F, I, S, B>(factory: F) -> Result<(), lambda_http::Error>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, actix_http::Request>,
|
||||
|
||||
@@ -66,12 +66,11 @@ pub struct OidcAdditionalClaims(pub(crate) serde_json::Map<String, serde_json::V
|
||||
impl openidconnect::AdditionalClaims for OidcAdditionalClaims {}
|
||||
type OidcToken = openidconnect::IdToken<
|
||||
OidcAdditionalClaims,
|
||||
openidconnect::core::CoreGenderClaim,
|
||||
openidconnect::core::CoreJweContentEncryptionAlgorithm,
|
||||
openidconnect::core::CoreJwsSigningAlgorithm,
|
||||
CoreGenderClaim,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
>;
|
||||
pub type OidcClaims =
|
||||
openidconnect::IdTokenClaims<OidcAdditionalClaims, openidconnect::core::CoreGenderClaim>;
|
||||
pub type OidcClaims = openidconnect::IdTokenClaims<OidcAdditionalClaims, CoreGenderClaim>;
|
||||
|
||||
#[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<ProviderMetadataWithLogout> {
|
||||
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<OidcToken> {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ pub(crate) struct AppFileStore<'a> {
|
||||
}
|
||||
|
||||
impl<'a> AppFileStore<'a> {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
cache: &'a FileCache<SqlFile>,
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+18
-17
@@ -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<AppState>,
|
||||
) -> actix_web::Result<TestRequest> {
|
||||
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<TestRequest> {
|
||||
pub(crate) async fn get_request_to(path: &str) -> actix_web::Result<TestRequest> {
|
||||
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<AppState> {
|
||||
pub(crate) async fn make_app_data_from_config(config: AppConfig) -> Data<AppState> {
|
||||
let state = AppState::init(&config).await.unwrap();
|
||||
Data::new(state)
|
||||
}
|
||||
|
||||
pub async fn make_app_data() -> Data<AppState> {
|
||||
pub(crate) async fn make_app_data() -> Data<AppState> {
|
||||
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<str>,
|
||||
) -> Result<actix_web::dev::ServiceResponse, actix_web::Error> {
|
||||
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<str>,
|
||||
app_data: Data<AppState>,
|
||||
) -> anyhow::Result<actix_web::dev::ServiceResponse> {
|
||||
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<str>,
|
||||
app_data: Data<AppState>,
|
||||
) -> anyhow::Result<actix_web::dev::ServiceResponse> {
|
||||
@@ -75,7 +76,7 @@ async fn req_path_with_app_data_and_accept(
|
||||
accept: header::Accept,
|
||||
) -> anyhow::Result<actix_web::dev::ServiceResponse> {
|
||||
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::<AppConfig>(&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<u8> {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn build_echo_response(body: Vec<u8>, 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))
|
||||
}))
|
||||
})
|
||||
|
||||
+5
-5
@@ -23,7 +23,7 @@ async fn test_concurrent_requests() {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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)
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
|
||||
+7
-7
@@ -14,7 +14,7 @@ async fn direct_request_status(path: &str, app_data: actix_web::web::Data<AppSta
|
||||
let req = test::TestRequest::get()
|
||||
.uri(path)
|
||||
.app_data(app_data)
|
||||
.insert_header(actix_web::http::header::Accept::html())
|
||||
.insert_header(http::header::Accept::html())
|
||||
.to_srv_request();
|
||||
match main_handler(req).await {
|
||||
Ok(resp) => 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"<!DOCTYPE html>"));
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
+25
-27
@@ -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::<Sha256>::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<Cookie<'static>> {
|
||||
}
|
||||
|
||||
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<R>(&self, f: impl FnOnce(&mut ProviderState) -> R) -> R {
|
||||
fn with_state_mut<R>(&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<String>,
|
||||
) {
|
||||
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!(
|
||||
|
||||
@@ -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(())
|
||||
|
||||
+14
-18
@@ -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<String> = expected.iter().map(|d| d.to_string()).collect();
|
||||
msg.push_str(&format!("Expected: {}\n", expected_strs.join(" or ")));
|
||||
let expected_strs: Vec<String> = 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user