Skip to main content

turbopack_core/
asset.rs

1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, Vc};
4use turbo_tasks_fs::{
5    FileContent, FileJsonContent, FileLinesContent, FileSystemPath, WriteLinkContent,
6};
7use turbo_tasks_hash::{HashAlgorithm, deterministic_hash};
8
9use crate::version::{VersionedAssetContent, VersionedContent};
10
11/// Returns an empty salt `Vc<RcStr>` meaning "no salt applied to this hash".
12///
13/// Use this instead of `Vc::cell(RcStr::default())` at call sites that don't control the
14/// hash salt — e.g. internal hashes not exposed to the user as filenames.
15#[turbo_tasks::function]
16pub fn no_hash_salt() -> Vc<RcStr> {
17    Vc::cell(RcStr::default())
18}
19
20/// A file or intermediate result containing content as a [`Rope`] or a symlink.
21///
22/// This is a supertrait for [`Source`], [`OutputAsset`], and [`OutputChunk`].
23///
24/// [`Rope`]: turbo_tasks_fs::rope::Rope
25/// [`Source`]: crate::source::Source
26/// [`OutputAsset`]: crate::output::OutputAsset
27/// [`OutputChunk`]: crate::chunk::OutputChunk
28#[turbo_tasks::value_trait]
29pub trait Asset {
30    #[turbo_tasks::function]
31    fn content(self: Vc<Self>) -> Vc<AssetContent>;
32
33    /// The content of the `Asset` alongside its version.
34    #[turbo_tasks::function]
35    fn versioned_content(self: Vc<Self>) -> Result<Vc<Box<dyn VersionedContent>>> {
36        Ok(Vc::upcast(VersionedAssetContent::new(self.content())))
37    }
38
39    /// Hash of the content of the `Asset`. If `salt` is non-empty it is mixed
40    /// into the hash in a single pass before the file bytes.
41    #[turbo_tasks::function]
42    fn content_hash(
43        self: Vc<Self>,
44        salt: Vc<RcStr>,
45        algorithm: HashAlgorithm,
46    ) -> Vc<Option<RcStr>> {
47        self.content().content_hash(salt, algorithm)
48    }
49}
50
51#[turbo_tasks::value(shared)]
52#[derive(Clone)]
53pub enum AssetContent {
54    File(ResolvedVc<FileContent>),
55    /// A symbolic link. See [`WriteLinkContent`] for how it is written.
56    Redirect(WriteLinkContent),
57}
58
59#[turbo_tasks::value_impl]
60impl AssetContent {
61    #[turbo_tasks::function]
62    pub fn file(file: ResolvedVc<FileContent>) -> Result<Vc<Self>> {
63        Ok(AssetContent::File(file).cell())
64    }
65
66    #[turbo_tasks::function]
67    pub fn parse_json(&self) -> Vc<FileJsonContent> {
68        match self {
69            AssetContent::File(content) => content.parse_json(),
70            AssetContent::Redirect(..) => {
71                FileJsonContent::unparsable(rcstr!("a redirect can't be parsed as json")).cell()
72            }
73        }
74    }
75
76    #[turbo_tasks::function]
77    pub fn file_content(&self) -> Vc<FileContent> {
78        match self {
79            AssetContent::File(content) => **content,
80            AssetContent::Redirect(..) => FileContent::NotFound.cell(),
81        }
82    }
83
84    #[turbo_tasks::function]
85    pub fn lines(&self) -> Vc<FileLinesContent> {
86        match self {
87            AssetContent::File(content) => content.lines(),
88            AssetContent::Redirect(..) => FileLinesContent::Unparsable.cell(),
89        }
90    }
91
92    #[turbo_tasks::function]
93    pub fn len(&self) -> Vc<Option<u64>> {
94        match self {
95            AssetContent::File(content) => content.len(),
96            AssetContent::Redirect(..) => Vc::cell(None),
97        }
98    }
99
100    #[turbo_tasks::function]
101    pub fn parse_json_with_comments(&self) -> Vc<FileJsonContent> {
102        match self {
103            AssetContent::File(content) => content.parse_json_with_comments(),
104            AssetContent::Redirect(..) => {
105                FileJsonContent::unparsable(rcstr!("a redirect can't be parsed as json")).cell()
106            }
107        }
108    }
109
110    #[turbo_tasks::function]
111    pub async fn write(&self, path: FileSystemPath) -> Result<()> {
112        match self {
113            AssetContent::File(file) => {
114                path.write(**file).as_side_effect().await?;
115            }
116            AssetContent::Redirect(content) => {
117                path.write_link(content.clone().cell())
118                    .as_side_effect()
119                    .await?;
120            }
121        }
122        Ok(())
123    }
124
125    #[turbo_tasks::function]
126    pub async fn hash(&self, salt: Vc<RcStr>, algorithm: HashAlgorithm) -> Result<Vc<RcStr>> {
127        Ok(match self {
128            AssetContent::File(content) => content.hash(salt, algorithm),
129            AssetContent::Redirect(content) => Vc::cell(RcStr::from(
130                // no_hash_salt
131                deterministic_hash(&salt.await?, content, algorithm),
132            )),
133        })
134    }
135
136    /// Compared to [AssetContent::hash], this hashes only the bytes of the file content and
137    /// nothing else, returning `None` for redirects or missing files.
138    ///
139    /// If `salt` is non-empty it is written into the hasher before the file bytes in a single
140    /// pass. An empty salt produces the same result as hashing without a prefix.
141    #[turbo_tasks::function]
142    pub async fn content_hash(
143        &self,
144        salt: Vc<RcStr>,
145        algorithm: HashAlgorithm,
146    ) -> Result<Vc<Option<RcStr>>> {
147        match self {
148            AssetContent::File(content) => Ok(content.content_hash(salt, algorithm)),
149            AssetContent::Redirect(..) => Ok(Vc::cell(None)),
150        }
151    }
152}