Skip to main content

rspack_resolver/
error.rs

1use std::{io, path::PathBuf, sync::Arc};
2
3use thiserror::Error;
4
5/// All resolution errors
6///
7/// `thiserror` is used to display meaningful error messages.
8#[derive(Debug, Clone, PartialEq, Error)]
9#[non_exhaustive]
10pub enum ResolveError {
11  /// Ignored path
12  ///
13  /// Derived from ignored path (false value) from browser field in package.json
14  /// ```json
15  /// {
16  ///     "browser": {
17  ///         "./module": false
18  ///     }
19  /// }
20  /// ```
21  /// See <https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module>
22  #[error("Path is ignored {0}")]
23  Ignored(PathBuf),
24
25  /// Module not found
26  #[error("Cannot find module '{0}'")]
27  NotFound(/* specifier */ String),
28
29  /// Matched alias value  not found
30  #[error("Cannot find module '{0}' for matched aliased key '{1}'")]
31  MatchedAliasNotFound(/* specifier */ String, /* alias key */ String),
32
33  /// Tsconfig not found
34  #[error("Tsconfig not found {0}")]
35  TsconfigNotFound(PathBuf),
36
37  /// Tsconfig's project reference path points to it self
38  #[error("Tsconfig's project reference path points to this tsconfig {0}")]
39  TsconfigSelfReference(PathBuf),
40
41  #[error("{0}")]
42  IOError(IOError),
43
44  /// Node.js builtin modules
45  ///
46  /// This is an error due to not being a Node.js runtime.
47  /// The `alias` option can be used to resolve a builtin module to a polyfill.
48  #[error("Builtin module {0}")]
49  Builtin(String),
50
51  /// All of the aliased extension are not found
52  ///
53  /// Displays `Cannot resolve 'index.mjs' with extension aliases 'index.mts' in ...`
54  #[error("Cannot resolve '{0}' for extension aliases '{1}' in '{2}'")]
55  ExtensionAlias(
56    /* File name */ String,
57    /* Tried file names */ String,
58    /* Path to dir */ PathBuf,
59  ),
60
61  /// The provided path specifier cannot be parsed
62  #[error("{0}")]
63  Specifier(SpecifierError),
64
65  /// JSON parse error
66  #[error("{0:?}")]
67  JSON(JSONError),
68
69  #[error(
70    r#"Invalid module "{0}" specifier is not a valid subpath for the "exports" resolution of {1}"#
71  )]
72  InvalidModuleSpecifier(String, PathBuf),
73
74  #[error(r#"Invalid "exports" target "{0}" defined for '{1}' in the package config {2}"#)]
75  InvalidPackageTarget(String, String, PathBuf),
76
77  #[error(r#"Package subpath '{0}' is not defined by "exports" in {1}"#)]
78  PackagePathNotExported(String, PathBuf),
79
80  #[error(r#"Invalid package config "{0}", "exports" cannot contain some keys starting with '.' and some not. The exports object must either be an object of package subpath keys or an object of main entry condition name keys only."#)]
81  InvalidPackageConfig(PathBuf),
82
83  #[error(r#"Default condition should be last one in "{0}""#)]
84  InvalidPackageConfigDefault(PathBuf),
85
86  #[error(r#"Expecting folder to folder mapping. "{0}" should end with "/"#)]
87  InvalidPackageConfigDirectory(PathBuf),
88
89  #[error(r#"Package import specifier "{0}" is not defined in package {1}"#)]
90  PackageImportNotDefined(String, PathBuf),
91
92  #[error("{0} is unimplemented")]
93  Unimplemented(&'static str),
94
95  /// Occurs when alias paths reference each other.
96  #[error("Recursion in resolving")]
97  Recursion,
98}
99
100impl ResolveError {
101  pub fn is_ignore(&self) -> bool {
102    matches!(self, Self::Ignored(_))
103  }
104
105  pub(crate) fn from_serde_json_error(
106    path: PathBuf,
107    error: &serde_json::Error,
108    content: Option<String>,
109  ) -> Self {
110    Self::JSON(JSONError {
111      path,
112      message: error.to_string(),
113      line: error.line(),
114      column: error.column(),
115      content,
116    })
117  }
118}
119
120/// Error for [ResolveError::Specifier]
121#[derive(Debug, Clone, Eq, PartialEq, Error)]
122pub enum SpecifierError {
123  #[error("The specifiers must be a non-empty string. Received \"{0}\"")]
124  Empty(String),
125}
126
127/// JSON error from [serde_json::Error]
128#[derive(Debug, Clone, Eq, PartialEq)]
129pub struct JSONError {
130  pub path: PathBuf,
131  pub message: String,
132  pub line: usize,
133  pub column: usize,
134  pub content: Option<String>,
135}
136
137#[derive(Debug, Clone, Error)]
138#[error("{0}")]
139pub struct IOError(Arc<io::Error>);
140
141impl PartialEq for IOError {
142  fn eq(&self, other: &Self) -> bool {
143    self.0.kind() == other.0.kind()
144  }
145}
146
147impl From<IOError> for io::Error {
148  fn from(error: IOError) -> Self {
149    let io_error = error.0.as_ref();
150    Self::new(io_error.kind(), io_error.to_string())
151  }
152}
153
154impl From<io::Error> for ResolveError {
155  fn from(err: io::Error) -> Self {
156    Self::IOError(IOError(Arc::new(err)))
157  }
158}
159
160#[tokio::test]
161async fn test_into_io_error() {
162  use std::io::{self, ErrorKind};
163  let error_string = "IOError occurred";
164  let string_error = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
165  let string_error2 = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
166  let resolve_io_error: ResolveError = ResolveError::from(string_error2);
167
168  assert_eq!(resolve_io_error, ResolveError::from(string_error));
169  assert_eq!(resolve_io_error.clone(), resolve_io_error);
170  let ResolveError::IOError(io_error) = resolve_io_error else {
171    unreachable!()
172  };
173  assert_eq!(
174    format!("{io_error:?}"),
175    r#"IOError(Custom { kind: Interrupted, error: "IOError occurred" })"#
176  );
177  // fix for https://github.com/web-infra-dev/rspack/issues/4564
178  let std_io_error: io::Error = io_error.into();
179  assert_eq!(std_io_error.kind(), ErrorKind::Interrupted);
180  assert_eq!(std_io_error.to_string(), error_string);
181  assert_eq!(
182    format!("{std_io_error:?}"),
183    r#"Custom { kind: Interrupted, error: "IOError occurred" }"#
184  );
185}
186
187#[tokio::test]
188async fn test_coverage() {
189  let error = ResolveError::NotFound("x".into());
190  assert_eq!(format!("{error:?}"), r#"NotFound("x")"#);
191  assert_eq!(error.clone(), error);
192
193  let error = ResolveError::Specifier(SpecifierError::Empty("x".into()));
194  assert_eq!(format!("{error:?}"), r#"Specifier(Empty("x"))"#);
195  assert_eq!(error.clone(), error);
196}