Skip to content

Commit e1c5d80

Browse files
committed
bytes instead of strings in file-system interface
also adds a test
1 parent 18968d8 commit e1c5d80

File tree

3 files changed

+46
-10
lines changed

3 files changed

+46
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ you can store the files directly inside the database, in a table that has the fo
137137
```sql
138138
CREATE TABLE sqlpage_files(
139139
path VARCHAR(255) NOT NULL PRIMARY KEY,
140-
contents TEXT,
140+
contents BLOB,
141141
last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
142142
);
143143
```

src/file_cache.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,10 @@ impl<T: AsyncFromStrWithState> FileCache<T> {
103103
log::trace!("Loading and parsing {:?}", path);
104104
let file_contents = app_state
105105
.file_system
106-
.read_file(app_state, path)
106+
.read_to_string(app_state, path)
107107
.await
108108
.with_context(|| format!("Couldn't load {path:?} into cache"));
109+
109110
let parsed = match file_contents {
110111
Ok(contents) => Ok(T::from_str_with_state(app_state, &contents).await?),
111112
Err(e) => Err(e),

src/filesystem.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::Context;
44
use chrono::{DateTime, Utc};
55
use sqlx::any::{AnyKind, AnyStatement, AnyTypeInfo};
66
use sqlx::postgres::types::PgTimeTz;
7-
use sqlx::{Postgres, Statement, Type};
7+
use sqlx::{Executor, Postgres, Statement, Type};
88
use std::io::ErrorKind;
99
use std::path::{Component, Path, PathBuf};
1010

@@ -14,9 +14,9 @@ pub(crate) struct FileSystem {
1414
}
1515

1616
impl FileSystem {
17-
pub async fn init(local_root: &Path, db: &Database) -> Self {
17+
pub async fn init(local_root: impl Into<PathBuf>, db: &Database) -> Self {
1818
Self {
19-
local_root: PathBuf::from(local_root),
19+
local_root: local_root.into(),
2020
db_fs_queries: match DbFsQueries::init(db).await {
2121
Ok(q) => Some(q),
2222
Err(e) => {
@@ -53,14 +53,24 @@ impl FileSystem {
5353
}
5454
}
5555

56-
pub async fn read_file(&self, app_state: &AppState, path: &Path) -> anyhow::Result<String> {
56+
pub async fn read_to_string(
57+
&self,
58+
app_state: &AppState,
59+
path: &Path,
60+
) -> anyhow::Result<String> {
61+
let bytes = self.read_file(app_state, path).await?;
62+
String::from_utf8(bytes)
63+
.with_context(|| format!("The file at {path:?} contains invalid UTF8 characters"))
64+
}
65+
66+
pub async fn read_file(&self, app_state: &AppState, path: &Path) -> anyhow::Result<Vec<u8>> {
5767
let local_path = self.safe_local_path(path)?;
58-
let local_result = tokio::fs::read_to_string(&local_path).await;
68+
let local_result = tokio::fs::read(&local_path).await;
5969
match (local_result, &self.db_fs_queries) {
6070
(Ok(f), _) => Ok(f),
6171
(Err(e), Some(db_fs)) if e.kind() == ErrorKind::NotFound => {
6272
// no local file, try the database
63-
db_fs.read_file(app_state, path).await
73+
db_fs.read_file(app_state, path.as_ref()).await
6474
}
6575
(Err(e), _) => Err(e).with_context(|| format!("Unable to read local file {path:?}")),
6676
}
@@ -142,13 +152,38 @@ impl DbFsQueries {
142152
})
143153
}
144154

145-
async fn read_file(&self, app_state: &AppState, path: &Path) -> anyhow::Result<String> {
155+
async fn read_file(&self, app_state: &AppState, path: &Path) -> anyhow::Result<Vec<u8>> {
146156
self.read_file
147-
.query_as::<(String,)>()
157+
.query_as::<(Vec<u8>,)>()
148158
.bind(path.display().to_string())
149159
.fetch_one(&app_state.db.connection)
150160
.await
151161
.map(|(modified,)| modified)
152162
.with_context(|| format!("Unable to read {path:?} from the database"))
153163
}
154164
}
165+
166+
#[actix_web::test]
167+
async fn test_sql_file_read_utf8() -> anyhow::Result<()> {
168+
let state = AppState::init().await?;
169+
state
170+
.db
171+
.connection
172+
.execute(
173+
r#"
174+
CREATE TABLE sqlpage_files(
175+
path VARCHAR(255) NOT NULL PRIMARY KEY,
176+
contents BLOB,
177+
last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
178+
);
179+
INSERT INTO sqlpage_files(path, contents) VALUES ('unit test file.txt', 'Héllö world! 😀');
180+
"#,
181+
)
182+
.await?;
183+
let fs = FileSystem::init("/", &state.db).await;
184+
let actual = fs
185+
.read_to_string(&state, "unit test file.txt".as_ref())
186+
.await?;
187+
assert_eq!(actual, "Héllö world! 😀");
188+
Ok(())
189+
}

0 commit comments

Comments
 (0)