1use std::fs;
2use std::path::{Path, PathBuf};
3
4use uuid::Uuid;
5
6#[derive(Debug)]
9pub struct TempDbPath {
10 path: PathBuf,
11}
12
13impl TempDbPath {
14 pub fn new(prefix: impl AsRef<str>) -> Self {
15 let sanitized = sanitize_prefix(prefix.as_ref());
16 let path = std::env::temp_dir().join(format!(
17 "{sanitized}-{}-{}.sqlite",
18 std::process::id(),
19 Uuid::new_v4()
20 ));
21 Self { path }
22 }
23
24 pub fn path(&self) -> &Path {
25 &self.path
26 }
27
28 pub fn to_string_lossy(&self) -> String {
29 self.path.to_string_lossy().into_owned()
30 }
31
32 pub fn remove_now(&self) {
33 remove_sqlite_path(&self.path);
34 }
35}
36
37impl AsRef<Path> for TempDbPath {
38 fn as_ref(&self) -> &Path {
39 self.path()
40 }
41}
42
43impl Drop for TempDbPath {
44 fn drop(&mut self) {
45 remove_sqlite_path(&self.path);
46 }
47}
48
49pub fn temp_db_path(prefix: impl AsRef<str>) -> TempDbPath {
50 TempDbPath::new(prefix)
51}
52
53pub fn unique_temp_db_path(prefix: impl AsRef<str>) -> String {
54 TempDbPath::new(prefix).to_string_lossy()
55}
56
57pub fn unique_temp_file_path(prefix: impl AsRef<str>) -> String {
58 let sanitized = sanitize_prefix(prefix.as_ref());
59 std::env::temp_dir()
60 .join(format!(
61 "{sanitized}-{}-{}",
62 std::process::id(),
63 Uuid::new_v4()
64 ))
65 .to_string_lossy()
66 .into_owned()
67}
68
69fn remove_sqlite_path(path: &Path) {
70 let _ = fs::remove_file(path);
71 for suffix in ["-wal", "-shm", "-journal"] {
72 let sidecar = PathBuf::from(format!("{}{}", path.to_string_lossy(), suffix));
73 let _ = fs::remove_file(sidecar);
74 }
75}
76
77fn sanitize_prefix(prefix: &str) -> String {
78 prefix
79 .chars()
80 .map(|ch| {
81 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
82 ch
83 } else {
84 '-'
85 }
86 })
87 .collect()
88}