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: Vc<FileSystemPath>,
27        child_fs: ResolvedVc<Box<dyn FileSystem>>,
28    ) -> Result<Vc<Self>> {
29        let child_path = child_path.await?;
30
31        Ok(AttachedFileSystem {
32            root_fs: child_path.fs,
33            child_path: child_path.path.clone(),
34            child_fs,
35        }
36        .cell())
37    }
38
39    /// Converts the given [Vc<FileSystemPath>] to a path in this [FileSystem].
40    ///
41    /// The given path has to be inside of the root [FileSystem], the child
42    /// [FileSystem] or this [AttachedFileSystem].
43    #[turbo_tasks::function]
44    pub async fn convert_path(
45        self: ResolvedVc<Self>,
46        contained_path_vc: Vc<FileSystemPath>,
47    ) -> Result<Vc<FileSystemPath>> {
48        let contained_path = contained_path_vc.await?;
49        let self_fs: ResolvedVc<Box<dyn FileSystem>> = ResolvedVc::upcast(self);
50        let this = self.await?;
51
52        match contained_path.fs {
53            // already on this filesystem
54            fs if fs == self_fs => Ok(contained_path_vc),
55            // in the root filesystem, just need to rebase on this filesystem
56            fs if fs == this.root_fs => Ok(self
57                .root()
58                .resolve()
59                .await?
60                .join(contained_path.path.clone())),
61            // in the child filesystem, so we expand to the full path by appending to child_path
62            fs if fs == this.child_fs => Ok(self
63                .child_path()
64                .resolve()
65                .await?
66                .join(contained_path.path.clone())),
67            _ => bail!(
68                "path {} not part of self, the root fs or the child fs",
69                contained_path_vc.to_string().await?
70            ),
71        }
72    }
73
74    /// Constructs a [Vc<FileSystemPath>] of the attachment point referencing
75    /// this [AttachedFileSystem]
76    #[turbo_tasks::function]
77    async fn child_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
78        Ok(self
79            .root()
80            .resolve()
81            .await?
82            .join(self.await?.child_path.clone()))
83    }
84
85    /// Resolves the local path of the root or child filesystem from a path
86    /// on the [AttachedFileSystem]
87    #[turbo_tasks::function]
88    pub async fn get_inner_fs_path(
89        self: ResolvedVc<Self>,
90        path: Vc<FileSystemPath>,
91    ) -> Result<Vc<FileSystemPath>> {
92        let this = self.await?;
93        let path = path.await?;
94        let self_fs: ResolvedVc<Box<dyn FileSystem>> = ResolvedVc::upcast(self);
95
96        if path.fs != self_fs {
97            let self_fs_str = self_fs.to_string().await?;
98            let path_fs_str = path.fs.to_string().await?;
99            bail!(
100                "path fs does not match (expected {}, got {})",
101                self_fs_str,
102                path_fs_str
103            )
104        }
105
106        let child_path = self.child_path().await?;
107        Ok(if let Some(inner_path) = child_path.get_path_to(&path) {
108            this.child_fs
109                .root()
110                .resolve()
111                .await?
112                .join(inner_path.into())
113        } else {
114            this.root_fs.root().resolve().await?.join(path.path.clone())
115        })
116    }
117}
118
119#[turbo_tasks::value_impl]
120impl FileSystem for AttachedFileSystem {
121    #[turbo_tasks::function(fs)]
122    fn read(self: Vc<Self>, path: Vc<FileSystemPath>) -> Vc<FileContent> {
123        self.get_inner_fs_path(path).read()
124    }
125
126    #[turbo_tasks::function(fs)]
127    fn read_link(self: Vc<Self>, path: Vc<FileSystemPath>) -> Vc<LinkContent> {
128        self.get_inner_fs_path(path).read_link()
129    }
130
131    #[turbo_tasks::function(fs)]
132    fn raw_read_dir(self: Vc<Self>, path: Vc<FileSystemPath>) -> Vc<RawDirectoryContent> {
133        self.get_inner_fs_path(path).raw_read_dir()
134    }
135
136    #[turbo_tasks::function(fs)]
137    fn write(self: Vc<Self>, path: Vc<FileSystemPath>, content: Vc<FileContent>) -> Vc<()> {
138        self.get_inner_fs_path(path).write(content)
139    }
140
141    #[turbo_tasks::function(fs)]
142    fn write_link(self: Vc<Self>, path: Vc<FileSystemPath>, target: Vc<LinkContent>) -> Vc<()> {
143        self.get_inner_fs_path(path).write_link(target)
144    }
145
146    #[turbo_tasks::function]
147    fn metadata(self: Vc<Self>, path: Vc<FileSystemPath>) -> Vc<FileMeta> {
148        self.get_inner_fs_path(path).metadata()
149    }
150}
151
152#[turbo_tasks::value_impl]
153impl ValueToString for AttachedFileSystem {
154    #[turbo_tasks::function]
155    async fn to_string(&self) -> Result<Vc<RcStr>> {
156        let root_fs_str = self.root_fs.to_string().await?;
157        let child_fs_str = self.child_fs.to_string().await?;
158        Ok(Vc::cell(
159            format!("{root_fs_str}-with-{child_fs_str}").into(),
160        ))
161    }
162}