use std::error::Error;
use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorCode {
InactiveEffect,
MissingService,
TypeMismatch,
DuplicateService,
AccessDenied,
InvalidConfig,
Plugin,
Event,
PropertyConflict,
Other,
}
impl ErrorCode {
pub const fn message(self) -> &'static str {
match self {
Self::InactiveEffect => "cannot create effect on inactive context",
Self::MissingService => "required service is unavailable",
Self::TypeMismatch => "stored value has an unexpected type",
Self::DuplicateService => "service has already been registered",
Self::AccessDenied => "service belongs to another fiber",
Self::InvalidConfig => "invalid config",
Self::Plugin => "plugin failed",
Self::Event => "event listener failed",
Self::PropertyConflict => "property is already declared",
Self::Other => "cordis error",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationIssue {
pub message: String,
pub path: Vec<String>,
}
impl ValidationIssue {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
path: Vec::new(),
}
}
pub fn at(mut self, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.path = path.into_iter().map(Into::into).collect();
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
pub issues: Vec<ValidationIssue>,
}
impl ValidationError {
pub fn new(issues: impl IntoIterator<Item = ValidationIssue>) -> Self {
Self {
issues: issues.into_iter().collect(),
}
}
}
impl Display for ValidationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "invalid config:")?;
for (index, issue) in self.issues.iter().enumerate() {
write!(f, " - {}", issue.message)?;
if !issue.path.is_empty() {
write!(f, " (at {})", issue.path.join("."))?;
}
if index + 1 < self.issues.len() {
writeln!(f)?;
}
}
Ok(())
}
}
impl Error for ValidationError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CordisError {
code: ErrorCode,
message: String,
validation: Option<ValidationError>,
}
impl CordisError {
pub fn new(code: ErrorCode) -> Self {
Self {
code,
message: code.message().to_owned(),
validation: None,
}
}
pub fn with_message(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
validation: None,
}
}
pub fn validation(issues: impl IntoIterator<Item = ValidationIssue>) -> Self {
let validation = ValidationError::new(issues);
Self {
code: ErrorCode::InvalidConfig,
message: validation.to_string(),
validation: Some(validation),
}
}
pub const fn code(&self) -> ErrorCode {
self.code
}
pub fn validation_error(&self) -> Option<&ValidationError> {
self.validation.as_ref()
}
pub fn context(mut self, context: impl AsRef<str>) -> Self {
self.message = format!("{}: {}", context.as_ref(), self.message);
self
}
}
impl Display for CordisError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl Error for CordisError {}
impl From<ValidationError> for CordisError {
fn from(value: ValidationError) -> Self {
Self {
code: ErrorCode::InvalidConfig,
message: value.to_string(),
validation: Some(value),
}
}
}
impl From<String> for CordisError {
fn from(value: String) -> Self {
Self::with_message(ErrorCode::Other, value)
}
}
impl From<&str> for CordisError {
fn from(value: &str) -> Self {
Self::with_message(ErrorCode::Other, value)
}
}
pub type Result<T, E = CordisError> = std::result::Result<T, E>;