use std::path::Path;
use crate::metering::MeteringStore;
use crate::paths::ConfigSource;
pub enum ReadSide {
Store(MeteringStore),
Absent,
Unavailable,
}
pub async fn read_side(source: &ConfigSource) -> ReadSide {
let config = match crate::paths::load_config(source).await {
Ok(c) => c,
Err(_) => return ReadSide::Unavailable,
};
let url = match resolve_sqlite_url(&config.database.url, source.home()) {
Some(u) => u,
None => return ReadSide::Absent,
};
match crate::db::connect(&url).await {
Ok(db) => ReadSide::Store(MeteringStore::new(db)),
Err(_) => ReadSide::Unavailable,
}
}
pub async fn open_readonly(source: &ConfigSource) -> Option<MeteringStore> {
match read_side(source).await {
ReadSide::Store(store) => Some(store),
ReadSide::Absent | ReadSide::Unavailable => None,
}
}
fn resolve_sqlite_url(url: &str, home: &Path) -> Option<String> {
let after_scheme = match url
.strip_prefix("sqlite://")
.or_else(|| url.strip_prefix("sqlite:"))
{
Some(rest) => rest,
None => return Some(url.to_string()),
};
let path_part = after_scheme.split('?').next().unwrap_or(after_scheme);
if path_part.is_empty() || path_part == ":memory:" {
return None;
}
let path = Path::new(path_part.strip_prefix("./").unwrap_or(path_part));
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
home.join(path)
};
if !absolute.exists() {
return None;
}
Some(format!("sqlite://{}?mode=ro", absolute.display()))
}
#[cfg(test)]
mod tests {
use super::resolve_sqlite_url;
use std::path::Path;
#[test]
fn non_sqlite_urls_pass_through() {
assert_eq!(
resolve_sqlite_url("postgres://db.internal/bitrouter", Path::new("/h")),
Some("postgres://db.internal/bitrouter".to_string())
);
}
#[test]
fn memory_and_empty_are_none() {
assert_eq!(resolve_sqlite_url("sqlite::memory:", Path::new("/h")), None);
assert_eq!(resolve_sqlite_url("sqlite://", Path::new("/h")), None);
}
#[test]
fn missing_file_is_none() {
assert_eq!(
resolve_sqlite_url(
"sqlite://./does-not-exist.db",
Path::new("/nonexistent-home")
),
None
);
}
#[test]
fn relative_path_anchors_to_home_when_present() {
let dir = std::env::temp_dir().join(format!("br-reader-test-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let db = dir.join("bitrouter.db");
let _ = std::fs::write(&db, b"");
let got = resolve_sqlite_url("sqlite://./bitrouter.db", &dir);
assert_eq!(got, Some(format!("sqlite://{}?mode=ro", db.display())));
let _ = std::fs::remove_dir_all(&dir);
}
}