1use std::{io, path::PathBuf, sync::Arc};
2
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Error)]
9#[non_exhaustive]
10pub enum ResolveError {
11 #[error("Path is ignored {0}")]
23 Ignored(PathBuf),
24
25 #[error("Cannot find module '{0}'")]
27 NotFound(String),
28
29 #[error("Cannot find module '{0}' for matched aliased key '{1}'")]
31 MatchedAliasNotFound(String, String),
32
33 #[error("Tsconfig not found {0}")]
35 TsconfigNotFound(PathBuf),
36
37 #[error("Tsconfig's project reference path points to this tsconfig {0}")]
39 TsconfigSelfReference(PathBuf),
40
41 #[error("{0}")]
42 IOError(IOError),
43
44 #[error("Builtin module {0}")]
49 Builtin(String),
50
51 #[error("Cannot resolve '{0}' for extension aliases '{1}' in '{2}'")]
55 ExtensionAlias(
56 String,
57 String,
58 PathBuf,
59 ),
60
61 #[error("{0}")]
63 Specifier(SpecifierError),
64
65 #[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 #[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#[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#[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 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}