turbo_tasks_fs/
attach.rs

1use anyhow::{Result, bail};
2use turbo_rcstr::RcStr;
3use turbo_tasks::{ResolvedVc, ValueToString, Vc};
4
5use crate::{FileContent, FileMeta, FileSystem, FileSystemPath, LinkContent, RawDirectoryContent};
6
7/// A wrapper [FileSystem] which attaches a child [FileSystem] as a
8/// "subdirectory" in the given root [FileSystem].
9///
10/// Caveat: The `child_path` itself is not visible as a directory entry.
11#[turbo_tasks::value]
12pub struct AttachedFileSystem {
13    root_fs: ResolvedVc<Box<dyn FileSystem>>,
14    // we turn this into a string because creating a FileSystemPath requires the filesystem which
15    // we are creating (circular reference)
16    child_path: RcStr,
17    child_fs: ResolvedVc<Box<dyn FileSystem>>,
18}
19
20#[turbo_tasks::value_impl]
21impl AttachedFileSystem {
22    /// Create a new [AttachedFileSystem] which will have the `child_fs` as
23    /// an invisible subdirectory of the `child_path`
24    #[turbo_tasks::function]
25    pub async fn new(
26        child_path: FileSystemPath,
27        child_fs: ResolvedVc<Box<dyn FileSystem>>,
28    ) -> Result<Vc<Self>> {
29        Ok(AttachedFileSystem {
30            root_fs: child_path.fs,
31            child_path: child_path.path.clone(),
32            child_fs,
33        }
34        .cell())
35    }
36
37    /// Converts the given [FileSystemPath] to a path in this [FileSystem].
38    ///
39    /// The given path has to be inside of the root [FileSystem], the child
40    /// [FileSystem] or this [AttachedFileSystem].
41    #[turbo_tasks::function]
42    pub async fn convert_path(
43        self: ResolvedVc<Self>,
44        contained_path: FileSystemPath,
45    ) -> Result<Vc<FileSystemPath>> {
46        let self_fs: ResolvedVc<Box<dyn FileSystem>> = ResolvedVc::upcast(self);
47        let this = self.await?;
48
49        match contained_path.fs {
50            // already on this filesystem
51            fs if fs == self_fs => Ok(contained_path.cell()),
52            // in the root filesystem, just need to rebase on this filesystem
53            fs if fs == this.root_fs => Ok(self.root().await?.join(&contained_path.path)?.cell()),
54            // in the child filesystem, so we expand to the full path by appending to child_path
55            fs if fs == this.child_fs => {
56                Ok(self.child_path().await?.join(&contained_path.path)?.cell())
57            }
58            _ => bail!(
59                "path {} not part of self, the root fs or the child fs",
60                contained_path.value_to_string().await?
61            ),
62        }
63    }
64
65    /// Constructs a [FileSystemPath] of the attachment point referencing
66    /// this [AttachedFileSystem]
67    #[turbo_tasks::function]
68    async fn child_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
69        Ok(self.root().await?.join(&self.await?.child_path)?.cell())
70    }
71
72    /// Resolves the local path of the root or child filesystem from a path
73    /// on the [AttachedFileSystem]
74    #[turbo_tasks::function]
75    pub async fn get_inner_fs_path(
76        self: ResolvedVc<Self>,
77        path: FileSystemPath,
78    ) -> Result<Vc<FileSystemPath>> {
79        let this = self.await?;
80        let self_fs: ResolvedVc<Box<dyn FileSystem>> = ResolvedVc::upcast(self);
81
82        if path.fs != self_fs {
83            let self_fs_str = self_fs.to_string().await?;
84            let path_fs_str = path.fs.to_string().await?;
85            bail!(
86                "path fs does not match (expected {}, got {})",
87                self_fs_str,
88                path_fs_str
89            )
90        }
91
92        let child_path = self.child_path().await?;
93        Ok(if let Some(inner_path) = child_path.get_path_to(&path) {
94            this.child_fs.root().await?.join(inner_path)?.cell()
95        } else {
96            this.root_fs.root().await?.join(&path.path)?.cell()
97        })
98    }
99}
100
101#[turbo_tasks::value_impl]
102impl FileSystem for AttachedFileSystem {
103    #[turbo_tasks::function(fs)]
104    async fn read(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<FileContent>> {
105        Ok(self.get_inner_fs_path(path).await?.read())
106    }
107
108    #[turbo_tasks::function(fs)]
109    async fn read_link(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<LinkContent>> {
110        Ok(self.get_inner_fs_path(path).await?.read_link())
111    }
112
113    #[turbo_tasks::function(fs)]
114    async fn raw_read_dir(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<RawDirectoryContent>> {
115        Ok(self.get_inner_fs_path(path).await?.raw_read_dir())
116    }
117
118    #[turbo_tasks::function(fs)]
119    async fn write(
120        self: Vc<Self>,
121        path: FileSystemPath,
122        content: Vc<FileContent>,
123    ) -> Result<Vc<()>> {
124        Ok(self.get_inner_fs_path(path).await?.write(content))
125    }
126
127    #[turbo_tasks::function(fs)]
128    async fn write_link(
129        self: Vc<Self>,
130        path: FileSystemPath,
131        target: Vc<LinkContent>,
132    ) -> Result<Vc<()>> {
133        Ok(self.get_inner_fs_path(path).await?.write_link(target))
134    }
135
136    #[turbo_tasks::function]
137    async fn metadata(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<FileMeta>> {
138        Ok(self.get_inner_fs_path(path).await?.metadata())
139    }
140}
141
142#[turbo_tasks::value_impl]
143impl ValueToString for AttachedFileSystem {
144    #[turbo_tasks::function]
145    async fn to_string(&self) -> Result<Vc<RcStr>> {
146        let root_fs_str = self.root_fs.to_string().await?;
147        let child_fs_str = self.child_fs.to_string().await?;
148        Ok(Vc::cell(
149            format!("{root_fs_str}-with-{child_fs_str}").into(),
150        ))
151    }
152}