1use std::fs;
2use std::io;
3use std::path::PathBuf;
4
5#[derive(thiserror::Error, Debug)]
6pub enum DocStoreError {
7 #[error("io error: {0}")]
8 Io(String),
9}
10
11impl From<io::Error> for DocStoreError {
12 fn from(err: io::Error) -> Self {
13 DocStoreError::Io(err.to_string())
14 }
15}
16
17#[derive(Debug, Clone)]
18pub struct DocKey {
19 pub tenant_id: String,
20 pub workspace_id: String,
21 pub source_id: String,
22 pub doc_id: String,
23}
24
25pub trait DocStore: Send + Sync {
26 fn put(&self, key: &DocKey, bytes: &[u8]) -> Result<PathBuf, DocStoreError>;
27 fn get(&self, key: &DocKey) -> Result<Vec<u8>, DocStoreError>;
28 fn delete(&self, key: &DocKey) -> Result<(), DocStoreError>;
29}
30
31#[derive(Debug, Clone)]
32pub struct FsDocStore {
33 pub root: PathBuf,
34}
35
36impl FsDocStore {
37 pub fn new(root: impl Into<PathBuf>) -> Self {
38 Self { root: root.into() }
39 }
40
41 fn path_for(&self, key: &DocKey) -> PathBuf {
42 self.root
43 .join(&key.tenant_id)
44 .join(&key.workspace_id)
45 .join(&key.source_id)
46 .join(&key.doc_id)
47 }
48}
49
50impl DocStore for FsDocStore {
51 fn put(&self, key: &DocKey, bytes: &[u8]) -> Result<PathBuf, DocStoreError> {
52 let path = self.path_for(key);
53 if let Some(parent) = path.parent() {
54 fs::create_dir_all(parent)?;
55 }
56 fs::write(&path, bytes)?;
57 Ok(path)
58 }
59
60 fn get(&self, key: &DocKey) -> Result<Vec<u8>, DocStoreError> {
61 let path = self.path_for(key);
62 let data = fs::read(path)?;
63 Ok(data)
64 }
65
66 fn delete(&self, key: &DocKey) -> Result<(), DocStoreError> {
67 let path = self.path_for(key);
68 if path.exists() {
69 fs::remove_file(path)?;
70 }
71 Ok(())
72 }
73}