Skip to main content

rspack_loader_runner/
content.rs

1use std::{
2  fmt::Debug,
3  path::{Path, PathBuf},
4  sync::Arc,
5};
6
7use anymap::CloneAny;
8use once_cell::sync::OnceCell;
9use rspack_cacheable::{
10  cacheable,
11  utils::PortablePath,
12  with::{As, AsInner, AsOption, AsPreset},
13};
14use rspack_error::{Error, Result, ToStringResultToRspackResultExt};
15use rspack_paths::{Utf8Path, Utf8PathBuf};
16use rustc_hash::FxHashMap;
17
18use crate::{Scheme, get_scheme, parse_resource};
19
20#[derive(Clone, PartialEq, Eq)]
21pub enum Content {
22  String(String),
23  Buffer(Vec<u8>),
24}
25
26impl Content {
27  pub fn try_into_string(self) -> Result<String> {
28    match self {
29      Content::String(s) => Ok(s),
30      Content::Buffer(b) => String::from_utf8(b).to_rspack_result(),
31    }
32  }
33
34  pub fn into_string_lossy(self) -> String {
35    match self {
36      Content::String(s) => s,
37      Content::Buffer(b) => String::from_utf8_lossy(&b).into_owned(),
38    }
39  }
40
41  pub fn as_bytes(&self) -> &[u8] {
42    match self {
43      Content::String(s) => s.as_bytes(),
44      Content::Buffer(b) => b,
45    }
46  }
47
48  pub fn into_bytes(self) -> Vec<u8> {
49    match self {
50      Content::String(s) => s.into_bytes(),
51      Content::Buffer(b) => b,
52    }
53  }
54
55  pub fn is_buffer(&self) -> bool {
56    matches!(self, Content::Buffer(..))
57  }
58
59  pub fn is_string(&self) -> bool {
60    matches!(self, Content::String(..))
61  }
62}
63
64impl TryFrom<Content> for String {
65  type Error = Error;
66
67  fn try_from(content: Content) -> Result<Self> {
68    content.try_into_string()
69  }
70}
71
72impl From<Content> for Vec<u8> {
73  fn from(content: Content) -> Self {
74    content.into_bytes()
75  }
76}
77
78impl From<String> for Content {
79  fn from(s: String) -> Self {
80    Self::String(s)
81  }
82}
83
84impl From<Vec<u8>> for Content {
85  fn from(buf: Vec<u8>) -> Self {
86    Self::Buffer(buf)
87  }
88}
89
90impl Debug for Content {
91  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92    let mut content = f.debug_struct("Content");
93
94    let s = match self {
95      Self::String(s) => s.to_string(),
96      Self::Buffer(b) => String::from_utf8_lossy(b).to_string(),
97    };
98
99    let ty = match self {
100      Self::String(_) => "String",
101      Self::Buffer(_) => "Buffer",
102    };
103
104    // Safe string truncation to approximately 20 characters
105    let truncated = if s.len() <= 20 {
106      s.as_str()
107    } else {
108      // Find the last character boundary at or before position 20
109      let mut end = 20;
110      while end > 0 && !s.is_char_boundary(end) {
111        end -= 1;
112      }
113      &s[0..end]
114    };
115
116    content.field(ty, &truncated.to_owned()).finish()
117  }
118}
119
120#[cacheable]
121#[derive(Debug, Clone)]
122pub struct ResourceData {
123  /// Resource with absolute path, query and fragment
124  resource: String,
125  /// Absolute resource path only
126  #[cacheable(with=AsOption<AsPreset>)]
127  resource_path: Option<Utf8PathBuf>,
128  /// Resource query with `?` prefix
129  resource_query: Option<String>,
130  /// Resource fragment with `#` prefix
131  resource_fragment: Option<String>,
132  resource_description: Option<DescriptionData>,
133  mimetype: Option<String>,
134  parameters: Option<String>,
135  encoding: Option<String>,
136  encoded_content: Option<String>,
137  context: Option<String>,
138  #[cacheable(with=AsInner)]
139  scheme: OnceCell<Scheme>,
140}
141
142impl ResourceData {
143  pub fn new_with_resource(resource: String) -> Self {
144    if let Some(parsed) = parse_resource(&resource) {
145      return Self::new_with_path(resource, parsed.path, parsed.query, parsed.fragment);
146    };
147    Self {
148      resource,
149      resource_path: None,
150      resource_query: None,
151      resource_fragment: None,
152      resource_description: None,
153      mimetype: None,
154      parameters: None,
155      encoding: None,
156      encoded_content: None,
157      scheme: OnceCell::new(),
158      context: None,
159    }
160  }
161
162  pub fn new_with_path(
163    resource: String,
164    path: Utf8PathBuf,
165    query: Option<String>,
166    fragment: Option<String>,
167  ) -> Self {
168    Self {
169      resource,
170      resource_path: Some(path),
171      resource_query: query,
172      resource_fragment: fragment,
173      resource_description: None,
174      mimetype: None,
175      parameters: None,
176      encoding: None,
177      encoded_content: None,
178      scheme: OnceCell::new(),
179      context: None,
180    }
181  }
182
183  pub fn set_context(&mut self, context: Option<String>) {
184    self.context = context;
185  }
186
187  pub fn context(&self) -> Option<&str> {
188    self.context.as_deref()
189  }
190
191  pub fn get_scheme(&self) -> &Scheme {
192    self.scheme.get_or_init(|| {
193      if let Some(path) = self.path() {
194        get_scheme(path.as_str())
195      } else {
196        Scheme::None
197      }
198    })
199  }
200
201  pub fn resource(&self) -> &str {
202    &self.resource
203  }
204
205  pub fn set_resource(&mut self, v: String) {
206    self.resource = v;
207  }
208
209  pub fn path(&self) -> Option<&Utf8Path> {
210    self.resource_path.as_deref()
211  }
212
213  pub fn set_path<P: Into<Utf8PathBuf>>(&mut self, v: P) {
214    let new_path = v.into();
215    if let Some(path) = self.path()
216      && path != new_path
217    {
218      self.scheme.take();
219      self.resource_path = Some(new_path);
220    }
221  }
222
223  pub fn set_path_optional<P: Into<Utf8PathBuf>>(&mut self, v: Option<P>) {
224    if let Some(v) = v {
225      self.set_path(v);
226    }
227  }
228
229  pub fn query(&self) -> Option<&str> {
230    self.resource_query.as_deref()
231  }
232
233  pub fn set_query(&mut self, v: String) {
234    self.resource_query = Some(v);
235  }
236
237  pub fn set_query_optional(&mut self, v: Option<String>) {
238    self.resource_query = v;
239  }
240
241  pub fn fragment(&self) -> Option<&str> {
242    self.resource_fragment.as_deref()
243  }
244
245  pub fn set_fragment(&mut self, v: String) {
246    self.resource_fragment = Some(v);
247  }
248
249  pub fn set_fragment_optional(&mut self, v: Option<String>) {
250    self.resource_fragment = v;
251  }
252
253  pub fn description(&self) -> Option<&DescriptionData> {
254    self.resource_description.as_ref()
255  }
256
257  pub fn set_description_optional(&mut self, v: Option<DescriptionData>) {
258    self.resource_description = v;
259  }
260
261  pub fn mimetype(&self) -> Option<&str> {
262    self.mimetype.as_deref()
263  }
264
265  pub fn set_mimetype(&mut self, v: String) {
266    self.mimetype = Some(v);
267  }
268
269  pub fn parameters(&self) -> Option<&str> {
270    self.parameters.as_deref()
271  }
272
273  pub fn set_parameters(&mut self, v: String) {
274    self.parameters = Some(v);
275  }
276
277  pub fn encoding(&self) -> Option<&str> {
278    self.encoding.as_deref()
279  }
280
281  pub fn set_encoding(&mut self, v: String) {
282    self.encoding = Some(v);
283  }
284
285  pub fn encoded_content(&self) -> Option<&str> {
286    self.encoded_content.as_deref()
287  }
288
289  pub fn set_encoded_content(&mut self, v: String) {
290    self.encoded_content = Some(v);
291  }
292
293  pub fn update_resource_data(&mut self, new_resource: String) {
294    if self.resource_path.is_some()
295      && let Some(parsed) = parse_resource(&new_resource)
296    {
297      self.set_path(parsed.path);
298      self.set_query_optional(parsed.query);
299      self.set_fragment_optional(parsed.fragment);
300    }
301    self.set_resource(new_resource);
302  }
303}
304
305/// Used for [Rule.descriptionData](https://rspack.rs/config/module.html#ruledescriptiondata) and
306/// package.json.sideEffects in tree shaking.
307#[cacheable]
308#[derive(Debug, Clone)]
309pub struct DescriptionData {
310  /// Path to package.json
311  #[cacheable(with=As<PortablePath>)]
312  path: PathBuf,
313
314  /// Raw package.json
315  #[cacheable(with=AsInner<AsPreset>)]
316  json: Arc<serde_json::Value>,
317}
318
319impl DescriptionData {
320  pub fn new(path: PathBuf, json: Arc<serde_json::Value>) -> Self {
321    Self { path, json }
322  }
323
324  pub fn path(&self) -> &Path {
325    &self.path
326  }
327
328  pub fn json(&self) -> &serde_json::Value {
329    self.json.as_ref()
330  }
331
332  pub fn into_parts(self) -> (PathBuf, Arc<serde_json::Value>) {
333    (self.path, self.json)
334  }
335}
336
337pub type AdditionalData = anymap::Map<dyn CloneAny + Send + Sync>;
338pub type ParseMeta = FxHashMap<String, Box<dyn CloneAny + Send + Sync>>;