use derive_more::From;
use orion_error::{ErrorCode, StructError, ToStructError, UvsReason};
use serde::Serialize;
use std::error::Error as StdError;
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Serialize, From)]
pub enum SourceReason {
#[error("not data")]
NotData,
#[error("eof")]
EOF,
#[error("supplier error : {0}")]
SupplierError(String),
#[from(skip)]
#[error("disconnected: {0}")]
Disconnect(String),
#[from(skip)]
#[error("{0}")]
Other(String),
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for SourceReason {
fn error_code(&self) -> i32 {
match self {
SourceReason::NotData => 100, SourceReason::EOF => 101,
SourceReason::Disconnect(_) => 503,
SourceReason::SupplierError(_) => 500, SourceReason::Other(_) => 520,
SourceReason::Uvs(r) => r.error_code(),
}
}
}
pub type SourceError = StructError<SourceReason>;
pub type SourceResult<T> = Result<T, StructError<SourceReason>>;
impl SourceReason {
pub fn err(self) -> SourceError {
self.to_err()
}
pub fn err_detail<S: Into<String>>(self, detail: S) -> SourceError {
self.to_err().with_detail(detail.into())
}
pub fn err_source<E>(self, source: E) -> SourceError
where
E: StdError + Send + Sync + 'static,
{
self.to_err().with_source(source)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_reason_error_codes() {
assert_eq!(SourceReason::NotData.error_code(), 100);
assert_eq!(SourceReason::EOF.error_code(), 101);
assert_eq!(
SourceReason::Disconnect("conn lost".into()).error_code(),
503
);
assert_eq!(
SourceReason::SupplierError("upstream".into()).error_code(),
500
);
assert_eq!(SourceReason::Other("misc".into()).error_code(), 520);
}
#[test]
fn source_reason_error_codes_are_distinct() {
let codes = vec![
SourceReason::NotData.error_code(),
SourceReason::EOF.error_code(),
SourceReason::Disconnect("x".into()).error_code(),
SourceReason::SupplierError("x".into()).error_code(),
SourceReason::Other("x".into()).error_code(),
];
let mut unique = codes.clone();
unique.sort();
unique.dedup();
assert_eq!(codes.len(), unique.len(), "error codes should be distinct");
}
#[test]
fn source_reason_informational_codes_are_below_200() {
assert!(SourceReason::NotData.error_code() < 200);
assert!(SourceReason::EOF.error_code() < 200);
}
#[test]
fn source_reason_retryable_codes_are_5xx() {
let code = SourceReason::Disconnect("x".into()).error_code();
assert!((500..600).contains(&code));
}
#[test]
fn source_reason_err_detail_sets_detail() {
let err = SourceReason::Other("boom".into()).err_detail("ctx");
assert_eq!(err.detail().as_deref(), Some("ctx"));
}
#[test]
fn source_reason_err_source_preserves_source_message() {
let err = SourceReason::Disconnect("read failed".into())
.err_source(std::io::Error::other("disk gone"));
assert!(err.to_string().contains("disk gone"));
}
}