use serde::{Deserialize, Serialize};
use std::error::Error;
pub use serde_versioned_derive::Versioned;
pub trait FromVersion<T>: Sized {
fn convert(self) -> T;
}
pub trait Versioned: Sized {
type VersionEnum: for<'a> Deserialize<'a> + Serialize;
fn from_version(version: Self::VersionEnum) -> Result<Self, VersionConversionError>;
fn to_version(&self) -> Self::VersionEnum;
fn from_format<'a, F, E>(input: &'a str, deserializer: F) -> Result<Self, FormatError<E>>
where
F: FnOnce(&'a str) -> Result<Self::VersionEnum, E>,
E: Error + Send + Sync + 'static,
{
deserializer(input)
.map_err(|e| FormatError::deserialize(e, Some(input.to_string())))
.and_then(|version| Self::from_version(version).map_err(FormatError::VersionConversion))
}
#[doc(hidden)]
fn extract_version_string(_version: &Self::VersionEnum) -> String {
"unknown".to_string()
}
fn to_format<F, T, E>(&self, serializer: F) -> Result<T, E>
where
F: FnOnce(&Self::VersionEnum) -> Result<T, E>,
{
let version = self.to_version();
serializer(&version)
}
}
#[derive(Debug)]
pub struct VersionConversionError {
pub version: String,
pub source: Box<dyn Error + Send + Sync + 'static>,
pub context: Option<String>,
}
impl VersionConversionError {
pub fn new(version: impl Into<String>, source: Box<dyn Error + Send + Sync + 'static>) -> Self {
Self {
version: version.into(),
source,
context: None,
}
}
pub fn with_context(
version: impl Into<String>,
source: Box<dyn Error + Send + Sync + 'static>,
context: impl Into<String>,
) -> Self {
Self {
version: version.into(),
source,
context: Some(context.into()),
}
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
}
impl Error for VersionConversionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.source.as_ref())
}
}
impl std::fmt::Display for VersionConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Failed to convert from version {}: {}",
self.version, self.source
)?;
if let Some(ref context) = self.context {
write!(f, " ({context})")?;
}
Ok(())
}
}
#[derive(Debug)]
pub enum FormatError<E> {
Deserialize {
error: E,
input: Option<String>,
},
VersionConversion(VersionConversionError),
}
impl<E: Error + Send + Sync + 'static> FormatError<E> {
pub const fn deserialize(error: E, input: Option<String>) -> Self {
Self::Deserialize { error, input }
}
pub fn version_conversion(
version: impl Into<String>,
source: Box<dyn Error + Send + Sync + 'static>,
) -> Self {
Self::VersionConversion(VersionConversionError::new(version, source))
}
pub const fn is_deserialize(&self) -> bool {
matches!(self, Self::Deserialize { .. })
}
pub const fn is_version_conversion(&self) -> bool {
matches!(self, Self::VersionConversion(_))
}
}
impl<E: Error + Send + Sync + 'static> Error for FormatError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Deserialize { error, .. } => Some(error),
Self::VersionConversion(e) => e.source(),
}
}
}
impl<E: Error + Send + Sync + 'static> std::fmt::Display for FormatError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Deserialize { error, input } => {
write!(f, "Deserialization error: {error}")?;
if let Some(input_str) = input {
if input_str.len() > 100 {
write!(f, " (input: {:?}...)", &input_str[..100])?;
} else {
write!(f, " (input: {input_str:?})")?;
}
}
Ok(())
}
Self::VersionConversion(e) => {
write!(f, "Version conversion error: {e}")
}
}
}
}