Skip to main content

systemprompt_traits/
storage.rs

1//! File storage trait and identifier types.
2//!
3//! [`FileStorage`] is dispatched as a trait object (`dyn _`), so it uses
4//! `#[async_trait]`; native `async fn` in traits is not yet `dyn`-compatible.
5//!
6//! Defines the [`FileStorage`] abstraction implemented by every storage
7//! backend (local disk, object stores, cloud blob services), along with the
8//! [`StoredFileId`] / [`StoredFileMetadata`] value types returned across the
9//! storage boundary. Errors are reported as [`FileStorageError`] so that
10//! callers can match on cause rather than parsing strings.
11//!
12//! Copyright (c) systemprompt.io — Business Source License 1.1.
13//! See <https://systemprompt.io> for licensing details.
14
15use async_trait::async_trait;
16use std::path::Path;
17
18use crate::BoxedSource;
19
20pub type FileStorageResult<T> = Result<T, FileStorageError>;
21
22#[derive(Debug, thiserror::Error)]
23#[non_exhaustive]
24pub enum FileStorageError {
25    #[error("file not found: {0}")]
26    NotFound(String),
27
28    #[error("validation failed: {0}")]
29    Validation(String),
30
31    #[error("io error: {0}")]
32    Io(#[from] std::io::Error),
33
34    #[error("serialization error: {0}")]
35    Serialization(#[from] serde_json::Error),
36
37    #[error("storage backend error: {0}")]
38    Backend(#[source] BoxedSource),
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct StoredFileId(pub String);
43
44impl StoredFileId {
45    #[must_use]
46    pub fn new(id: impl Into<String>) -> Self {
47        Self(id.into())
48    }
49
50    #[must_use]
51    pub fn as_str(&self) -> &str {
52        &self.0
53    }
54}
55
56impl From<String> for StoredFileId {
57    fn from(s: String) -> Self {
58        Self(s)
59    }
60}
61
62impl From<&str> for StoredFileId {
63    fn from(s: &str) -> Self {
64        Self(s.to_owned())
65    }
66}
67
68impl std::fmt::Display for StoredFileId {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}", self.0)
71    }
72}
73
74#[derive(Debug, Clone)]
75pub struct StoredFileMetadata {
76    pub id: StoredFileId,
77    pub path: String,
78    pub mime_type: String,
79    pub size_bytes: Option<i64>,
80    pub created_at: chrono::DateTime<chrono::Utc>,
81    pub updated_at: chrono::DateTime<chrono::Utc>,
82}
83
84#[async_trait]
85pub trait FileStorage: Send + Sync {
86    async fn store(&self, path: &Path, content: &[u8]) -> FileStorageResult<StoredFileId>;
87
88    async fn retrieve(&self, id: &StoredFileId) -> FileStorageResult<Vec<u8>>;
89
90    async fn delete(&self, id: &StoredFileId) -> FileStorageResult<()>;
91
92    async fn metadata(&self, id: &StoredFileId) -> FileStorageResult<StoredFileMetadata>;
93
94    async fn exists(&self, id: &StoredFileId) -> FileStorageResult<bool>;
95
96    fn public_url(&self, _id: &StoredFileId) -> Option<String> {
97        None
98    }
99}