#![warn(missing_docs)]
#![allow(stable_features)]
#![allow(clippy::result_large_err)]
#![allow(clippy::doc_lazy_continuation)]
#![allow(clippy::new_without_default)]
#![allow(clippy::useless_format)]
#![allow(clippy::unnecessary_to_owned)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
#![allow(clippy::single_char_add_str)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::useless_vec)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::await_holding_lock)]
#![allow(clippy::unwrap_or_default)]
#![allow(clippy::vec_init_then_push)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::overly_complex_bool_expr)]
#![allow(clippy::len_zero)]
pub mod backtrace;
pub mod circuit_breaker;
pub mod decrust;
pub mod reporter;
pub mod syntax;
pub mod types;
use std::path::PathBuf;
use std::time::Duration;
pub use self::backtrace::{
AsBacktrace,
BacktraceCompat,
BacktraceFrame,
BacktraceProvider,
BacktraceStatus,
DecrustBacktrace as Backtrace, GenerateImplicitData,
Location,
ThreadId,
Timestamp,
};
pub use self::circuit_breaker::{
CircuitBreaker, CircuitBreakerConfig, CircuitBreakerObserver, CircuitBreakerState,
CircuitMetrics, CircuitOperationType, CircuitTransitionEvent,
};
pub use self::decrust::{
AstMissingImportFixGenerator, AstUnusedCodeFixGenerator, AutocorrectableError,
ClosureCaptureLifetimeFixGenerator, ConfigMissingKeyFixGenerator, ConfigSyntaxFixGenerator,
CrateUsageAnalysis, Decrust, DependencyAnalysisResult, DependencyAnalyzer,
DivisionByZeroFixGenerator, InteractiveRecommendation, InvalidArgumentCountFixGenerator,
IoMissingDirectoryFixGenerator, IoPermissionFixGenerator, JsonParseFixGenerator,
MissingOkErrFixGenerator, NetworkConnectionFixGenerator, NetworkTlsFixGenerator,
OptimizationImpact, QuestionMarkPropagationFixGenerator, RecommendationType,
RecursiveTypeFixGenerator, ReturnLocalReferenceFixGenerator, RuntimePanicFixGenerator,
SecurityImpact, UnnecessaryCloneFixGenerator, UnnecessaryParenthesesFixGenerator,
UnsafeUnwrapFixGenerator, UnstableFeatureFixGenerator, UnusedMutFixGenerator,
VersionCompatibility, YamlParseFixGenerator,
};
pub use self::reporter::{ErrorReportConfig, ErrorReporter};
pub use self::syntax::{FixTemplate, SyntaxGenerator, TemplateRegistry};
pub use self::types::{
Autocorrection, ErrorCategory, ErrorContext, ErrorReportFormat, ErrorSeverity, ErrorSource,
ExtractedParameters, FixDetails, FixType, ParameterExtractor, ParameterSource,
};
pub type Result<T, E = DecrustError> = std::result::Result<T, E>;
pub type DiagnosticResult<T> = std::result::Result<T, Vec<DecrustError>>;
#[derive(Debug)]
pub struct OptionalError(pub Option<Box<dyn std::error::Error + Send + Sync + 'static>>);
impl Clone for OptionalError {
fn clone(&self) -> Self {
match &self.0 {
Some(err) => {
let cloned_err = std::io::Error::other(format!("{}", err));
OptionalError(Some(Box::new(cloned_err)))
}
None => OptionalError(None),
}
}
}
impl std::fmt::Display for OptionalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
Some(err) => write!(f, "{}", err),
None => write!(f, "No error"),
}
}
}
impl std::error::Error for OptionalError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0 {
Some(err) => Some(err.as_ref()),
None => None,
}
}
}
impl std::error::Error for DecrustError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
DecrustError::Io { source, .. } => Some(source),
DecrustError::WithRichContext { source, .. } => Some(source.as_ref()),
DecrustError::Oops { source, .. } => Some(source.as_ref()),
DecrustError::Parse { source, .. } => Some(source.as_ref()),
DecrustError::Network { source, .. } => Some(source.as_ref()),
DecrustError::Config { source, .. } => source
.0
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
DecrustError::Internal { source, .. } => source
.0
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
DecrustError::Concurrency { source, .. } => source
.0
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
DecrustError::ExternalService { source, .. } => source
.0
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
DecrustError::MultipleErrors { errors, .. } => errors
.first()
.map(|e| e as &(dyn std::error::Error + 'static)),
DecrustError::CircuitBreakerOpen { .. } => None,
DecrustError::ResourceExhausted { .. } => None,
DecrustError::StateConflict { .. } => None,
DecrustError::MissingValue { .. } => None,
DecrustError::Validation { .. } => None,
DecrustError::NotFound { .. } => None,
DecrustError::Timeout { .. } => None,
DecrustError::Style { .. } => None,
}
}
}
impl PartialEq for DecrustError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(DecrustError::Parse { kind: k1, .. }, DecrustError::Parse { kind: k2, .. }) => {
k1 == k2
}
(DecrustError::Oops { message: m1, .. }, DecrustError::Oops { message: m2, .. }) => {
m1 == m2
}
(DecrustError::Network { kind: k1, .. }, DecrustError::Network { kind: k2, .. }) => {
k1 == k2
}
(DecrustError::Style { message: m1, .. }, DecrustError::Style { message: m2, .. }) => {
m1 == m2
}
(
DecrustError::Config { message: m1, .. },
DecrustError::Config { message: m2, .. },
) => m1 == m2,
(DecrustError::Io { operation: op1, .. }, DecrustError::Io { operation: op2, .. }) => {
op1 == op2
}
(
DecrustError::Internal { message: m1, .. },
DecrustError::Internal { message: m2, .. },
) => m1 == m2,
(
DecrustError::Concurrency { message: m1, .. },
DecrustError::Concurrency { message: m2, .. },
) => m1 == m2,
(
DecrustError::Timeout { operation: op1, .. },
DecrustError::Timeout { operation: op2, .. },
) => op1 == op2,
(
DecrustError::StateConflict { message: m1, .. },
DecrustError::StateConflict { message: m2, .. },
) => m1 == m2,
(
DecrustError::CircuitBreakerOpen { name: n1, .. },
DecrustError::CircuitBreakerOpen { name: n2, .. },
) => n1 == n2,
(
DecrustError::ResourceExhausted { resource: r1, .. },
DecrustError::ResourceExhausted { resource: r2, .. },
) => r1 == r2,
(
DecrustError::ExternalService {
service_name: s1, ..
},
DecrustError::ExternalService {
service_name: s2, ..
},
) => s1 == s2,
(
DecrustError::MissingValue {
item_description: i1,
..
},
DecrustError::MissingValue {
item_description: i2,
..
},
) => i1 == i2,
(
DecrustError::MultipleErrors { errors: e1, .. },
DecrustError::MultipleErrors { errors: e2, .. },
) => e1 == e2,
(
DecrustError::Validation {
field: f1,
message: m1,
..
},
DecrustError::Validation {
field: f2,
message: m2,
..
},
) => f1 == f2 && m1 == m2,
(
DecrustError::NotFound {
resource_type: r1,
identifier: i1,
..
},
DecrustError::NotFound {
resource_type: r2,
identifier: i2,
..
},
) => r1 == r2 && i1 == i2,
(
DecrustError::WithRichContext {
context: c1,
source: s1,
},
DecrustError::WithRichContext {
context: c2,
source: s2,
},
) => c1.message == c2.message && s1 == s2,
_ => false,
}
}
}
impl backtrace::BacktraceCompat for DecrustError {
fn backtrace(&self) -> Option<&backtrace::DecrustBacktrace> {
match self {
DecrustError::Io { backtrace, .. } => Some(backtrace),
DecrustError::Oops { backtrace, .. } => Some(backtrace),
DecrustError::Style { backtrace, .. } => Some(backtrace),
DecrustError::Parse { backtrace, .. } => Some(backtrace),
DecrustError::Config { backtrace, .. } => Some(backtrace),
DecrustError::Timeout { backtrace, .. } => Some(backtrace),
DecrustError::Network { backtrace, .. } => Some(backtrace),
DecrustError::NotFound { backtrace, .. } => Some(backtrace),
DecrustError::Internal { backtrace, .. } => Some(backtrace),
DecrustError::Validation { backtrace, .. } => Some(backtrace),
DecrustError::Concurrency { backtrace, .. } => Some(backtrace),
DecrustError::MissingValue { backtrace, .. } => Some(backtrace),
DecrustError::StateConflict { backtrace, .. } => Some(backtrace),
DecrustError::MultipleErrors { backtrace, .. } => Some(backtrace),
DecrustError::ExternalService { backtrace, .. } => Some(backtrace),
DecrustError::ResourceExhausted { backtrace, .. } => Some(backtrace),
DecrustError::CircuitBreakerOpen { backtrace, .. } => Some(backtrace),
DecrustError::WithRichContext { source, .. } => source.backtrace(),
}
}
}
impl OptionalError {
pub fn new(opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
OptionalError(opt)
}
pub fn has_error(&self) -> bool {
self.0.is_some()
}
}
impl From<Option<Box<dyn std::error::Error + Send + Sync + 'static>>> for OptionalError {
fn from(opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
OptionalError(opt)
}
}
impl AsRef<Option<Box<dyn std::error::Error + Send + Sync + 'static>>> for OptionalError {
fn as_ref(&self) -> &Option<Box<dyn std::error::Error + Send + Sync + 'static>> {
&self.0
}
}
#[derive(Debug)]
#[allow(clippy::result_large_err)]
pub enum DecrustError {
Io {
source: std::io::Error,
path: Option<PathBuf>,
operation: String,
backtrace: Backtrace,
},
Parse {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
kind: String,
context_info: String,
backtrace: Backtrace,
},
Network {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
url: Option<String>,
kind: String,
backtrace: Backtrace,
},
Config {
message: String,
path: Option<PathBuf>,
source: OptionalError,
backtrace: Backtrace,
},
Validation {
field: String,
message: String,
#[doc(hidden)]
expected: Option<String>,
#[doc(hidden)]
actual: Option<String>,
#[doc(hidden)]
rule: Option<String>,
backtrace: Backtrace,
},
Internal {
message: String,
source: OptionalError,
#[doc(hidden)]
component: Option<String>,
backtrace: Backtrace,
},
CircuitBreakerOpen {
name: String,
retry_after: Option<Duration>,
#[doc(hidden)]
failure_count: Option<u32>,
#[doc(hidden)]
last_error: Option<String>,
backtrace: Backtrace,
},
Timeout {
operation: String,
duration: Duration,
backtrace: Backtrace,
},
ResourceExhausted {
resource: String,
limit: String,
current: String,
backtrace: Backtrace,
},
NotFound {
resource_type: String,
identifier: String,
backtrace: Backtrace,
},
StateConflict {
message: String,
backtrace: Backtrace,
},
Concurrency {
message: String,
source: OptionalError,
backtrace: Backtrace,
},
ExternalService {
service_name: String,
message: String,
source: OptionalError,
backtrace: Backtrace,
},
MissingValue {
item_description: String,
backtrace: Backtrace,
},
MultipleErrors {
errors: Vec<DecrustError>,
backtrace: Backtrace,
},
WithRichContext {
context: types::ErrorContext,
source: Box<DecrustError>,
},
Style {
message: String,
backtrace: Backtrace,
},
Oops {
message: String,
source: Box<dyn std::error::Error + Send + Sync + 'static>,
backtrace: Backtrace,
},
}
impl Clone for DecrustError {
fn clone(&self) -> Self {
match self {
Self::Io {
source,
path,
operation,
..
} => Self::Io {
source: std::io::Error::new(source.kind(), format!("{}", source)),
path: path.clone(),
operation: operation.clone(),
backtrace: Backtrace::generate(),
},
Self::Parse {
source,
kind,
context_info,
..
} => Self::Parse {
source: Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{}", source),
)),
kind: kind.clone(),
context_info: context_info.clone(),
backtrace: Backtrace::generate(),
},
Self::Network {
source, url, kind, ..
} => Self::Network {
source: Box::new(std::io::Error::other(format!("{}", source))),
url: url.clone(),
kind: kind.clone(),
backtrace: Backtrace::generate(),
},
Self::Config {
message,
path,
source,
..
} => Self::Config {
message: message.clone(),
path: path.clone(),
source: source.clone(),
backtrace: Backtrace::generate(),
},
Self::Validation {
field,
message,
expected,
actual,
rule,
..
} => Self::Validation {
field: field.clone(),
message: message.clone(),
expected: expected.clone(),
actual: actual.clone(),
rule: rule.clone(),
backtrace: Backtrace::generate(),
},
Self::Internal {
message,
source,
component,
..
} => Self::Internal {
message: message.clone(),
source: source.clone(),
component: component.clone(),
backtrace: Backtrace::generate(),
},
Self::CircuitBreakerOpen {
name,
retry_after,
failure_count,
last_error,
..
} => Self::CircuitBreakerOpen {
name: name.clone(),
retry_after: *retry_after,
failure_count: *failure_count,
last_error: last_error.clone(),
backtrace: Backtrace::generate(),
},
Self::Timeout {
operation,
duration,
..
} => Self::Timeout {
operation: operation.clone(),
duration: *duration,
backtrace: Backtrace::generate(),
},
Self::ResourceExhausted {
resource,
limit,
current,
..
} => Self::ResourceExhausted {
resource: resource.clone(),
limit: limit.clone(),
current: current.clone(),
backtrace: Backtrace::generate(),
},
Self::NotFound {
resource_type,
identifier,
..
} => Self::NotFound {
resource_type: resource_type.clone(),
identifier: identifier.clone(),
backtrace: Backtrace::generate(),
},
Self::StateConflict { message, .. } => Self::StateConflict {
message: message.clone(),
backtrace: Backtrace::generate(),
},
Self::Concurrency {
message, source, ..
} => Self::Concurrency {
message: message.clone(),
source: source.clone(),
backtrace: Backtrace::generate(),
},
Self::ExternalService {
service_name,
message,
source,
..
} => Self::ExternalService {
service_name: service_name.clone(),
message: message.clone(),
source: source.clone(),
backtrace: Backtrace::generate(),
},
Self::MissingValue {
item_description, ..
} => Self::MissingValue {
item_description: item_description.clone(),
backtrace: Backtrace::generate(),
},
Self::MultipleErrors { errors, .. } => Self::MultipleErrors {
errors: errors.clone(),
backtrace: Backtrace::generate(),
},
Self::WithRichContext { context, source } => {
Self::WithRichContext {
context: context.clone(),
source: Box::new((**source).clone()),
}
}
Self::Style { message, .. } => Self::Style {
message: message.clone(),
backtrace: Backtrace::generate(),
},
Self::Oops {
message, source, ..
} => Self::Oops {
message: message.clone(),
source: Box::new(std::io::Error::other(format!("{}", source))),
backtrace: Backtrace::generate(),
},
}
}
}
impl std::fmt::Display for DecrustError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DecrustError::Io {
source,
path,
operation,
..
} => {
write!(
f,
"I/O error during operation '{}' on path '{}': {}",
operation,
path.as_ref()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "N/A".to_string()),
source
)
}
DecrustError::Parse {
source,
kind,
context_info,
..
} => {
write!(f, "{} parsing error: {} ({})", kind, source, context_info)
}
DecrustError::Network {
source, url, kind, ..
} => {
write!(
f,
"{} network error: {} (URL: {})",
kind,
source,
url.as_deref().unwrap_or("N/A")
)
}
DecrustError::Config {
message,
path,
source,
..
} => {
if let Some(p) = path {
if let Some(s) = &source.0 {
write!(
f,
"Configuration error in '{}': {} ({})",
p.display(),
message,
s
)
} else {
write!(f, "Configuration error in '{}': {}", p.display(), message)
}
} else if let Some(s) = &source.0 {
write!(f, "Configuration error: {} ({})", message, s)
} else {
write!(f, "Configuration error: {}", message)
}
}
DecrustError::Validation { field, message, .. } => {
write!(f, "Validation error for '{}': {}", field, message)
}
DecrustError::Internal {
message, source, ..
} => {
if let Some(s) = &source.0 {
write!(f, "Internal error: {} ({})", message, s)
} else {
write!(f, "Internal error: {}", message)
}
}
DecrustError::CircuitBreakerOpen {
name, retry_after, ..
} => {
if let Some(duration) = retry_after {
write!(
f,
"Circuit breaker '{}' is open. Retry after {:?}",
name, duration
)
} else {
write!(f, "Circuit breaker '{}' is open", name)
}
}
DecrustError::Timeout {
operation,
duration,
..
} => {
write!(
f,
"Operation '{}' timed out after {:?}",
operation, duration
)
}
DecrustError::ResourceExhausted {
resource,
limit,
current,
..
} => {
write!(
f,
"Resource '{}' exhausted: {} (limit: {})",
resource, current, limit
)
}
DecrustError::NotFound {
resource_type,
identifier,
..
} => {
write!(f, "{} not found: {}", resource_type, identifier)
}
DecrustError::StateConflict { message, .. } => {
write!(f, "State conflict: {}", message)
}
DecrustError::Concurrency {
message, source, ..
} => {
if let Some(s) = &source.0 {
write!(f, "Concurrency error: {} ({})", message, s)
} else {
write!(f, "Concurrency error: {}", message)
}
}
DecrustError::ExternalService {
service_name,
message,
source,
..
} => {
if let Some(s) = &source.0 {
write!(
f,
"External service '{}' error: {} ({})",
service_name, message, s
)
} else {
write!(f, "External service '{}' error: {}", service_name, message)
}
}
DecrustError::MissingValue {
item_description, ..
} => {
write!(f, "Missing value: {}", item_description)
}
DecrustError::MultipleErrors { errors, .. } => {
write!(f, "Multiple errors ({} total):", errors.len())?;
for (i, err) in errors.iter().enumerate() {
write!(f, "\n {}. {}", i + 1, err)?;
}
Ok(())
}
DecrustError::WithRichContext {
context, source, ..
} => {
write!(f, "{}: {}", context.message, source)
}
DecrustError::Style { message, .. } => {
write!(f, "Style issue: {}", message)
}
DecrustError::Oops {
message, source, ..
} => {
write!(f, "{}: {}", message, source)
}
}
}
}
impl DecrustError {
pub fn add_context(self, context: types::ErrorContext) -> Self {
DecrustError::WithRichContext {
context,
source: Box::new(self),
}
}
pub fn add_context_msg(self, message: impl Into<String>) -> Self {
let error_context = types::ErrorContext::new(message);
self.add_context(error_context)
}
pub fn category(&self) -> types::ErrorCategory {
match self {
DecrustError::Io { .. } => types::ErrorCategory::Io,
DecrustError::Parse { .. } => types::ErrorCategory::Parsing,
DecrustError::Network { .. } => types::ErrorCategory::Network,
DecrustError::Config { .. } => types::ErrorCategory::Configuration,
DecrustError::Validation { .. } => types::ErrorCategory::Validation,
DecrustError::Internal { .. } => types::ErrorCategory::Internal,
DecrustError::CircuitBreakerOpen { .. } => types::ErrorCategory::CircuitBreaker,
DecrustError::Timeout { .. } => types::ErrorCategory::Timeout,
DecrustError::ResourceExhausted { .. } => types::ErrorCategory::ResourceExhaustion,
DecrustError::NotFound { .. } => types::ErrorCategory::NotFound,
DecrustError::StateConflict { .. } => types::ErrorCategory::StateConflict,
DecrustError::Concurrency { .. } => types::ErrorCategory::Concurrency,
DecrustError::ExternalService { .. } => types::ErrorCategory::ExternalService,
DecrustError::MultipleErrors { .. } => types::ErrorCategory::Multiple,
DecrustError::WithRichContext { source, .. } => source.category(),
DecrustError::Style { .. } => types::ErrorCategory::Style,
DecrustError::Oops { .. } => types::ErrorCategory::Unspecified,
DecrustError::MissingValue { .. } => types::ErrorCategory::Validation,
}
}
pub fn severity(&self) -> types::ErrorSeverity {
if let DecrustError::WithRichContext { context, .. } = self {
context.severity
} else {
types::ErrorSeverity::Error
}
}
pub fn get_rich_context(&self) -> Option<&types::ErrorContext> {
match self {
DecrustError::WithRichContext { context, .. } => Some(context),
_ => None,
}
}
}
pub trait DecrustResultExt<T, EOrig> {
fn decrust_context_msg(self, message: &str) -> Result<T, DecrustError>;
fn decrust_context_msg_owned(self, message: String) -> Result<T, DecrustError>;
fn decrust_context_rich(self, context: types::ErrorContext) -> Result<T, DecrustError>;
}
impl<T, E> DecrustResultExt<T, E> for std::result::Result<T, E>
where
E: Into<DecrustError>,
{
#[track_caller]
fn decrust_context_msg(self, message: &str) -> Result<T, DecrustError> {
match self {
Ok(value) => Ok(value),
Err(err) => {
let decrust_err: DecrustError = err.into();
Err(DecrustError::WithRichContext {
context: types::ErrorContext::new(message),
source: Box::new(decrust_err),
})
}
}
}
#[track_caller]
fn decrust_context_msg_owned(self, message: String) -> Result<T, DecrustError> {
match self {
Ok(value) => Ok(value),
Err(err) => {
let decrust_err: DecrustError = err.into();
Err(DecrustError::WithRichContext {
context: types::ErrorContext::new(message),
source: Box::new(decrust_err),
})
}
}
}
#[track_caller]
fn decrust_context_rich(self, context: types::ErrorContext) -> Result<T, DecrustError> {
match self {
Ok(value) => Ok(value),
Err(err) => {
let decrust_err: DecrustError = err.into();
Err(DecrustError::WithRichContext {
context,
source: Box::new(decrust_err),
})
}
}
}
}
pub trait DecrustOptionExt<T> {
fn decrust_ok_or_missing_value(self, item_description: &str) -> Result<T, DecrustError>;
fn decrust_ok_or_missing_value_owned(self, item_description: String)
-> Result<T, DecrustError>;
}
impl<T> DecrustOptionExt<T> for Option<T> {
#[track_caller]
fn decrust_ok_or_missing_value(self, item_description: &str) -> Result<T, DecrustError> {
match self {
Some(v) => Ok(v),
None => Err(DecrustError::MissingValue {
item_description: item_description.to_string(),
backtrace: Backtrace::generate(),
}),
}
}
#[track_caller]
fn decrust_ok_or_missing_value_owned(
self,
item_description: String,
) -> Result<T, DecrustError> {
match self {
Some(v) => Ok(v),
None => Err(DecrustError::MissingValue {
item_description,
backtrace: Backtrace::generate(),
}),
}
}
}
pub trait InfallibleResultExt<E> {
fn extract_err(self) -> E;
}
impl<E> InfallibleResultExt<E> for Result<std::convert::Infallible, E> {
fn extract_err(self) -> E {
match self {
Ok(infallible) => match infallible {},
Err(e) => e,
}
}
}
pub trait DecrustResultExtConvenience<T, EOrig> {
fn decrust_context<S: Into<String>>(self, message: S) -> Result<T, DecrustError>;
}
impl<T, E> DecrustResultExtConvenience<T, E> for std::result::Result<T, E>
where
E: Into<DecrustError>,
{
fn decrust_context<S: Into<String>>(self, message: S) -> Result<T, DecrustError> {
self.decrust_context_msg_owned(message.into())
}
}
pub trait DecrustOptionExtConvenience<T> {
fn decrust_ok_or_missing<S: Into<String>>(self, item_description: S)
-> Result<T, DecrustError>;
}
impl<T> DecrustOptionExtConvenience<T> for Option<T> {
fn decrust_ok_or_missing<S: Into<String>>(
self,
item_description: S,
) -> Result<T, DecrustError> {
self.decrust_ok_or_missing_value_owned(item_description.into())
}
}
impl From<std::io::Error> for DecrustError {
fn from(err: std::io::Error) -> Self {
DecrustError::Io {
source: err,
path: None,
operation: "I/O operation".to_string(),
backtrace: Backtrace::generate(),
}
}
}
impl From<Box<dyn std::error::Error + Send + Sync + 'static>> for DecrustError {
fn from(err: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
DecrustError::Oops {
message: "Generic error occurred".to_string(),
source: err,
backtrace: Backtrace::generate(),
}
}
}
impl From<Box<dyn std::error::Error>> for DecrustError {
fn from(err: Box<dyn std::error::Error>) -> Self {
let message = format!("Generic error occurred: {}", err);
DecrustError::Internal {
message,
source: OptionalError(None), component: None,
backtrace: Backtrace::generate(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use backtrace::BacktraceCompat;
#[test]
fn test_error_creation_and_context() {
let source_opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>> = None;
let err = DecrustError::Internal {
message: "Test error".to_string(),
source: OptionalError(source_opt),
component: None,
backtrace: Backtrace::generate(),
};
assert_eq!(err.category(), types::ErrorCategory::Internal);
let err_with_context_res: Result<(), DecrustError> =
Err(err).decrust_context_msg("Additional context");
assert!(err_with_context_res.is_err());
let err_with_context = err_with_context_res.unwrap_err();
if let DecrustError::WithRichContext {
context, source, ..
} = &err_with_context
{
assert_eq!(context.message, "Additional context");
if let DecrustError::Internal { message, .. } = source.as_ref() {
assert_eq!(message, "Test error");
} else {
panic!("Expected Internal error variant, got {:?}", source);
}
} else {
panic!(
"Expected WithRichContext error variant, got {:?}",
err_with_context
);
}
}
#[test]
fn test_error_clone() {
let io_err_orig = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let original_err = DecrustError::Io {
source: io_err_orig,
path: Some(PathBuf::from("/path/to/file")),
operation: "read_file".to_string(),
backtrace: Backtrace::generate(),
};
let cloned_err = original_err.clone();
assert_eq!(cloned_err.category(), types::ErrorCategory::Io);
if let DecrustError::Io {
ref path,
ref operation,
ref source,
..
} = cloned_err
{
assert_eq!(*path, Some(PathBuf::from("/path/to/file")));
assert_eq!(*operation, "read_file");
assert_eq!(source.kind(), std::io::ErrorKind::NotFound);
} else {
panic!("Expected Io error variant");
}
assert!(BacktraceCompat::backtrace(&cloned_err).is_some());
}
#[test]
fn test_option_ext() {
let opt_value: Option<i32> = Some(42);
let result = opt_value.decrust_ok_or_missing_value("test value");
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
let opt_none: Option<i32> = None;
let result = opt_none.decrust_ok_or_missing_value("test value");
assert!(result.is_err());
if let Err(DecrustError::MissingValue {
item_description, ..
}) = result
{
assert_eq!(item_description, "test value");
} else {
panic!("Expected MissingValue error variant");
}
let opt_none2: Option<i32> = None;
let result2 = opt_none2.decrust_ok_or_missing_value_owned("owned test value".to_string());
assert!(result2.is_err());
if let Err(DecrustError::MissingValue {
item_description, ..
}) = result2
{
assert_eq!(item_description, "owned test value");
} else {
panic!("Expected MissingValue error variant");
}
let opt_none3: Option<i32> = None;
let result3 = opt_none3.decrust_ok_or_missing("convenience test value");
assert!(result3.is_err());
if let Err(DecrustError::MissingValue {
item_description, ..
}) = result3
{
assert_eq!(item_description, "convenience test value");
} else {
panic!("Expected MissingValue error variant");
}
}
#[test]
fn test_object_safety() {
let result: Result<i32, DecrustError> = Ok(42);
let option: Option<i32> = Some(42);
let _result_trait: &dyn DecrustResultExt<i32, DecrustError> = &result;
let _option_trait: &dyn DecrustOptionExt<i32> = &option;
fn use_dyn_result_trait(_r: &dyn DecrustResultExt<i32, DecrustError>) {
}
fn use_dyn_option_trait(_o: &dyn DecrustOptionExt<i32>) {
}
use_dyn_result_trait(&result);
use_dyn_option_trait(&option);
assert!(true);
}
#[test]
fn test_infallible_result_ext() {
fn always_fails() -> Result<std::convert::Infallible, String> {
Err("This always fails".to_string())
}
let error: String = always_fails().extract_err();
assert_eq!(error, "This always fails");
fn always_fails_decrust() -> Result<std::convert::Infallible, DecrustError> {
Err(DecrustError::Oops {
message: "Test oops error".to_string(),
source: Box::new(std::io::Error::other("test")),
backtrace: Backtrace::generate(),
})
}
let error: DecrustError = always_fails_decrust().extract_err();
if let DecrustError::Oops { message, .. } = error {
assert_eq!(message, "Test oops error");
} else {
panic!("Expected Oops error variant");
}
}
#[test]
fn test_multiple_errors() {
let err1 = DecrustError::Validation {
field: "username".to_string(),
message: "Username too short".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
};
let err2 = DecrustError::Validation {
field: "password".to_string(),
message: "Password too weak".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
};
let multi_err = DecrustError::MultipleErrors {
errors: vec![err1, err2.clone()],
backtrace: Backtrace::generate(),
};
if let DecrustError::MultipleErrors { errors, .. } = multi_err {
assert_eq!(errors.len(), 2);
if let DecrustError::Validation { field, .. } = &errors[0] {
assert_eq!(field, "username");
} else {
panic!("Expected Validation error variant for errors[0]");
}
if let DecrustError::Validation { field, .. } = &errors[1] {
assert_eq!(field, "password");
} else {
panic!("Expected Validation error variant for errors[1]");
}
} else {
panic!("Expected MultipleErrors error variant");
}
}
#[test]
fn test_whatever_error() {
let original_io_error = std::io::Error::other("some io problem");
let err = DecrustError::Oops {
message: "A oops message".to_string(),
source: Box::new(original_io_error)
as Box<dyn std::error::Error + Send + Sync + 'static>,
backtrace: Backtrace::generate(),
};
if let DecrustError::Oops {
message, source, ..
} = err
{
assert_eq!(message, "A oops message");
assert_eq!(source.to_string(), "some io problem");
} else {
panic!("Expected Oops error variant");
}
}
#[test]
fn test_io_error_display() {
let path_buf = PathBuf::from("/my/file.txt");
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "original os error");
let ak_err = DecrustError::Io {
source: io_err,
path: Some(path_buf),
operation: "reading".to_string(),
backtrace: Backtrace::generate(),
};
assert_eq!(
ak_err.to_string(),
"I/O error during operation 'reading' on path '/my/file.txt': original os error"
);
}
#[test]
fn test_io_error_display_no_path() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "original os error");
let ak_err = DecrustError::Io {
source: io_err,
path: None,
operation: "reading".to_string(),
backtrace: Backtrace::generate(),
};
assert_eq!(
ak_err.to_string(),
"I/O error during operation 'reading' on path 'N/A': original os error"
);
}
}