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
18pub type FileStorageResult<T> = Result<T, FileStorageError>;
19
20#[derive(Debug, thiserror::Error)]
21#[non_exhaustive]
22pub enum FileStorageError {
23    #[error("file not found: {0}")]
24    NotFound(String),
25
26    #[error("validation failed: {0}")]
27    Validation(String),
28
29    #[error("io error: {0}")]
30    Io(#[from] std::io::Error),
31
32    #[error("serialization error: {0}")]
33    Serialization(#[from] serde_json::Error),
34
35    #[error("storage backend error: {0}")]
36    Backend(String),
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct StoredFileId(pub String);
41
42impl StoredFileId {
43    #[must_use]
44    pub fn new(id: impl Into<String>) -> Self {
45        Self(id.into())
46    }
47
48    #[must_use]
49    pub fn as_str(&self) -> &str {
50        &self.0
51    }
52}
53
54impl From<String> for StoredFileId {
55    fn from(s: String) -> Self {
56        Self(s)
57    }
58}
59
60impl From<&str> for StoredFileId {
61    fn from(s: &str) -> Self {
62        Self(s.to_owned())
63    }
64}
65
66impl std::fmt::Display for StoredFileId {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{}", self.0)
69    }
70}
71
72#[derive(Debug, Clone)]
73pub struct StoredFileMetadata {
74    pub id: StoredFileId,
75    pub path: String,
76    pub mime_type: String,
77    pub size_bytes: Option<i64>,
78    pub created_at: chrono::DateTime<chrono::Utc>,
79    pub updated_at: chrono::DateTime<chrono::Utc>,
80}
81
82#[async_trait]
83pub trait FileStorage: Send + Sync {
84    async fn store(&self, path: &Path, content: &[u8]) -> FileStorageResult<StoredFileId>;
85
86    async fn retrieve(&self, id: &StoredFileId) -> FileStorageResult<Vec<u8>>;
87
88    async fn delete(&self, id: &StoredFileId) -> FileStorageResult<()>;
89
90    async fn metadata(&self, id: &StoredFileId) -> FileStorageResult<StoredFileMetadata>;
91
92    async fn exists(&self, id: &StoredFileId) -> FileStorageResult<bool>;
93
94    fn public_url(&self, _id: &StoredFileId) -> Option<String> {
95        None
96    }
97}