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 let truncated = if s.len() <= 20 {
106 s.as_str()
107 } else {
108 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: String,
125 #[cacheable(with=AsOption<AsPreset>)]
127 resource_path: Option<Utf8PathBuf>,
128 resource_query: Option<String>,
130 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#[cacheable]
308#[derive(Debug, Clone)]
309pub struct DescriptionData {
310 #[cacheable(with=As<PortablePath>)]
312 path: PathBuf,
313
314 #[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>>;