use serde::{Deserialize, Serialize};
use std::fmt;
use weavatrix_edit::{EditError, ErrorCode as EditErrorCode};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PlanErrorCode {
BasePlanInvalid,
SchemaMismatch,
InvalidPlan,
EvidenceMalformed,
EvidenceMissing,
EvidenceInvalid,
EvidenceTooLarge,
PlanTooLarge,
UnsafePath,
OperationConflict,
ExtensionConflict,
UnsafeNumber,
JsonEncoding,
NotTextOnly,
}
impl PlanErrorCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::BasePlanInvalid => "BASE_PLAN_INVALID",
Self::SchemaMismatch => "SCHEMA_MISMATCH",
Self::InvalidPlan => "INVALID_PLAN",
Self::EvidenceMalformed => "EVIDENCE_MALFORMED",
Self::EvidenceMissing => "EVIDENCE_MISSING",
Self::EvidenceInvalid => "EVIDENCE_INVALID",
Self::EvidenceTooLarge => "EVIDENCE_TOO_LARGE",
Self::PlanTooLarge => "PLAN_TOO_LARGE",
Self::UnsafePath => "UNSAFE_PATH",
Self::OperationConflict => "OPERATION_CONFLICT",
Self::ExtensionConflict => "EXTENSION_CONFLICT",
Self::UnsafeNumber => "UNSAFE_NUMBER",
Self::JsonEncoding => "JSON_ENCODING",
Self::NotTextOnly => "NOT_TEXT_ONLY",
}
}
}
impl fmt::Display for PlanErrorCode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanError {
code: PlanErrorCode,
message: Box<str>,
#[serde(skip_serializing_if = "Option::is_none")]
field: Option<Box<str>>,
#[serde(skip_serializing_if = "Option::is_none")]
operation_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
edit_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
related_edit_index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<Box<str>>,
#[serde(skip_serializing_if = "Option::is_none")]
base_code: Option<EditErrorCode>,
}
impl PlanError {
pub(crate) fn new(code: PlanErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into().into_boxed_str(),
field: None,
operation_index: None,
edit_index: None,
related_edit_index: None,
path: None,
base_code: None,
}
}
pub(crate) fn at_field(mut self, field: impl Into<String>) -> Self {
self.field = Some(field.into().into_boxed_str());
self
}
pub(crate) const fn at_operation(mut self, operation_index: usize) -> Self {
self.operation_index = Some(operation_index);
self
}
pub(crate) fn at_path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into().into_boxed_str());
self
}
pub(crate) const fn with_code(mut self, code: PlanErrorCode) -> Self {
self.code = code;
self
}
pub(crate) fn from_edit(error: &EditError) -> Self {
Self {
code: PlanErrorCode::BasePlanInvalid,
message: error.message().into(),
field: None,
operation_index: None,
edit_index: error.edit_index(),
related_edit_index: error.related_edit_index(),
path: None,
base_code: Some(error.code()),
}
}
#[must_use]
pub const fn code(&self) -> PlanErrorCode {
self.code
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn field(&self) -> Option<&str> {
self.field.as_deref()
}
#[must_use]
pub const fn operation_index(&self) -> Option<usize> {
self.operation_index
}
#[must_use]
pub const fn edit_index(&self) -> Option<usize> {
self.edit_index
}
#[must_use]
pub const fn related_edit_index(&self) -> Option<usize> {
self.related_edit_index
}
#[must_use]
pub fn path(&self) -> Option<&str> {
self.path.as_deref()
}
#[must_use]
pub const fn base_code(&self) -> Option<EditErrorCode> {
self.base_code
}
}
impl fmt::Display for PlanError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}: {}", self.code, self.message)
}
}
impl std::error::Error for PlanError {}