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, LinkContent, LinkType,
6};
7use turbo_tasks_hash::{HashAlgorithm, Xxh3Hash64Hasher};
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    // for the relative link, the target is raw value read from the link
56    // for the absolute link, the target is stripped of the root path while reading
57    // See [LinkContent::Link] for more details.
58    Redirect { target: RcStr, link_type: LinkType },
59}
60
61#[turbo_tasks::value_impl]
62impl AssetContent {
63    #[turbo_tasks::function]
64    pub fn file(file: ResolvedVc<FileContent>) -> Result<Vc<Self>> {
65        Ok(AssetContent::File(file).cell())
66    }
67
68    #[turbo_tasks::function]
69    pub fn parse_json(&self) -> Vc<FileJsonContent> {
70        match self {
71            AssetContent::File(content) => content.parse_json(),
72            AssetContent::Redirect { .. } => {
73                FileJsonContent::unparsable(rcstr!("a redirect can't be parsed as json")).cell()
74            }
75        }
76    }
77
78    #[turbo_tasks::function]
79    pub fn file_content(&self) -> Vc<FileContent> {
80        match self {
81            AssetContent::File(content) => **content,
82            AssetContent::Redirect { .. } => FileContent::NotFound.cell(),
83        }
84    }
85
86    #[turbo_tasks::function]
87    pub fn lines(&self) -> Vc<FileLinesContent> {
88        match self {
89            AssetContent::File(content) => content.lines(),
90            AssetContent::Redirect { .. } => FileLinesContent::Unparsable.cell(),
91        }
92    }
93
94    #[turbo_tasks::function]
95    pub fn len(&self) -> Vc<Option<u64>> {
96        match self {
97            AssetContent::File(content) => content.len(),
98            AssetContent::Redirect { .. } => Vc::cell(None),
99        }
100    }
101
102    #[turbo_tasks::function]
103    pub fn parse_json_with_comments(&self) -> Vc<FileJsonContent> {
104        match self {
105            AssetContent::File(content) => content.parse_json_with_comments(),
106            AssetContent::Redirect { .. } => {
107                FileJsonContent::unparsable(rcstr!("a redirect can't be parsed as json")).cell()
108            }
109        }
110    }
111
112    #[turbo_tasks::function]
113    pub async fn write(&self, path: FileSystemPath) -> Result<()> {
114        match self {
115            AssetContent::File(file) => {
116                path.write(**file).as_side_effect().await?;
117            }
118            AssetContent::Redirect { target, link_type } => {
119                path.write_symbolic_link_dir(
120                    LinkContent::Link {
121                        target: target.clone(),
122                        link_type: *link_type,
123                    }
124                    .cell(),
125                )
126                .as_side_effect()
127                .await?;
128            }
129        }
130        Ok(())
131    }
132
133    #[turbo_tasks::function]
134    pub async fn hash(&self) -> Result<Vc<u64>> {
135        match self {
136            AssetContent::File(content) => Ok(content.hash()),
137            AssetContent::Redirect { target, link_type } => {
138                use turbo_tasks_hash::DeterministicHash;
139                let mut hasher = Xxh3Hash64Hasher::new();
140                target.deterministic_hash(&mut hasher);
141                link_type.deterministic_hash(&mut hasher);
142                Ok(Vc::cell(hasher.finish()))
143            }
144        }
145    }
146
147    /// Compared to [AssetContent::hash], this hashes only the bytes of the file content and
148    /// nothing else, returning `None` for redirects or missing files.
149    ///
150    /// If `salt` is non-empty it is written into the hasher before the file bytes in a single
151    /// pass. An empty salt produces the same result as hashing without a prefix.
152    #[turbo_tasks::function]
153    pub async fn content_hash(
154        &self,
155        salt: Vc<RcStr>,
156        algorithm: HashAlgorithm,
157    ) -> Result<Vc<Option<RcStr>>> {
158        match self {
159            AssetContent::File(content) => Ok(content.content_hash(salt, algorithm)),
160            AssetContent::Redirect { .. } => Ok(Vc::cell(None)),
161        }
162    }
163}