solid_pod_rs/storage/mod.rs
1//! Storage abstraction for Solid pods.
2//!
3//! The `Storage` trait is the sole interface between the Solid
4//! protocol layer and concrete persistence backends. Implementations
5//! must be `Send + Sync + 'static` and safe for concurrent access.
6//!
7//! Two backends ship with the crate:
8//!
9//! - `memory::MemoryBackend` — in-memory, ideal for tests.
10//! - `fs::FsBackend` — filesystem-rooted, uses `tokio::fs` and
11//! `notify` for change events.
12
13use async_trait::async_trait;
14use bytes::Bytes;
15use serde::{Deserialize, Serialize};
16
17use crate::error::PodError;
18
19#[cfg(feature = "fs-backend")]
20pub mod fs;
21
22#[cfg(feature = "memory-backend")]
23pub mod memory;
24
25/// Metadata describing a resource stored in a pod.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ResourceMeta {
28 /// Strong ETag (typically hex-encoded SHA-256).
29 pub etag: String,
30 /// Last modification time, UTC.
31 pub modified: chrono::DateTime<chrono::Utc>,
32 /// Size of the body in bytes.
33 pub size: u64,
34 /// MIME type, e.g. `"application/ld+json"`.
35 pub content_type: String,
36 /// `Link` header values.
37 ///
38 /// Each entry is a single `Link` value (no outer commas), e.g.
39 /// `<http://www.w3.org/ns/ldp#Resource>; rel="type"`.
40 pub links: Vec<String>,
41}
42
43impl ResourceMeta {
44 /// Construct a default `ResourceMeta` with the current UTC time.
45 pub fn new(etag: impl Into<String>, size: u64, content_type: impl Into<String>) -> Self {
46 ResourceMeta {
47 etag: etag.into(),
48 modified: chrono::Utc::now(),
49 size,
50 content_type: content_type.into(),
51 links: Vec::new(),
52 }
53 }
54}
55
56/// Change events emitted by storage watchers.
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub enum StorageEvent {
59 /// A resource was created at the given path.
60 Created(String),
61 /// A resource was updated at the given path.
62 Updated(String),
63 /// A resource was deleted at the given path.
64 Deleted(String),
65}
66
67/// The storage abstraction. Implementations back a Solid pod with
68/// arbitrary persistence.
69///
70/// All paths use forward slashes and are rooted at `/`. Container
71/// paths end with `/`.
72#[async_trait]
73pub trait Storage: Send + Sync + 'static {
74 /// Fetch a resource body + metadata.
75 async fn get(&self, path: &str) -> Result<(Bytes, ResourceMeta), PodError>;
76
77 /// Write (create-or-replace) a resource.
78 ///
79 /// Returns the new metadata including the computed ETag.
80 async fn put(
81 &self,
82 path: &str,
83 body: Bytes,
84 content_type: &str,
85 ) -> Result<ResourceMeta, PodError>;
86
87 /// Delete a resource.
88 async fn delete(&self, path: &str) -> Result<(), PodError>;
89
90 /// List direct children of a container.
91 ///
92 /// Returned paths are relative to the container. A trailing `/`
93 /// indicates a sub-container.
94 async fn list(&self, container: &str) -> Result<Vec<String>, PodError>;
95
96 /// Fetch metadata without the body.
97 async fn head(&self, path: &str) -> Result<ResourceMeta, PodError>;
98
99 /// Return whether a resource exists.
100 async fn exists(&self, path: &str) -> Result<bool, PodError>;
101
102 /// Register a watcher for a resource or container.
103 ///
104 /// The returned channel receives `StorageEvent` messages for
105 /// changes under `path`. Closing the receiver detaches the
106 /// watcher.
107 async fn watch(
108 &self,
109 path: &str,
110 ) -> Result<tokio::sync::mpsc::Receiver<StorageEvent>, PodError>;
111}