Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: improve storage error message #8740

Merged
merged 6 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions crates/rspack_core/src/cache/persistent/storage/memory.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::sync::{Arc, Mutex};

use rspack_error::Result;
use rspack_storage::Storage;
use rspack_storage::{Result, Storage};
use rustc_hash::FxHashMap as HashMap;
use tokio::sync::oneshot::{channel, Receiver};

Expand Down
4 changes: 2 additions & 2 deletions crates/rspack_core/src/cache/persistent/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{path::PathBuf, sync::Arc};
pub use memory::MemoryStorage;
use rspack_fs::IntermediateFileSystem;
pub use rspack_storage::Storage;
use rspack_storage::{PackBridgeFS, PackStorage, PackStorageOptions};
use rspack_storage::{BridgeFileSystem, PackStorage, PackStorageOptions};

/// Storage Options
///
Expand All @@ -31,7 +31,7 @@ pub fn create_storage(
bucket_size: 20,
pack_size: 500 * 1024,
expire: 7 * 24 * 60 * 60 * 1000,
fs: Arc::new(PackBridgeFS(fs)),
fs: Arc::new(BridgeFileSystem(fs)),
version,
};
Arc::new(PackStorage::new(option))
Expand Down
177 changes: 177 additions & 0 deletions crates/rspack_storage/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use rspack_error::miette;

use crate::fs::{BatchFSError, FSError};

#[derive(Debug)]
pub struct InvalidDetail {
pub reason: String,
pub packs: Vec<String>,
}

#[derive(Debug)]
pub enum ValidateResult {
NotExists,
Valid,
Invalid(InvalidDetail),
}

impl ValidateResult {
pub fn invalid(reason: &str) -> Self {
Self::Invalid(InvalidDetail {
reason: reason.to_string(),
packs: vec![],
})
}
pub fn invalid_with_packs(reason: &str, packs: Vec<String>) -> Self {
Self::Invalid(InvalidDetail {
reason: reason.to_string(),
packs,
})
}
pub fn is_valid(&self) -> bool {
matches!(self, ValidateResult::Valid)
}
}

#[derive(Debug)]
enum ErrorReason {
Reason(String),
Detail(InvalidDetail),
Error(Box<dyn std::error::Error + Send + Sync>),
}

#[derive(Debug)]
pub enum ErrorType {
Validate,
Save,
Load,
}

impl std::fmt::Display for ErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorType::Validate => write!(f, "validate"),
ErrorType::Save => write!(f, "save"),
ErrorType::Load => write!(f, "load"),
}
}
}

#[derive(Debug)]
pub struct Error {
r#type: Option<ErrorType>,
scope: Option<&'static str>,
inner: ErrorReason,
}

impl From<FSError> for Error {
fn from(e: FSError) -> Self {
Self {
r#type: None,
scope: None,
inner: ErrorReason::Error(Box::new(e)),
}
}
}

impl From<BatchFSError> for Error {
fn from(e: BatchFSError) -> Self {
Self {
r#type: None,
scope: None,
inner: ErrorReason::Error(Box::new(e)),
}
}
}

impl Error {
pub fn from_detail(
r#type: Option<ErrorType>,
scope: Option<&'static str>,
detail: InvalidDetail,
) -> Self {
Self {
r#type,
scope,
inner: ErrorReason::Detail(detail),
}
}
pub fn from_error(
r#type: Option<ErrorType>,
scope: Option<&'static str>,
error: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self {
r#type,
scope,
inner: ErrorReason::Error(error),
}
}
pub fn from_reason(
r#type: Option<ErrorType>,
scope: Option<&'static str>,
reason: String,
) -> Self {
Self {
r#type,
scope,
inner: ErrorReason::Reason(reason),
}
}
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(t) = &self.r#type {
write!(f, "{} ", t)?;
}
if let Some(scope) = self.scope {
write!(f, "scope `{}` ", scope)?;
}
write!(f, "failed due to")?;

match &self.inner {
ErrorReason::Detail(detail) => {
write!(f, " {}", detail.reason)?;
let mut pack_info_lines = detail
.packs
.iter()
.map(|p| format!("- {}", p))
.collect::<Vec<_>>();
if pack_info_lines.len() > 5 {
pack_info_lines.truncate(5);
pack_info_lines.push("...".to_string());
}
if !pack_info_lines.is_empty() {
write!(f, ":\n{}", pack_info_lines.join("\n"))?;
}
}
ErrorReason::Error(e) => {
write!(f, " {}", e)?;
}
ErrorReason::Reason(e) => {
write!(f, " {}", e)?;
}
}
Ok(())
}
}

impl std::error::Error for Error {}

impl miette::Diagnostic for Error {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
Some(Box::new(format!(
"Error::{}",
self
.r#type
.as_ref()
.map_or("".to_string(), |t| t.to_string())
)))
}
fn severity(&self) -> Option<miette::Severity> {
Some(miette::Severity::Warning)
}
}

pub type Result<T> = std::result::Result<T, Error>;
160 changes: 160 additions & 0 deletions crates/rspack_storage/src/fs/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::io::ErrorKind;

use cow_utils::CowUtils;
use rspack_paths::Utf8Path;
use tokio::task::JoinError;

#[derive(Debug)]
pub enum FSOperation {
Read,
Write,
Dir,
Remove,
Stat,
Move,
Redirect,
}

impl std::fmt::Display for FSOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read => write!(f, "read"),
Self::Write => write!(f, "write"),
Self::Dir => write!(f, "create dir"),
Self::Remove => write!(f, "remove"),
Self::Stat => write!(f, "stat"),
Self::Move => write!(f, "move"),
Self::Redirect => write!(f, "redirect"),
}
}
}

#[derive(Debug)]
pub struct FSError {
file: String,
inner: rspack_fs::Error,
opt: FSOperation,
}

impl std::error::Error for FSError {}

impl FSError {
pub fn from_fs_error(file: &Utf8Path, opt: FSOperation, error: rspack_fs::Error) -> Self {
Self {
file: file.to_string(),
inner: error,
opt,
}
}
pub fn from_message(file: &Utf8Path, opt: FSOperation, message: String) -> Self {
Self {
file: file.to_string(),
inner: rspack_fs::Error::Io(std::io::Error::new(std::io::ErrorKind::Other, message)),
opt,
}
}
pub fn is_not_found(&self) -> bool {
if matches!(self.kind(), ErrorKind::NotFound) {
return true;
}
let error_content = self.inner.to_string();
let lower_case_error_content = error_content.cow_to_lowercase();
lower_case_error_content.contains("no such file")
|| lower_case_error_content.contains("file not exists")
}
pub fn kind(&self) -> ErrorKind {
match &self.inner {
rspack_fs::Error::Io(e) => e.kind(),
}
}
}

impl std::fmt::Display for FSError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} `{}` failed due to `{}`",
self.opt,
self.file,
match &self.inner {
rspack_fs::Error::Io(e) => e,
}
)
}
}

#[derive(Debug)]
pub struct BatchFSError {
message: String,
join_error: Option<JoinError>,
errors: Vec<Box<dyn std::error::Error + std::marker::Send + Sync>>,
}

impl From<FSError> for BatchFSError {
fn from(error: FSError) -> Self {
Self {
message: "".to_string(),
join_error: None,
errors: vec![Box::new(error)],
}
}
}

impl std::fmt::Display for BatchFSError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)?;
if let Some(join_error) = &self.join_error {
write!(f, " due to `{}`", join_error)?;
}
if self.errors.len() == 1 {
write!(f, "{}", self.errors[0])?;
} else {
for error in &self.errors {
write!(f, "\n- {}", error)?;
}
}

Ok(())
}
}

impl BatchFSError {
pub fn try_from_joined_result<T: std::error::Error + std::marker::Send + Sync + 'static, R>(
message: &str,
res: Result<Vec<Result<R, T>>, JoinError>,
) -> Result<Vec<R>, Self> {
match res {
Ok(res) => Self::try_from_results(message, res),
Err(join_error) => Err(Self {
message: message.to_string(),
errors: vec![],
join_error: Some(join_error),
}),
}
}

pub fn try_from_results<T: std::error::Error + std::marker::Send + Sync + 'static, R>(
message: &str,
results: Vec<Result<R, T>>,
) -> Result<Vec<R>, Self> {
let mut errors = vec![];
let mut res = vec![];
for r in results {
match r {
Ok(r) => res.push(r),
Err(e) => errors.push(Box::new(e).into()),
}
}
if errors.is_empty() {
Ok(res)
} else {
Err(Self {
message: message.to_string(),
errors,
join_error: None,
})
}
}
}

impl std::error::Error for BatchFSError {}
Loading
Loading