1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-28 10:09:20 -05:00
denoland-deno/ext/node/errors.rs
David Sherret 35cbe2937d
refactor(node): internally add .code() to node resolution errors (#24610)
This makes it easier to tell what kind of error something is (even for
deeply nested errors) and will help in the future once we add error
codes to the JS errors this returns.
2024-07-22 22:47:08 +02:00

709 lines
20 KiB
Rust

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::fmt::Write;
use std::path::PathBuf;
use deno_core::ModuleSpecifier;
use thiserror::Error;
use crate::NodeModuleKind;
use crate::NodeResolutionMode;
macro_rules! kinded_err {
($name:ident, $kind_name:ident) => {
#[derive(Error, Debug)]
#[error(transparent)]
pub struct $name(pub Box<$kind_name>);
impl $name {
pub fn as_kind(&self) -> &$kind_name {
&self.0
}
pub fn into_kind(self) -> $kind_name {
*self.0
}
}
impl<E> From<E> for $name
where
$kind_name: From<E>,
{
fn from(err: E) -> Self {
$name(Box::new($kind_name::from(err)))
}
}
};
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
pub enum NodeJsErrorCode {
ERR_INVALID_MODULE_SPECIFIER,
ERR_INVALID_PACKAGE_CONFIG,
ERR_INVALID_PACKAGE_TARGET,
ERR_MODULE_NOT_FOUND,
ERR_PACKAGE_IMPORT_NOT_DEFINED,
ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_UNKNOWN_FILE_EXTENSION,
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
}
impl std::fmt::Display for NodeJsErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl NodeJsErrorCode {
pub fn as_str(&self) -> &'static str {
use NodeJsErrorCode::*;
match self {
ERR_INVALID_MODULE_SPECIFIER => "ERR_INVALID_MODULE_SPECIFIER",
ERR_INVALID_PACKAGE_CONFIG => "ERR_INVALID_PACKAGE_CONFIG",
ERR_INVALID_PACKAGE_TARGET => "ERR_INVALID_PACKAGE_TARGET",
ERR_MODULE_NOT_FOUND => "ERR_MODULE_NOT_FOUND",
ERR_PACKAGE_IMPORT_NOT_DEFINED => "ERR_PACKAGE_IMPORT_NOT_DEFINED",
ERR_PACKAGE_PATH_NOT_EXPORTED => "ERR_PACKAGE_PATH_NOT_EXPORTED",
ERR_UNKNOWN_FILE_EXTENSION => "ERR_UNKNOWN_FILE_EXTENSION",
ERR_UNSUPPORTED_DIR_IMPORT => "ERR_UNSUPPORTED_DIR_IMPORT",
ERR_UNSUPPORTED_ESM_URL_SCHEME => "ERR_UNSUPPORTED_ESM_URL_SCHEME",
}
}
}
pub trait NodeJsErrorCoded {
fn code(&self) -> NodeJsErrorCode;
}
kinded_err!(
ResolvePkgSubpathFromDenoModuleError,
ResolvePkgSubpathFromDenoModuleErrorKind
);
impl NodeJsErrorCoded for ResolvePkgSubpathFromDenoModuleError {
fn code(&self) -> NodeJsErrorCode {
use ResolvePkgSubpathFromDenoModuleErrorKind::*;
match self.as_kind() {
PackageSubpathResolve(e) => e.code(),
UrlToNodeResolution(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum ResolvePkgSubpathFromDenoModuleErrorKind {
#[error(transparent)]
PackageSubpathResolve(#[from] PackageSubpathResolveError),
#[error(transparent)]
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
}
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
#[derive(Debug, Clone, Error)]
#[error(
"[{}] Invalid module '{}' {}{}",
self.code(),
request,
reason,
maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default()
)]
pub struct InvalidModuleSpecifierError {
pub request: String,
pub reason: Cow<'static, str>,
pub maybe_referrer: Option<String>,
}
impl NodeJsErrorCoded for InvalidModuleSpecifierError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
}
}
#[derive(Debug, Error)]
pub enum LegacyMainResolveError {
#[error(transparent)]
PathToDeclarationUrl(PathToDeclarationUrlError),
}
impl NodeJsErrorCoded for LegacyMainResolveError {
fn code(&self) -> NodeJsErrorCode {
match self {
Self::PathToDeclarationUrl(e) => e.code(),
}
}
}
kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind);
impl NodeJsErrorCoded for PackageFolderResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
PackageFolderResolveErrorKind::NotFoundPackage { .. }
| PackageFolderResolveErrorKind::NotFoundReferrer { .. }
| PackageFolderResolveErrorKind::Io { .. } => {
NodeJsErrorCode::ERR_MODULE_NOT_FOUND
}
}
}
}
#[derive(Debug, Error)]
pub enum PackageFolderResolveErrorKind {
#[error(
"Could not find package '{}' from referrer '{}'{}.",
package_name,
referrer,
referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default()
)]
NotFoundPackage {
package_name: String,
referrer: ModuleSpecifier,
/// Extra information about the referrer.
referrer_extra: Option<String>,
},
#[error(
"Could not find referrer npm package '{}'{}.",
referrer,
referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default()
)]
NotFoundReferrer {
referrer: ModuleSpecifier,
/// Extra information about the referrer.
referrer_extra: Option<String>,
},
#[error("Failed resolving '{package_name}' from referrer '{referrer}'.")]
Io {
package_name: String,
referrer: ModuleSpecifier,
#[source]
source: std::io::Error,
},
}
kinded_err!(PackageSubpathResolveError, PackageSubpathResolveErrorKind);
impl NodeJsErrorCoded for PackageSubpathResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
PackageSubpathResolveErrorKind::PkgJsonLoad(e) => e.code(),
PackageSubpathResolveErrorKind::PackageFolderResolve(e) => e.code(),
PackageSubpathResolveErrorKind::Exports(e) => e.code(),
PackageSubpathResolveErrorKind::LegacyMain(e) => e.code(),
PackageSubpathResolveErrorKind::LegacyExact(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum PackageSubpathResolveErrorKind {
#[error(transparent)]
PkgJsonLoad(#[from] PackageJsonLoadError),
#[error(transparent)]
PackageFolderResolve(#[from] PackageFolderResolveError),
#[error(transparent)]
Exports(PackageExportsResolveError),
#[error(transparent)]
LegacyMain(LegacyMainResolveError),
#[error(transparent)]
LegacyExact(PathToDeclarationUrlError),
}
#[derive(Debug, Error)]
#[error(
"Target '{}' not found from '{}'{}{}.",
target,
pkg_json_path.display(),
maybe_referrer.as_ref().map(|r|
format!(
" from{} referrer {}",
match referrer_kind {
NodeModuleKind::Esm => "",
NodeModuleKind::Cjs => " cjs",
},
r
)
).unwrap_or_default(),
match mode {
NodeResolutionMode::Execution => "",
NodeResolutionMode::Types => " for types",
}
)]
pub struct PackageTargetNotFoundError {
pub pkg_json_path: PathBuf,
pub target: String,
pub maybe_referrer: Option<ModuleSpecifier>,
pub referrer_kind: NodeModuleKind,
pub mode: NodeResolutionMode,
}
impl NodeJsErrorCoded for PackageTargetNotFoundError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_MODULE_NOT_FOUND
}
}
kinded_err!(PackageTargetResolveError, PackageTargetResolveErrorKind);
impl NodeJsErrorCoded for PackageTargetResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
PackageTargetResolveErrorKind::NotFound(e) => e.code(),
PackageTargetResolveErrorKind::InvalidPackageTarget(e) => e.code(),
PackageTargetResolveErrorKind::InvalidModuleSpecifier(e) => e.code(),
PackageTargetResolveErrorKind::PackageResolve(e) => e.code(),
PackageTargetResolveErrorKind::PathToDeclarationUrl(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum PackageTargetResolveErrorKind {
#[error(transparent)]
NotFound(#[from] PackageTargetNotFoundError),
#[error(transparent)]
InvalidPackageTarget(#[from] InvalidPackageTargetError),
#[error(transparent)]
InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
#[error(transparent)]
PackageResolve(#[from] PackageResolveError),
#[error(transparent)]
PathToDeclarationUrl(#[from] PathToDeclarationUrlError),
}
kinded_err!(PackageExportsResolveError, PackageExportsResolveErrorKind);
impl NodeJsErrorCoded for PackageExportsResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
PackageExportsResolveErrorKind::PackagePathNotExported(e) => e.code(),
PackageExportsResolveErrorKind::PackageTargetResolve(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum PackageExportsResolveErrorKind {
#[error(transparent)]
PackagePathNotExported(#[from] PackagePathNotExportedError),
#[error(transparent)]
PackageTargetResolve(#[from] PackageTargetResolveError),
}
#[derive(Debug, Error)]
pub enum PathToDeclarationUrlError {
#[error(transparent)]
SubPath(#[from] PackageSubpathResolveError),
}
impl NodeJsErrorCoded for PathToDeclarationUrlError {
fn code(&self) -> NodeJsErrorCode {
match self {
PathToDeclarationUrlError::SubPath(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
#[error(
"[{}] Invalid package config. {}",
self.code(),
self.0
)]
pub struct PackageJsonLoadError(
#[source]
#[from]
pub deno_config::package_json::PackageJsonLoadError,
);
impl NodeJsErrorCoded for PackageJsonLoadError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
}
}
kinded_err!(ClosestPkgJsonError, ClosestPkgJsonErrorKind);
impl NodeJsErrorCoded for ClosestPkgJsonError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
ClosestPkgJsonErrorKind::CanonicalizingDir(e) => e.code(),
ClosestPkgJsonErrorKind::Load(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum ClosestPkgJsonErrorKind {
#[error(transparent)]
CanonicalizingDir(#[from] CanonicalizingPkgJsonDirError),
#[error(transparent)]
Load(#[from] PackageJsonLoadError),
}
#[derive(Debug, Error)]
#[error("[{}] Failed canonicalizing package.json directory '{}'.", self.code(), dir_path.display())]
pub struct CanonicalizingPkgJsonDirError {
pub dir_path: PathBuf,
#[source]
pub source: std::io::Error,
}
impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_MODULE_NOT_FOUND
}
}
#[derive(Debug, Error)]
#[error("TypeScript files are not supported in npm packages: {specifier}")]
pub struct TypeScriptNotSupportedInNpmError {
pub specifier: ModuleSpecifier,
}
impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
}
}
kinded_err!(UrlToNodeResolutionError, UrlToNodeResolutionErrorKind);
impl NodeJsErrorCoded for UrlToNodeResolutionError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
UrlToNodeResolutionErrorKind::TypeScriptNotSupported(e) => e.code(),
UrlToNodeResolutionErrorKind::ClosestPkgJson(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum UrlToNodeResolutionErrorKind {
#[error(transparent)]
TypeScriptNotSupported(#[from] TypeScriptNotSupportedInNpmError),
#[error(transparent)]
ClosestPkgJson(#[from] ClosestPkgJsonError),
}
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
#[derive(Debug, Error)]
#[error(
"[{}] Package import specifier \"{}\" is not defined{}{}",
self.code(),
name,
package_json_path.as_ref().map(|p| format!(" in package {}", p.display())).unwrap_or_default(),
maybe_referrer.as_ref().map(|r| format!(" imported from '{}'", r)).unwrap_or_default(),
)]
pub struct PackageImportNotDefinedError {
pub name: String,
pub package_json_path: Option<PathBuf>,
pub maybe_referrer: Option<ModuleSpecifier>,
}
impl NodeJsErrorCoded for PackageImportNotDefinedError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
}
}
kinded_err!(PackageImportsResolveError, PackageImportsResolveErrorKind);
#[derive(Debug, Error)]
pub enum PackageImportsResolveErrorKind {
#[error(transparent)]
ClosestPkgJson(ClosestPkgJsonError),
#[error(transparent)]
InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
#[error(transparent)]
NotDefined(#[from] PackageImportNotDefinedError),
#[error(transparent)]
Target(#[from] PackageTargetResolveError),
}
impl NodeJsErrorCoded for PackageImportsResolveErrorKind {
fn code(&self) -> NodeJsErrorCode {
match self {
Self::ClosestPkgJson(e) => e.code(),
Self::InvalidModuleSpecifier(e) => e.code(),
Self::NotDefined(e) => e.code(),
Self::Target(e) => e.code(),
}
}
}
kinded_err!(PackageResolveError, PackageResolveErrorKind);
impl NodeJsErrorCoded for PackageResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
PackageResolveErrorKind::ClosestPkgJson(e) => e.code(),
PackageResolveErrorKind::InvalidModuleSpecifier(e) => e.code(),
PackageResolveErrorKind::ExportsResolve(e) => e.code(),
PackageResolveErrorKind::SubpathResolve(e) => e.code(),
}
}
}
#[derive(Debug, Error)]
pub enum PackageResolveErrorKind {
#[error(transparent)]
ClosestPkgJson(#[from] ClosestPkgJsonError),
#[error(transparent)]
InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
#[error(transparent)]
ExportsResolve(#[from] PackageExportsResolveError),
#[error(transparent)]
SubpathResolve(#[from] PackageSubpathResolveError),
}
#[derive(Debug, Error)]
pub enum NodeResolveError {
#[error("Failed joining '{path}' from '{base}'.")]
RelativeJoinError {
path: String,
base: ModuleSpecifier,
#[source]
source: url::ParseError,
},
#[error(transparent)]
PackageImportsResolve(#[from] PackageImportsResolveError),
#[error(transparent)]
UnsupportedEsmUrlScheme(#[from] UnsupportedEsmUrlSchemeError),
#[error("Failed resolving specifier from data url referrer.")]
DataUrlReferrerFailed {
#[source]
source: url::ParseError,
},
#[error(transparent)]
PackageResolve(#[from] PackageResolveError),
#[error(transparent)]
PathToDeclarationUrl(#[from] PathToDeclarationUrlError),
#[error(transparent)]
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
#[error(transparent)]
FinalizeResolution(#[from] FinalizeResolutionError),
}
kinded_err!(FinalizeResolutionError, FinalizeResolutionErrorKind);
#[derive(Debug, Error)]
pub enum FinalizeResolutionErrorKind {
#[error(transparent)]
InvalidModuleSpecifierError(#[from] InvalidModuleSpecifierError),
#[error(transparent)]
ModuleNotFound(#[from] ModuleNotFoundError),
#[error(transparent)]
UnsupportedDirImport(#[from] UnsupportedDirImportError),
}
#[derive(Debug, Error)]
#[error(
"[{}] Cannot find {} '{}'{}",
self.code(),
typ,
specifier,
maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default()
)]
pub struct ModuleNotFoundError {
pub specifier: ModuleSpecifier,
pub maybe_referrer: Option<ModuleSpecifier>,
pub typ: &'static str,
}
impl ModuleNotFoundError {
pub fn code(&self) -> &'static str {
"ERR_MODULE_NOT_FOUND"
}
}
#[derive(Debug, Error)]
#[error(
"[{}] Directory import '{}' is not supported resolving ES modules{}",
self.code(),
dir_url,
maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default(),
)]
pub struct UnsupportedDirImportError {
pub dir_url: ModuleSpecifier,
pub maybe_referrer: Option<ModuleSpecifier>,
}
impl NodeJsErrorCoded for UnsupportedDirImportError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
}
}
#[derive(Debug)]
pub struct InvalidPackageTargetError {
pub pkg_json_path: PathBuf,
pub sub_path: String,
pub target: String,
pub is_import: bool,
pub maybe_referrer: Option<ModuleSpecifier>,
}
impl std::error::Error for InvalidPackageTargetError {}
impl std::fmt::Display for InvalidPackageTargetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let rel_error = !self.is_import
&& !self.target.is_empty()
&& !self.target.starts_with("./");
f.write_char('[')?;
f.write_str(self.code().as_str())?;
f.write_char(']')?;
if self.sub_path == "." {
assert!(!self.is_import);
write!(
f,
" Invalid \"exports\" main target {} defined in the package config {}",
self.target,
self.pkg_json_path.display()
)?;
} else {
let ie = if self.is_import { "imports" } else { "exports" };
write!(
f,
" Invalid \"{}\" target {} defined for '{}' in the package config {}",
ie,
self.target,
self.sub_path,
self.pkg_json_path.display()
)?;
};
if let Some(referrer) = &self.maybe_referrer {
write!(f, " imported from '{}'", referrer)?;
}
if rel_error {
write!(f, "; target must start with \"./\"")?;
}
Ok(())
}
}
impl NodeJsErrorCoded for InvalidPackageTargetError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
}
}
#[derive(Debug)]
pub struct PackagePathNotExportedError {
pub pkg_json_path: PathBuf,
pub subpath: String,
pub maybe_referrer: Option<ModuleSpecifier>,
pub mode: NodeResolutionMode,
}
impl NodeJsErrorCoded for PackagePathNotExportedError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
}
}
impl std::error::Error for PackagePathNotExportedError {}
impl std::fmt::Display for PackagePathNotExportedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_char('[')?;
f.write_str(self.code().as_str())?;
f.write_char(']')?;
let types_msg = match self.mode {
NodeResolutionMode::Execution => String::new(),
NodeResolutionMode::Types => " for types".to_string(),
};
if self.subpath == "." {
write!(
f,
" No \"exports\" main defined{} in '{}'",
types_msg,
self.pkg_json_path.display()
)?;
} else {
write!(
f,
" Package subpath '{}' is not defined{} by \"exports\" in '{}'",
self.subpath,
types_msg,
self.pkg_json_path.display()
)?;
};
if let Some(referrer) = &self.maybe_referrer {
write!(f, " imported from '{}'", referrer)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Error)]
#[error(
"[{}] Only file and data URLs are supported by the default ESM loader.{} Received protocol '{}'",
self.code(),
if cfg!(windows) && url_scheme.len() == 2 { " On Windows, absolute path must be valid file:// URLS."} else { "" },
url_scheme
)]
pub struct UnsupportedEsmUrlSchemeError {
pub url_scheme: String,
}
impl NodeJsErrorCoded for UnsupportedEsmUrlSchemeError {
fn code(&self) -> NodeJsErrorCode {
NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
}
}
#[derive(Debug, Error)]
pub enum ResolvePkgJsonBinExportError {
#[error(transparent)]
PkgJsonLoad(#[from] PackageJsonLoadError),
#[error("Failed resolving binary export. '{}' did not exist", pkg_json_path.display())]
MissingPkgJson { pkg_json_path: PathBuf },
#[error("Failed resolving binary export. {message}")]
InvalidBinProperty { message: String },
#[error(transparent)]
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
}
#[derive(Debug, Error)]
pub enum ResolveBinaryCommandsError {
#[error(transparent)]
PkgJsonLoad(#[from] PackageJsonLoadError),
#[error("'{}' did not have a name", pkg_json_path.display())]
MissingPkgJsonName { pkg_json_path: PathBuf },
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn types_resolution_package_path_not_exported() {
let separator_char = if cfg!(windows) { '\\' } else { '/' };
assert_eq!(
PackagePathNotExportedError {
pkg_json_path: PathBuf::from("test_path").join("package.json"),
subpath: "./jsx-runtime".to_string(),
maybe_referrer: None,
mode: NodeResolutionMode::Types
}.to_string(),
format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './jsx-runtime' is not defined for types by \"exports\" in 'test_path{separator_char}package.json'")
);
assert_eq!(
PackagePathNotExportedError {
pkg_json_path: PathBuf::from("test_path").join("package.json"),
subpath: ".".to_string(),
maybe_referrer: None,
mode: NodeResolutionMode::Types
}.to_string(),
format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] No \"exports\" main defined for types in 'test_path{separator_char}package.json'")
);
}
}