Skip to main content

vortex_io/filesystem/
prefix.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use futures::StreamExt;
8use futures::stream::BoxStream;
9use vortex_error::VortexResult;
10
11use crate::VortexReadAt;
12use crate::filesystem::FileListing;
13use crate::filesystem::FileSystem;
14use crate::filesystem::FileSystemRef;
15
16/// A [`FileSystem`] decorator that roots all operations under a given prefix.
17///
18/// Paths returned from [`list`](FileSystem::list) are relative to the prefix, and paths
19/// passed to [`open_read`](FileSystem::open_read) are automatically prefixed.
20#[derive(Debug)]
21pub struct PrefixFileSystem {
22    inner: FileSystemRef,
23    prefix: String,
24}
25
26impl PrefixFileSystem {
27    pub fn new(inner: FileSystemRef, prefix: String) -> Self {
28        // Normalize to always have a trailing slash for clean concatenation.
29        let prefix = format!("{}/", prefix.trim_matches('/'));
30        Self { inner, prefix }
31    }
32}
33
34#[async_trait]
35impl FileSystem for PrefixFileSystem {
36    fn list(&self, prefix: &str) -> BoxStream<'_, VortexResult<FileListing>> {
37        let full_prefix = format!("{}{}", self.prefix, prefix.trim_start_matches('/'));
38
39        let strip_prefix = self.prefix.clone();
40        self.inner
41            .list(&full_prefix)
42            .map(move |result| {
43                result.map(|mut listing| {
44                    listing.path = listing
45                        .path
46                        .strip_prefix(&strip_prefix)
47                        .unwrap_or(&listing.path)
48                        .to_string();
49                    listing
50                })
51            })
52            .boxed()
53    }
54
55    async fn open_read(&self, path: &str) -> VortexResult<Arc<dyn VortexReadAt>> {
56        self.inner
57            .open_read(&format!("{}{}", self.prefix, path.trim_start_matches('/')))
58            .await
59    }
60}
61
62impl dyn FileSystem + 'static {
63    /// Create a new filesystem that applies the given prefix to all operations on this filesystem.
64    pub fn with_prefix(self: Arc<Self>, prefix: String) -> FileSystemRef {
65        Arc::new(PrefixFileSystem::new(self, prefix))
66    }
67}