anyfs_backend/layer.rs
1//! # Layer Trait
2//!
3//! Tower-style middleware composition for filesystem backends.
4//!
5//! ## Overview
6//!
7//! The [`Layer`] trait enables composable middleware that wraps backends to add
8//! functionality like caching, encryption, rate limiting, or logging.
9//!
10//! ## How It Works
11//!
12//! ```text
13//! Backend ──▶ Layer::layer() ──▶ Wrapped Backend
14//! ```
15//!
16//! Each middleware provides:
17//! 1. A wrapper struct that implements filesystem traits
18//! 2. A `Layer` implementation that creates the wrapper
19//!
20//! ## Example
21//!
22//! The Layer pattern separates middleware configuration from wrapping:
23//!
24//! ```rust
25//! use anyfs_backend::Layer;
26//!
27//! // Configuration for the layer
28//! struct CacheConfig {
29//! max_entries: usize,
30//! }
31//!
32//! // The layer holds configuration
33//! struct CacheLayer {
34//! config: CacheConfig,
35//! }
36//!
37//! // The middleware wraps any backend
38//! struct CacheMiddleware<B> {
39//! inner: B,
40//! config: CacheConfig,
41//! }
42//!
43//! // Layer creates the middleware
44//! impl<B> Layer<B> for CacheLayer {
45//! type Backend = CacheMiddleware<B>;
46//!
47//! fn layer(self, backend: B) -> Self::Backend {
48//! CacheMiddleware {
49//! inner: backend,
50//! config: self.config,
51//! }
52//! }
53//! }
54//! ```
55//!
56//! ## Fluent Composition
57//!
58//! Use [`LayerExt`] for fluent chaining:
59//!
60//! ```rust
61//! use anyfs_backend::LayerExt;
62//!
63//! // Hypothetical usage (requires concrete backend):
64//! // let backend = MemoryBackend::new()
65//! // .layer(QuotaLayer::new(limits))
66//! // .layer(TracingLayer::new());
67//! ```
68
69use crate::Fs;
70
71/// A layer that wraps a backend to add functionality.
72///
73/// Inspired by Tower's `Layer` trait, this enables composable middleware.
74/// Each middleware provides a corresponding `Layer` implementation.
75///
76/// # Type Parameters
77///
78/// - `B`: The backend type being wrapped (must implement [`Fs`])
79///
80/// # Design Notes
81///
82/// - `layer(self, backend)` consumes both the layer and backend
83/// - The resulting `Backend` type must also implement `Fs`
84/// - Middleware needing higher traits (e.g., `FsLink`) can add bounds in their impl
85///
86/// # Example
87///
88/// ```rust
89/// use anyfs_backend::Layer;
90///
91/// struct LoggingMiddleware<B> {
92/// inner: B,
93/// prefix: String,
94/// }
95///
96/// struct LoggingLayer {
97/// prefix: String,
98/// }
99///
100/// impl<B> Layer<B> for LoggingLayer {
101/// type Backend = LoggingMiddleware<B>;
102///
103/// fn layer(self, backend: B) -> Self::Backend {
104/// LoggingMiddleware {
105/// inner: backend,
106/// prefix: self.prefix,
107/// }
108/// }
109/// }
110/// ```
111pub trait Layer<B> {
112 /// The resulting backend type after applying this layer.
113 ///
114 /// For middleware that preserves filesystem capabilities, this type
115 /// should implement the same traits as the input backend `B`.
116 type Backend;
117
118 /// Wrap the given backend with this layer's functionality.
119 ///
120 /// Consumes both the layer configuration and the backend,
121 /// returning a new wrapped backend.
122 fn layer(self, backend: B) -> Self::Backend;
123}
124
125/// Extension trait for fluent layer composition.
126///
127/// Provides the `.layer()` method on any `Fs` backend for ergonomic chaining.
128///
129/// # Example
130///
131/// ```rust
132/// use anyfs_backend::{Fs, LayerExt, Layer};
133///
134/// // With LayerExt, you can chain layers fluently:
135/// fn compose_backend<B: Fs, L: Layer<B>>(backend: B, layer: L) -> L::Backend {
136/// backend.layer(layer)
137/// }
138/// ```
139pub trait LayerExt: Fs + Sized {
140 /// Apply a layer to this backend.
141 ///
142 /// Returns the wrapped backend with the layer's functionality added.
143 ///
144 /// # Example
145 ///
146 /// ```rust
147 /// use anyfs_backend::{Fs, LayerExt, Layer};
148 ///
149 /// fn add_middleware<B, L>(backend: B, layer: L) -> L::Backend
150 /// where
151 /// B: Fs,
152 /// L: Layer<B>,
153 /// {
154 /// backend.layer(layer)
155 /// }
156 /// ```
157 fn layer<L: Layer<Self>>(self, layer: L) -> L::Backend {
158 layer.layer(self)
159 }
160}
161
162// Blanket implementation - any Fs backend gets LayerExt for free
163impl<B: Fs> LayerExt for B {}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 /// Verify Layer trait is object-safe (can be used as trait object)
170 /// Note: Layer is NOT object-safe due to generic parameter and Sized bound
171 /// This is intentional - layers are compile-time composition
172
173 #[test]
174 fn layer_ext_is_auto_implemented() {
175 // LayerExt is blanket-implemented for all Fs types
176 fn _check<B: Fs + LayerExt>() {}
177 }
178
179 #[test]
180 fn layer_composes_types() {
181 use crate::{FsDir, FsRead, FsWrite, ReadDirIter};
182 use std::path::Path;
183
184 // Mock backend
185 struct MockBackend;
186
187 impl FsRead for MockBackend {
188 fn read(&self, _: &Path) -> Result<Vec<u8>, crate::FsError> {
189 Ok(vec![])
190 }
191 fn read_to_string(&self, _: &Path) -> Result<String, crate::FsError> {
192 Ok(String::new())
193 }
194 fn read_range(&self, _: &Path, _: u64, _: usize) -> Result<Vec<u8>, crate::FsError> {
195 Ok(vec![])
196 }
197 fn exists(&self, _: &Path) -> Result<bool, crate::FsError> {
198 Ok(true)
199 }
200 fn metadata(&self, _: &Path) -> Result<crate::Metadata, crate::FsError> {
201 Ok(crate::Metadata::default())
202 }
203 fn open_read(&self, _: &Path) -> Result<Box<dyn std::io::Read + Send>, crate::FsError> {
204 Ok(Box::new(std::io::empty()))
205 }
206 }
207
208 impl FsWrite for MockBackend {
209 fn write(&self, _: &Path, _: &[u8]) -> Result<(), crate::FsError> {
210 Ok(())
211 }
212 fn append(&self, _: &Path, _: &[u8]) -> Result<(), crate::FsError> {
213 Ok(())
214 }
215 fn truncate(&self, _: &Path, _: u64) -> Result<(), crate::FsError> {
216 Ok(())
217 }
218 fn remove_file(&self, _: &Path) -> Result<(), crate::FsError> {
219 Ok(())
220 }
221 fn rename(&self, _: &Path, _: &Path) -> Result<(), crate::FsError> {
222 Ok(())
223 }
224 fn copy(&self, _: &Path, _: &Path) -> Result<(), crate::FsError> {
225 Ok(())
226 }
227 fn open_write(
228 &self,
229 _: &Path,
230 ) -> Result<Box<dyn std::io::Write + Send>, crate::FsError> {
231 Ok(Box::new(std::io::sink()))
232 }
233 }
234
235 impl FsDir for MockBackend {
236 fn read_dir(&self, _: &Path) -> Result<ReadDirIter, crate::FsError> {
237 Ok(ReadDirIter::from_vec(vec![]))
238 }
239 fn create_dir(&self, _: &Path) -> Result<(), crate::FsError> {
240 Ok(())
241 }
242 fn create_dir_all(&self, _: &Path) -> Result<(), crate::FsError> {
243 Ok(())
244 }
245 fn remove_dir(&self, _: &Path) -> Result<(), crate::FsError> {
246 Ok(())
247 }
248 fn remove_dir_all(&self, _: &Path) -> Result<(), crate::FsError> {
249 Ok(())
250 }
251 }
252
253 // Mock wrapper
254 struct WrappedBackend<B> {
255 _inner: B,
256 }
257
258 impl<B: FsRead> FsRead for WrappedBackend<B> {
259 fn read(&self, _: &Path) -> Result<Vec<u8>, crate::FsError> {
260 Ok(vec![])
261 }
262 fn read_to_string(&self, _: &Path) -> Result<String, crate::FsError> {
263 Ok(String::new())
264 }
265 fn read_range(&self, _: &Path, _: u64, _: usize) -> Result<Vec<u8>, crate::FsError> {
266 Ok(vec![])
267 }
268 fn exists(&self, _: &Path) -> Result<bool, crate::FsError> {
269 Ok(true)
270 }
271 fn metadata(&self, _: &Path) -> Result<crate::Metadata, crate::FsError> {
272 Ok(crate::Metadata::default())
273 }
274 fn open_read(&self, _: &Path) -> Result<Box<dyn std::io::Read + Send>, crate::FsError> {
275 Ok(Box::new(std::io::empty()))
276 }
277 }
278
279 impl<B: FsWrite> FsWrite for WrappedBackend<B> {
280 fn write(&self, _: &Path, _: &[u8]) -> Result<(), crate::FsError> {
281 Ok(())
282 }
283 fn append(&self, _: &Path, _: &[u8]) -> Result<(), crate::FsError> {
284 Ok(())
285 }
286 fn truncate(&self, _: &Path, _: u64) -> Result<(), crate::FsError> {
287 Ok(())
288 }
289 fn remove_file(&self, _: &Path) -> Result<(), crate::FsError> {
290 Ok(())
291 }
292 fn rename(&self, _: &Path, _: &Path) -> Result<(), crate::FsError> {
293 Ok(())
294 }
295 fn copy(&self, _: &Path, _: &Path) -> Result<(), crate::FsError> {
296 Ok(())
297 }
298 fn open_write(
299 &self,
300 _: &Path,
301 ) -> Result<Box<dyn std::io::Write + Send>, crate::FsError> {
302 Ok(Box::new(std::io::sink()))
303 }
304 }
305
306 impl<B: FsDir> FsDir for WrappedBackend<B> {
307 fn read_dir(&self, _: &Path) -> Result<ReadDirIter, crate::FsError> {
308 Ok(ReadDirIter::from_vec(vec![]))
309 }
310 fn create_dir(&self, _: &Path) -> Result<(), crate::FsError> {
311 Ok(())
312 }
313 fn create_dir_all(&self, _: &Path) -> Result<(), crate::FsError> {
314 Ok(())
315 }
316 fn remove_dir(&self, _: &Path) -> Result<(), crate::FsError> {
317 Ok(())
318 }
319 fn remove_dir_all(&self, _: &Path) -> Result<(), crate::FsError> {
320 Ok(())
321 }
322 }
323
324 // Mock layer
325 struct MockLayer;
326
327 impl<B: Fs> Layer<B> for MockLayer {
328 type Backend = WrappedBackend<B>;
329
330 fn layer(self, backend: B) -> Self::Backend {
331 WrappedBackend { _inner: backend }
332 }
333 }
334
335 // Test composition
336 let backend = MockBackend;
337 let wrapped = backend.layer(MockLayer);
338
339 // Verify wrapped backend implements Fs
340 fn _takes_fs<T: Fs>(_: &T) {}
341 _takes_fs(&wrapped);
342 }
343}