Skip to content

Commit e9ce54f

Browse files
committed
fmt
1 parent 80164dd commit e9ce54f

File tree

7 files changed

+106
-19
lines changed

7 files changed

+106
-19
lines changed

src/app_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::webserver::routing::RoutingConfig;
12
use anyhow::Context;
23
use clap::Parser;
34
use config::Config;
@@ -6,7 +7,6 @@ use serde::de::Error;
67
use serde::{Deserialize, Deserializer, Serialize};
78
use std::net::{SocketAddr, ToSocketAddrs};
89
use std::path::{Path, PathBuf};
9-
use crate::webserver::routing::RoutingConfig;
1010

1111
#[derive(Parser)]
1212
#[clap(author, version, about, long_about = None)]

src/file_cache.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::webserver::routing::FileStore;
12
use crate::webserver::ErrorWithStatus;
23
use crate::AppState;
34
use actix_web::http::StatusCode;
@@ -13,7 +14,6 @@ use std::sync::atomic::{
1314
use std::sync::Arc;
1415
use std::time::SystemTime;
1516
use tokio::sync::RwLock;
16-
use crate::webserver::routing::FileStore;
1717

1818
/// The maximum time in milliseconds that a file can be cached before its freshness is checked
1919
/// (in production mode)
@@ -77,8 +77,7 @@ pub struct FileCache<T: AsyncFromStrWithState> {
7777

7878
impl<T: AsyncFromStrWithState> FileStore for FileCache<T> {
7979
async fn contains(&self, path: &Path) -> anyhow::Result<bool> {
80-
Ok(self.cache.read().await.contains_key(path)
81-
|| self.static_files.contains_key(path))
80+
Ok(self.cache.read().await.contains_key(path) || self.static_files.contains_key(path))
8281
}
8382
}
8483

src/filesystem.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,14 @@ impl FileSystem {
132132
Ok(self.local_root.join(path))
133133
}
134134

135-
pub(crate) async fn contains(&self, app_state: &AppState, path: &Path) -> anyhow::Result<bool> {
135+
pub(crate) async fn file_exists(
136+
&self,
137+
app_state: &AppState,
138+
path: &Path,
139+
) -> anyhow::Result<bool> {
136140
let local_exists = match self.safe_local_path(app_state, path, false) {
137141
Ok(safe_path) => tokio::fs::try_exists(safe_path).await?,
138-
Err(e) => {
139-
log::error!("Unable to check if {path:?} exists in the local filesystem: {e:#}");
140-
return Err(e);
141-
}
142+
Err(e) => return Err(e),
142143
};
143144

144145
// If not in local fs and we have db_fs, check database
@@ -158,14 +159,14 @@ async fn file_modified_since_local(path: &Path, since: DateTime<Utc>) -> tokio::
158159
.map(|modified_at| DateTime::<Utc>::from(modified_at) > since)
159160
}
160161

161-
pub(crate) struct DbFsQueries {
162+
pub struct DbFsQueries {
162163
was_modified: AnyStatement<'static>,
163164
read_file: AnyStatement<'static>,
164165
exists: AnyStatement<'static>,
165166
}
166167

167168
impl DbFsQueries {
168-
fn get_create_table_sql(db_kind: AnyKind) -> &'static str {
169+
pub fn get_create_table_sql(db_kind: AnyKind) -> &'static str {
169170
match db_kind {
170171
AnyKind::Mssql => "CREATE TABLE sqlpage_files(path NVARCHAR(255) NOT NULL PRIMARY KEY, contents VARBINARY(MAX), last_modified DATETIME2(3) NOT NULL DEFAULT CURRENT_TIMESTAMP);",
171172
AnyKind::Postgres => "CREATE TABLE IF NOT EXISTS sqlpage_files(path VARCHAR(255) NOT NULL PRIMARY KEY, contents BYTEA, last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP);",

src/webserver/database/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod sql_to_json;
1212
pub use sql::{make_placeholder, ParsedSqlFile};
1313

1414
pub struct Database {
15-
pub(crate) connection: sqlx::AnyPool,
15+
pub connection: sqlx::AnyPool,
1616
}
1717
impl Database {
1818
pub async fn close(&self) -> anyhow::Result<()> {

src/webserver/http.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,9 @@ pub async fn main_handler(
390390
let routing_action = match calculate_route(path_and_query, &store, &app_state.config).await {
391391
Ok(action) => action,
392392
Err(e) => {
393-
let e = e.context(format!("Unable to calculate the routing action for: {path_and_query:?}"));
393+
let e = e.context(format!(
394+
"Unable to calculate the routing action for: {path_and_query:?}"
395+
));
394396
return Err(anyhow_err_to_actix(e, app_state.config.environment));
395397
}
396398
};

src/webserver/routing.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use crate::{file_cache::FileCache, AppState};
21
use crate::filesystem::FileSystem;
32
use crate::webserver::database::ParsedSqlFile;
3+
use crate::{file_cache::FileCache, AppState};
44
use awc::http::uri::PathAndQuery;
55
use log::debug;
66
use std::path::{Path, PathBuf};
@@ -54,7 +54,7 @@ impl FileStore for AppFileStore<'_> {
5454
if self.cache.contains(path).await? {
5555
Ok(true)
5656
} else {
57-
self.filesystem.contains(self.app_state, path).await
57+
self.filesystem.file_exists(self.app_state, path).await
5858
}
5959
}
6060
}
@@ -112,7 +112,11 @@ where
112112
}
113113
}
114114

115-
async fn find_file_or_not_found<T>(path: &Path, extension: &str, store: &T) -> anyhow::Result<RoutingAction>
115+
async fn find_file_or_not_found<T>(
116+
path: &Path,
117+
extension: &str,
118+
store: &T,
119+
) -> anyhow::Result<RoutingAction>
116120
where
117121
T: FileStore,
118122
{
@@ -122,7 +126,11 @@ where
122126
}
123127
}
124128

125-
async fn find_file<T>(path: &Path, extension: &str, store: &T) -> anyhow::Result<Option<RoutingAction>>
129+
async fn find_file<T>(
130+
path: &Path,
131+
extension: &str,
132+
store: &T,
133+
) -> anyhow::Result<Option<RoutingAction>>
126134
where
127135
T: FileStore,
128136
{
@@ -426,7 +434,9 @@ mod tests {
426434
None => Config::default(),
427435
Some(value) => Config::new(value),
428436
};
429-
calculate_route(&PathAndQuery::from_str(path).unwrap(), &store, &config).await.unwrap()
437+
calculate_route(&PathAndQuery::from_str(path).unwrap(), &store, &config)
438+
.await
439+
.unwrap()
430440
}
431441

432442
fn default_not_found() -> RoutingAction {

tests/index.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ use actix_web::{
1313
};
1414
use sqlpage::{
1515
app_config::{test_database_url, AppConfig},
16+
filesystem::DbFsQueries,
1617
webserver::{
1718
self,
1819
http::{form_config, main_handler, payload_config},
1920
},
2021
AppState,
2122
};
23+
use sqlx::Executor as _;
2224

2325
#[actix_web::test]
2426
async fn test_index_ok() {
@@ -695,6 +697,74 @@ async fn test_failed_copy_followed_by_query() -> actix_web::Result<()> {
695697
Ok(())
696698
}
697699

700+
#[actix_web::test]
701+
async fn test_routing_with_db_fs_and_prefix() {
702+
let mut config = test_config();
703+
config.site_prefix = "/prefix/".to_string();
704+
let state = AppState::init(&config).await.unwrap();
705+
706+
// Set up database filesystem
707+
let create_table_sql = DbFsQueries::get_create_table_sql(state.db.connection.any_kind());
708+
state
709+
.db
710+
.connection
711+
.execute(format!("DROP TABLE IF EXISTS sqlpage_files; {create_table_sql}").as_str())
712+
.await
713+
.unwrap();
714+
715+
// Insert test file into database
716+
sqlx::query("INSERT INTO sqlpage_files(path, contents) VALUES ('tests/sql_test_files/it_works_simple.sql', 'SELECT ''It works !'' as message;')")
717+
.execute(&state.db.connection)
718+
.await
719+
.unwrap();
720+
721+
let app_data = actix_web::web::Data::new(state);
722+
723+
// Test basic routing with prefix
724+
let resp = req_path_with_app_data(
725+
"/prefix/tests/sql_test_files/it_works_simple.sql",
726+
app_data.clone(),
727+
)
728+
.await
729+
.unwrap();
730+
assert_eq!(resp.status(), http::StatusCode::OK);
731+
let body = test::read_body(resp).await;
732+
let body_str = String::from_utf8(body.to_vec()).unwrap();
733+
assert!(
734+
body_str.contains("It works !"),
735+
"{body_str}\nexpected to contain: It works !"
736+
);
737+
assert!(
738+
body_str.contains("href=\"/prefix/"),
739+
"{body_str}\nexpected to contain links with site prefix"
740+
);
741+
742+
// Test 404 handling with prefix
743+
let resp = req_path_with_app_data("/prefix/nonexistent.sql", app_data.clone())
744+
.await
745+
.expect_err("Expected 404 error")
746+
.to_string();
747+
assert!(resp.contains("404"));
748+
749+
// Test forbidden paths with prefix
750+
let resp = req_path_with_app_data("/prefix/sqlpage/migrations/0001_init.sql", app_data.clone())
751+
.await
752+
.expect_err("Expected forbidden error")
753+
.to_string();
754+
assert!(resp.to_lowercase().contains("forbidden"), "{resp}");
755+
756+
// accessing without prefix should redirect to the prefix
757+
let resp = req_path_with_app_data("/tests/sql_test_files/it_works_simple.sql", app_data)
758+
.await
759+
.unwrap();
760+
assert_eq!(resp.status(), http::StatusCode::MOVED_PERMANENTLY);
761+
let location = resp
762+
.headers()
763+
.get("location")
764+
.expect("location header should be present");
765+
assert_eq!(location.to_str().unwrap(), "/prefix/");
766+
}
767+
698768
async fn get_request_to_with_data(
699769
path: &str,
700770
data: actix_web::web::Data<AppState>,
@@ -752,7 +822,12 @@ async fn req_path_with_app_data(
752822
let resp = tokio::time::timeout(REQ_TIMEOUT, main_handler(req))
753823
.await
754824
.map_err(|e| anyhow::anyhow!("Request to {path} timed out: {e}"))?
755-
.map_err(|e| anyhow::anyhow!("Request to {path} failed: {e}"))?;
825+
.map_err(|e| {
826+
anyhow::anyhow!(
827+
"Request to {path} failed with status {}: {e:#}",
828+
e.as_response_error().status_code()
829+
)
830+
})?;
756831
Ok(resp)
757832
}
758833

0 commit comments

Comments
 (0)