From dd1f2295541a14cd1d32139abf5e0bd83d886585 Mon Sep 17 00:00:00 2001 From: gluax <16431709+gluax@users.noreply.github.com> Date: Thu, 4 Apr 2024 15:57:04 -0700 Subject: [PATCH] refactor(snops): flattten error object --- crates/snops-common/src/rpc/error.rs | 48 +++++++++--------- crates/snops/src/cannon/error.rs | 66 ++++++++++++------------- crates/snops/src/env/error.rs | 73 ++++++++++++++++------------ crates/snops/src/error.rs | 37 ++++++-------- crates/snops/src/schema/error.rs | 44 +++++++++-------- crates/snops/src/schema/storage.rs | 4 ++ crates/snops/src/server/error.rs | 28 +++++------ 7 files changed, 151 insertions(+), 149 deletions(-) diff --git a/crates/snops-common/src/rpc/error.rs b/crates/snops-common/src/rpc/error.rs index 23b41010..ee032c8e 100644 --- a/crates/snops-common/src/rpc/error.rs +++ b/crates/snops-common/src/rpc/error.rs @@ -3,14 +3,29 @@ use strum_macros::AsRefStr; use thiserror::Error; #[macro_export] -macro_rules! impl_serialize_pretty_error { +macro_rules! impl_into_type_str { ($name:path) => { - impl Serialize for $name { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - PrettyError::from(self).serialize(serializer) + impl From<&$name> for String { + fn from(e: &$name) -> Self { + e.as_ref().to_string() + } + } + }; + + ($name:path, |_| $body:expr) => { + impl From<&$name> for String { + fn from(_: &$name) -> Self { + $body + } + } + }; + + ($name:path, |$from_var:ident| $body:expr) => { + impl From<&$name> for String { + fn from($from_var: &$name) -> Self { + use $name::*; + + $body } } }; @@ -45,25 +60,6 @@ macro_rules! impl_into_status_code { }; } -#[derive(Debug, Serialize)] -pub struct PrettyError { - #[serde(rename = "type")] - pub ty: String, - pub error: String, -} - -impl<E> From<&E> for PrettyError -where - E: std::error::Error + AsRef<str>, -{ - fn from(error: &E) -> Self { - Self { - ty: error.as_ref().to_string(), - error: error.to_string(), - } - } -} - #[derive(Debug, Error, Serialize, Deserialize, AsRefStr)] pub enum AgentError { #[error("invalid agent state")] diff --git a/crates/snops/src/cannon/error.rs b/crates/snops/src/cannon/error.rs index 5ee5759c..893f81e7 100644 --- a/crates/snops/src/cannon/error.rs +++ b/crates/snops/src/cannon/error.rs @@ -2,9 +2,7 @@ use std::path::PathBuf; use axum::http::StatusCode; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use snops_common::{ - impl_into_status_code, impl_serialize_pretty_error, rpc::error::PrettyError, state::NodeKey, -}; +use snops_common::{impl_into_status_code, impl_into_type_str, state::NodeKey}; use strum_macros::AsRefStr; use thiserror::Error; @@ -14,13 +12,13 @@ use crate::error::{CommandError, StateError}; #[derive(Debug, Error, AsRefStr)] pub enum AuthorizeError { /// For when a bad AOT command is run - #[error("command error: {0}")] + #[error(transparent)] Command(#[from] CommandError), /// For if invalid JSON is returned from the AOT command #[error("expected function, fee, and broadcast fields in response")] InvalidJson, /// For if invalid JSON is returned from the AOT command - #[error("parse json error: {0}")] + #[error("{0}")] Json(#[source] serde_json::Error), /// For if invalid JSON is returned from the AOT command #[error("expected JSON object in response")] @@ -32,19 +30,19 @@ impl_into_status_code!(AuthorizeError, |value| match value { _ => StatusCode::INTERNAL_SERVER_ERROR, }); +impl_into_type_str!(AuthorizeError, |value| match value { + Command(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + _ => value.as_ref().to_string(), +}); + impl Serialize for AuthorizeError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::Command(e) => state.serialize_field("error", e), - _ => state.serialize_field("error", &self.to_string()), - }?; - + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() } } @@ -63,7 +61,6 @@ pub enum TransactionDrainError { } impl_into_status_code!(TransactionDrainError); -impl_serialize_pretty_error!(TransactionDrainError); #[derive(Debug, Error, AsRefStr)] pub enum TransactionSinkError { @@ -79,7 +76,6 @@ pub enum TransactionSinkError { } impl_into_status_code!(TransactionSinkError); -impl_serialize_pretty_error!(TransactionSinkError); #[derive(Debug, Error, AsRefStr)] pub enum SourceError { @@ -102,7 +98,6 @@ pub enum SourceError { } impl_into_status_code!(SourceError); -impl_serialize_pretty_error!(SourceError); #[derive(Debug, Error, AsRefStr)] pub enum CannonInstanceError { @@ -175,19 +170,19 @@ impl Serialize for ExecutionContextError { #[derive(Debug, Error, AsRefStr)] pub enum CannonError { - #[error("authorize error: {0}")] + #[error(transparent)] Authorize(#[from] AuthorizeError), - #[error("cannon instance error: {0}")] + #[error(transparent)] CannonInstance(#[from] CannonInstanceError), - #[error("command error cannon `{0}`: {1}")] + #[error("cannon `{0}`: {1}")] Command(usize, #[source] CommandError), - #[error("exec ctx error: {0}")] + #[error(transparent)] ExecutionContext(#[from] ExecutionContextError), #[error("target agent offline for {0} `{1}`: {2}")] TargetAgentOffline(&'static str, usize, String), - #[error("tx drain error: {0}")] + #[error(transparent)] TransactionDrain(#[from] TransactionDrainError), - #[error("tx sink error: {0}")] + #[error(transparent)] TransactionSink(#[from] TransactionSinkError), #[error("send `auth` error for cannon `{0}`: {1}")] SendAuthError( @@ -196,9 +191,9 @@ pub enum CannonError { ), #[error("send `tx` error for cannon `{0}`: {1}")] SendTxError(usize, #[source] tokio::sync::mpsc::error::SendError<String>), - #[error("source error: {0}")] + #[error(transparent)] Source(#[from] SourceError), - #[error("state error: {0}")] + #[error(transparent)] State(#[from] StateError), } @@ -215,23 +210,26 @@ impl_into_status_code!(CannonError, |value| match value { _ => StatusCode::INTERNAL_SERVER_ERROR, }); +impl_into_type_str!(CannonError, |value| match value { + Authorize(e) => format!("{}.{}", value.as_ref(), String::from(e)), + CannonInstance(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Command(_, e) => format!("{}.{}", value.as_ref(), e.as_ref()), + ExecutionContext(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + TransactionDrain(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + TransactionSink(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Source(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + State(e) => format!("{}.{}", value.as_ref(), String::from(e)), + _ => value.as_ref().to_string(), +}); + impl Serialize for CannonError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::Authorize(e) => state.serialize_field("error", e), - Self::CannonInstance(e) => state.serialize_field("error", e), - Self::Command(_, e) => state.serialize_field("error", e), - Self::ExecutionContext(e) => state.serialize_field("error", e), - Self::TransactionDrain(e) => state.serialize_field("error", e), - Self::TransactionSink(e) => state.serialize_field("error", e), - _ => state.serialize_field("error", &self.to_string()), - }?; + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() } diff --git a/crates/snops/src/env/error.rs b/crates/snops/src/env/error.rs index a26239c5..94b3f872 100644 --- a/crates/snops/src/env/error.rs +++ b/crates/snops/src/env/error.rs @@ -1,8 +1,7 @@ use axum::http::StatusCode; use serde::{ser::SerializeStruct, Serialize, Serializer}; use snops_common::{ - impl_into_status_code, impl_serialize_pretty_error, - rpc::error::PrettyError, + impl_into_status_code, impl_into_type_str, state::{AgentId, NodeKey}, }; use strum_macros::AsRefStr; @@ -25,11 +24,11 @@ pub enum ExecutionError { AgentOffline, #[error("env `{0}` not found")] EnvNotFound(usize), - #[error("cannon error: `{0}`")] + #[error(transparent)] Cannon(#[from] CannonError), - #[error("join error: `{0}`")] + #[error("{0}")] Join(#[from] JoinError), - #[error("reconcile error: `{0}`")] + #[error(transparent)] Reconcile(#[from] BatchReconcileError), #[error("env timeline is already being executed")] TimelineAlreadyStarted, @@ -43,16 +42,21 @@ impl_into_status_code!(ExecutionError, |value| match value { _ => StatusCode::INTERNAL_SERVER_ERROR, }); +impl_into_type_str!(ExecutionError, |value| match value { + Cannon(e) => format!("{}.{}", value.as_ref(), &String::from(e)), + _ => value.as_ref().to_string(), +}); + impl Serialize for ExecutionError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; + state.serialize_field("type", &String::from(self))?; match self { - Self::Cannon(e) => state.serialize_field("error", e), + Self::Cannon(e) => state.serialize_field("error", &e.to_string()), _ => state.serialize_field("error", &self.to_string()), }?; @@ -83,8 +87,6 @@ impl_into_status_code!(DelegationError, |value| match value { } }); -impl_serialize_pretty_error!(DelegationError); - #[derive(Debug, Error, AsRefStr)] pub enum PrepareError { #[error("duplicate node key: {0}")] @@ -95,7 +97,7 @@ pub enum PrepareError { MissingStorage, #[error("cannot have a node with zero replicas")] NodeHas0Replicas, - #[error("reconcile error: `{0}`")] + #[error(transparent)] Reconcile(#[from] ReconcileError), } @@ -105,16 +107,21 @@ impl_into_status_code!(PrepareError, |value| match value { Reconcile(e) => e.into(), }); +impl_into_type_str!(PrepareError, |value| match value { + Reconcile(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + _ => value.as_ref().to_string(), +}); + impl Serialize for PrepareError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; + state.serialize_field("type", &String::from(self))?; match self { - Self::Reconcile(e) => state.serialize_field("error", e), + Self::Reconcile(e) => state.serialize_field("error", &e.to_string()), _ => state.serialize_field("error", &self.to_string()), }?; @@ -129,7 +136,6 @@ pub enum CleanupError { } impl_into_status_code!(CleanupError, |_| StatusCode::NOT_FOUND); -impl_serialize_pretty_error!(CleanupError); #[derive(Debug, Error, AsRefStr)] pub enum ReconcileError { @@ -146,23 +152,21 @@ impl_into_status_code!(ReconcileError, |value| match value { EnvNotFound(_) | ExpectedInternalAgentPeer { .. } => StatusCode::NOT_FOUND, }); -impl_serialize_pretty_error!(ReconcileError); - #[derive(Debug, Error, AsRefStr)] pub enum EnvError { - #[error("cannon error: `{0}`")] + #[error(transparent)] Cannon(#[from] CannonError), - #[error("cleanup error: `{0}`")] + #[error(transparent)] Cleanup(#[from] CleanupError), #[error("delegation errors occured:{}", .0.iter().map(ToString::to_string).collect::<Vec<_>>().join("\n"))] Delegation(Vec<DelegationError>), - #[error("exec error: `{0}`")] + #[error(transparent)] Execution(#[from] ExecutionError), - #[error("prepare error: `{0}`")] + #[error(transparent)] Prepare(#[from] PrepareError), - #[error("reconcile error: `{0}`")] + #[error(transparent)] Reconcile(#[from] ReconcileError), - #[error("schema error: `{0}`")] + #[error(transparent)] Schema(#[from] SchemaError), } @@ -176,23 +180,28 @@ impl_into_status_code!(EnvError, |value| match value { Schema(e) => e.into(), }); +impl_into_type_str!(EnvError, |value| match value { + Cannon(e) => format!("{}.{}", value.as_ref(), String::from(e)), + Cleanup(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Delegation(e) => format!( + "{}.{}", + value.as_ref(), + e.iter().map(|x| x.as_ref()).collect::<Vec<_>>().join(",") + ), + Execution(e) => format!("{}.{}", value.as_ref(), String::from(e)), + Prepare(e) => format!("{}.{}", value.as_ref(), String::from(e)), + Reconcile(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Schema(e) => format!("{}.{}", value.as_ref(), String::from(e)), +}); + impl Serialize for EnvError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::Cannon(e) => state.serialize_field("error", e), - Self::Cleanup(e) => state.serialize_field("error", e), - Self::Delegation(e) => state.serialize_field("error", e), - Self::Execution(e) => state.serialize_field("error", e), - Self::Prepare(e) => state.serialize_field("error", e), - Self::Reconcile(e) => state.serialize_field("error", e), - Self::Schema(e) => state.serialize_field("error", e), - }?; + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() } diff --git a/crates/snops/src/error.rs b/crates/snops/src/error.rs index 10e28566..3bfc618c 100644 --- a/crates/snops/src/error.rs +++ b/crates/snops/src/error.rs @@ -1,22 +1,21 @@ use std::process::ExitStatus; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use snops_common::rpc::error::PrettyError; use snops_common::state::AgentId; -use snops_common::{impl_into_status_code, impl_serialize_pretty_error}; +use snops_common::{impl_into_status_code, impl_into_type_str}; use strum_macros::AsRefStr; use thiserror::Error; #[derive(Debug, Error, AsRefStr)] pub enum CommandError { - #[error("error {action} command `{cmd}`: {error}")] + #[error("{action} command `{cmd}`: {error}")] Action { action: &'static str, cmd: &'static str, #[source] error: std::io::Error, }, - #[error("error command `{cmd}` failed with `{status}`: {stderr}")] + #[error("command `{cmd}` failed with `{status}`: {stderr}")] Status { cmd: &'static str, status: ExitStatus, @@ -25,7 +24,6 @@ pub enum CommandError { } impl_into_status_code!(CommandError); -impl_serialize_pretty_error!(CommandError); impl CommandError { pub fn action(action: &'static str, cmd: &'static str, error: std::io::Error) -> Self { @@ -42,7 +40,7 @@ impl CommandError { } #[derive(Debug, Error)] -#[error("deserialize error: `{i}`: `{e}`")] +#[error("`{i}`: `{e}`")] pub struct DeserializeError { pub i: usize, #[source] @@ -53,13 +51,13 @@ impl_into_status_code!(DeserializeError); #[derive(Debug, Error, AsRefStr)] pub enum StateError { - #[error("common agent error: {0}")] + #[error(transparent)] Agent(#[from] snops_common::prelude::error::AgentError), #[error("source agent has no addr id: `{0}`")] NoAddress(AgentId), - #[error("common reconcile error: {0}")] + #[error(transparent)] Reconcile(#[from] snops_common::prelude::error::ReconcileError), - #[error("rpc error: {0}")] + #[error("{0}")] Rpc(#[from] tarpc::client::RpcError), #[error("source agent not found id: `{0}`")] SourceAgentNotFound(AgentId), @@ -67,25 +65,20 @@ pub enum StateError { impl_into_status_code!(StateError); +impl_into_type_str!(StateError, |value| match value { + Agent(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Reconcile(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + _ => value.as_ref().to_string(), +}); + impl Serialize for StateError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::Agent(e) => { - let pe = PrettyError::from(e); - state.serialize_field("error", &pe) - } - Self::Reconcile(e) => { - let pe = PrettyError::from(e); - state.serialize_field("error", &pe) - } - _ => state.serialize_field("error", &self.to_string()), - }?; + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() } diff --git a/crates/snops/src/schema/error.rs b/crates/snops/src/schema/error.rs index eb365e48..36307d0b 100644 --- a/crates/snops/src/schema/error.rs +++ b/crates/snops/src/schema/error.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use axum::http::StatusCode; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use snops_common::{impl_into_status_code, impl_serialize_pretty_error, rpc::error::PrettyError}; +use snops_common::{impl_into_status_code, impl_into_type_str}; use strum_macros::AsRefStr; use thiserror::Error; use url::Url; @@ -11,23 +11,23 @@ use crate::error::CommandError; #[derive(Debug, Error, AsRefStr)] pub enum StorageError { - #[error("command error id: `{0}`: {1}")] + #[error("storage id: `{1}`: {0}")] Command(CommandError, String), - #[error("error mkdirs for storage generation id: `{0}`: {1}")] + #[error("mkdirs for storage generation id: `{0}`: {1}")] GenerateStorage(String, #[source] std::io::Error), - #[error("error generating genesis id: `{0}`: {1}")] + #[error("generating genesis id: `{0}`: {1}")] FailedToGenGenesis(String, #[source] std::io::Error), - #[error("error fetching genesis block id: `{0}` url: `{1}`: {2}")] + #[error("fetching genesis block id: `{0}` url: `{1}`: {2}")] FailedToFetchGenesis(String, Url, #[source] reqwest::Error), - #[error("error writing genesis block id: `{0}`: {1}")] + #[error("writing genesis block id: `{0}`: {1}")] FailedToWriteGenesis(String, #[source] std::io::Error), - #[error("error taring ledger id: `{0}`: {1}")] + #[error("taring ledger id: `{0}`: {1}")] FailedToTarLedger(String, #[source] std::io::Error), #[error("the specified storage ID {0} doesn't exist, and no generation params were specified")] NoGenerationParams(String), - #[error("error reading balances {0:#?}: {1}")] + #[error("reading balances {0:#?}: {1}")] ReadBalances(PathBuf, #[source] std::io::Error), - #[error("error parsing balances {0:#?}: {1}")] + #[error("parsing balances {0:#?}: {1}")] ParseBalances(PathBuf, #[source] serde_json::Error), } @@ -38,7 +38,10 @@ impl_into_status_code!(StorageError, |value| match value { _ => StatusCode::INTERNAL_SERVER_ERROR, }); -impl_serialize_pretty_error!(StorageError); +impl_into_type_str!(StorageError, |value| match value { + Command(e, _) => format!("{}.{}", value.as_ref(), e.as_ref()), + _ => value.as_ref().to_string(), +}); #[derive(Debug, Error)] #[error("invalid node target string")] @@ -59,15 +62,13 @@ impl_into_status_code!(KeySourceError, |value| match value { InvalidCommitteeIndex(_) => StatusCode::BAD_REQUEST, }); -impl_serialize_pretty_error!(KeySourceError); - #[derive(Debug, Error, AsRefStr)] pub enum SchemaError { #[error("key source error: {0}")] KeySource(#[from] KeySourceError), - #[error("node target error: {0}")] + #[error(transparent)] NodeTarget(#[from] NodeTargetError), - #[error("storage error: {0}")] + #[error(transparent)] Storage(#[from] StorageError), } @@ -77,19 +78,20 @@ impl_into_status_code!(SchemaError, |value| match value { Storage(e) => e.into(), }); +impl_into_type_str!(SchemaError, |value| match value { + KeySource(e) => format!("{}.{}", value.as_ref(), e.as_ref()), + Storage(e) => format!("{}.{}", value.as_ref(), String::from(e)), + _ => value.as_ref().to_string(), +}); + impl Serialize for SchemaError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::KeySource(e) => state.serialize_field("error", e), - Self::NodeTarget(e) => state.serialize_field("error", &e.to_string()), - Self::Storage(e) => state.serialize_field("error", e), - }?; + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() } diff --git a/crates/snops/src/schema/storage.rs b/crates/snops/src/schema/storage.rs index 4dcb7350..4631a820 100644 --- a/crates/snops/src/schema/storage.rs +++ b/crates/snops/src/schema/storage.rs @@ -197,6 +197,8 @@ impl Document { let mut base = state.cli.path.join("storage"); base.push(&id); + // TODO: The dir can be made by a previous run and the aot stuff can fail + // i.e an empty/incomplete directory can exist and we should check those let exists = matches!(tokio::fs::try_exists(&base).await, Ok(true)); // TODO: respect self.prefer_existing @@ -400,6 +402,8 @@ fn pick_commitee_addr(entry: (String, u64)) -> String { entry.0 } +// TODO: function should also take storage id +// in case of error, the storage id can be used to provide more context async fn read_to_addrs<T: DeserializeOwned>( f: impl Fn(T) -> String, file: PathBuf, diff --git a/crates/snops/src/server/error.rs b/crates/snops/src/server/error.rs index 389843d9..159cbd32 100644 --- a/crates/snops/src/server/error.rs +++ b/crates/snops/src/server/error.rs @@ -2,7 +2,7 @@ use axum::{response::IntoResponse, Json}; use http::StatusCode; use serde::{ser::SerializeStruct, Serialize, Serializer}; use serde_json::json; -use snops_common::{impl_into_status_code, state::AgentId}; +use snops_common::{impl_into_status_code, impl_into_type_str, state::AgentId}; use thiserror::Error; use crate::{ @@ -14,13 +14,13 @@ use crate::{ pub enum ServerError { #[error("agent `{0}` not found")] AgentNotFound(AgentId), - #[error("cannon error: {0}")] + #[error(transparent)] Cannon(#[from] CannonError), - #[error("deserialize error: {0}")] + #[error(transparent)] Deserialize(#[from] DeserializeError), - #[error("env error: {0}")] + #[error(transparent)] Env(#[from] EnvError), - #[error("schema error: {0}")] + #[error(transparent)] Schema(#[from] SchemaError), } @@ -32,21 +32,21 @@ impl_into_status_code!(ServerError, |value| match value { Schema(e) => e.into(), }); +impl_into_type_str!(ServerError, |value| match value { + Cannon(e) => format!("{}.{}", value.as_ref(), String::from(e)), + Env(e) => format!("{}.{}", value.as_ref(), String::from(e)), + Schema(e) => format!("{}.{}", value.as_ref(), String::from(e)), + _ => value.as_ref().to_string(), +}); + impl Serialize for ServerError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("Error", 2)?; - state.serialize_field("type", self.as_ref())?; - - match self { - Self::Cannon(e) => state.serialize_field("error", e), - Self::Deserialize(e) => state.serialize_field("error", &e.to_string()), - Self::Env(e) => state.serialize_field("error", e), - Self::Schema(e) => state.serialize_field("error", e), - _ => state.serialize_field("error", &self.to_string()), - }?; + state.serialize_field("type", &String::from(self))?; + state.serialize_field("error", &self.to_string())?; state.end() }