unrspack_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 module when `Options::builtin_modules` is enabled.
45    ///
46    /// `is_runtime_module` can be used to determine whether the request
47    /// was prefixed with `node:` or not.
48    ///
49    /// `resolved` is always prefixed with "node:" in compliance with the ESM specification.
50    #[error("Builtin module {resolved}")]
51    Builtin { resolved: String, is_runtime_module: bool },
52
53    /// All of the aliased extension are not found
54    ///
55    /// Displays `Cannot resolve 'index.mjs' with extension aliases 'index.mts' in ...`
56    #[error("Cannot resolve '{0}' for extension aliases '{1}' in '{2}'")]
57    ExtensionAlias(
58        /* File name */ String,
59        /* Tried file names */ String,
60        /* Path to dir */ PathBuf,
61    ),
62
63    /// The provided path specifier cannot be parsed
64    #[error("{0}")]
65    Specifier(SpecifierError),
66
67    /// JSON parse error
68    #[error("{0:?}")]
69    JSON(JSONError),
70
71    /// Restricted by `ResolveOptions::restrictions`
72    #[error(r#"Path "{0}" restricted by {0}"#)]
73    Restriction(PathBuf, PathBuf),
74
75    #[error(r#"Invalid module "{0}" specifier is not a valid subpath for the "exports" resolution of {1}"#)]
76    InvalidModuleSpecifier(String, PathBuf),
77
78    #[error(r#"Invalid "exports" target "{0}" defined for '{1}' in the package config {2}"#)]
79    InvalidPackageTarget(String, String, PathBuf),
80
81    #[error(r#"Package subpath '{0}' is not defined by "exports" in {1}"#)]
82    PackagePathNotExported(String, PathBuf),
83
84    #[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."#)]
85    InvalidPackageConfig(PathBuf),
86
87    #[error(r#"Default condition should be last one in "{0}""#)]
88    InvalidPackageConfigDefault(PathBuf),
89
90    #[error(r#"Expecting folder to folder mapping. "{0}" should end with "/"#)]
91    InvalidPackageConfigDirectory(PathBuf),
92
93    #[error(r#"Package import specifier "{0}" is not defined in package {1}"#)]
94    PackageImportNotDefined(String, PathBuf),
95
96    #[error("{0} is unimplemented")]
97    Unimplemented(&'static str),
98
99    /// Occurs when alias paths reference each other.
100    #[error("Recursion in resolving")]
101    Recursion,
102}
103
104impl ResolveError {
105    #[must_use]
106    pub const fn is_ignore(&self) -> bool {
107        matches!(self, Self::Ignored(_))
108    }
109
110    #[must_use]
111    #[cfg(feature = "fs_cache")]
112    pub fn from_serde_json_error(
113        path: PathBuf,
114        error: &serde_json::Error,
115        content: Option<String>,
116    ) -> Self {
117        Self::JSON(JSONError {
118            path,
119            message: error.to_string(),
120            line: error.line(),
121            column: error.column(),
122            content,
123        })
124    }
125}
126
127/// Error for [ResolveError::Specifier]
128#[derive(Debug, Clone, Eq, PartialEq, Error)]
129pub enum SpecifierError {
130    #[error("The specifiers must be a non-empty string. Received \"{0}\"")]
131    Empty(String),
132}
133
134/// JSON error from [serde_json::Error]
135#[derive(Debug, Clone, Eq, PartialEq)]
136pub struct JSONError {
137    pub path: PathBuf,
138    pub message: String,
139    pub line: usize,
140    pub column: usize,
141    pub content: Option<String>,
142}
143
144#[derive(Debug, Clone, Error)]
145#[error("{0}")]
146pub struct IOError(Arc<io::Error>);
147
148impl PartialEq for IOError {
149    fn eq(&self, other: &Self) -> bool {
150        self.0.kind() == other.0.kind()
151    }
152}
153
154impl From<IOError> for io::Error {
155    fn from(error: IOError) -> Self {
156        let io_error = error.0.as_ref();
157        Self::new(io_error.kind(), io_error.to_string())
158    }
159}
160
161impl From<io::Error> for ResolveError {
162    fn from(err: io::Error) -> Self {
163        Self::IOError(IOError(Arc::new(err)))
164    }
165}
166
167#[test]
168fn test_into_io_error() {
169    use std::io::{self, ErrorKind};
170    let error_string = "IOError occurred";
171    let string_error = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
172    let string_error2 = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
173    let resolve_io_error: ResolveError = ResolveError::from(string_error2);
174
175    assert_eq!(resolve_io_error, ResolveError::from(string_error));
176    assert_eq!(resolve_io_error.clone(), resolve_io_error);
177    let ResolveError::IOError(io_error) = resolve_io_error else { unreachable!() };
178    assert_eq!(
179        format!("{io_error:?}"),
180        r#"IOError(Custom { kind: Interrupted, error: "IOError occurred" })"#
181    );
182    // fix for https://github.com/web-infra-dev/rspack/issues/4564
183    let std_io_error: io::Error = io_error.into();
184    assert_eq!(std_io_error.kind(), ErrorKind::Interrupted);
185    assert_eq!(std_io_error.to_string(), error_string);
186    assert_eq!(
187        format!("{std_io_error:?}"),
188        r#"Custom { kind: Interrupted, error: "IOError occurred" }"#
189    );
190}
191
192#[test]
193fn test_coverage() {
194    let error = ResolveError::NotFound("x".into());
195    assert_eq!(format!("{error:?}"), r#"NotFound("x")"#);
196    assert_eq!(error.clone(), error);
197
198    let error = ResolveError::Specifier(SpecifierError::Empty("x".into()));
199    assert_eq!(format!("{error:?}"), r#"Specifier(Empty("x"))"#);
200    assert_eq!(error.clone(), error);
201}