litl 0.6.0

A JSON dialect with support for tagged binary data
Documentation
use std::{borrow::Cow, str::FromStr};

use thiserror::Error;

#[derive(Debug, Error)]
pub enum TaggedDataError {
    #[error("z-base-32 decoding error {0:?}")]
    ZBase32Error(zbase32::DecodeError),
    #[error("Expected a tag")]
    ExpectedTagPresent,
    #[error("Expected data in tagged data")]
    ExpectedDataPresent,
    #[error("Expected raw tagged data")]
    ExpectedRaw,
    #[error("Expected nested tagged data")]
    ExpectedNested,
    #[error("Expected specific tag")]
    WrongTag,
    #[error("Data error {0}")]
    DataError(Box<dyn std::error::Error>),
}

impl TaggedDataError {
    pub fn data_error<E: std::error::Error + 'static>(e: E) -> Self {
        Self::DataError(Box::new(e))
    }
}

pub trait SingleTaggedData {
    const TAG: &'static str;

    fn as_bytes(&self) -> Cow<'_, [u8]>;

    fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
    where
        Self: Sized;
}

#[macro_export]
macro_rules! impl_single_tagged_data_serde {
    ($t:ty) => {
        impl std::fmt::Display for $t {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(
                    f,
                    "{}_h{}",
                    <Self as SingleTaggedData>::TAG,
                    litl::zbase32::encode(SingleTaggedData::as_bytes(self).as_ref())
                )
            }
        }

        impl std::str::FromStr for $t {
            type Err = litl::TaggedDataError;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                let mut parts = s.splitn(2, "_h");
                let tag = parts
                    .next()
                    .ok_or(litl::TaggedDataError::ExpectedTagPresent)?;
                if tag != <Self as SingleTaggedData>::TAG {
                    return Err(litl::TaggedDataError::WrongTag);
                }
                let data_str = parts
                    .next()
                    .ok_or(litl::TaggedDataError::ExpectedDataPresent)?;
                let data = litl::zbase32::decode(data_str).map_err(litl::TaggedDataError::ZBase32Error)?;
                Self::from_bytes(&data)
            }
        }

        impl serde::Serialize for $t {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                serializer.serialize_str(&format!("{}", self))
            }
        }

        impl<'de> serde::Deserialize<'de> for $t {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                let s = String::deserialize(deserializer)?;
                std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
            }
        }
    };
}

pub trait NestedTaggedData {
    const TAG: &'static str;
    type Inner: ToString + FromStr<Err = TaggedDataError>;

    fn as_inner(&self) -> &Self::Inner;
    fn from_inner(inner: Self::Inner) -> Self
    where
        Self: Sized;
}

#[macro_export]
macro_rules! impl_nested_tagged_data_serde {
    ($t:ty) => {
        impl std::fmt::Display for $t {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(
                    f,
                    "{}_{}",
                    <Self as NestedTaggedData>::TAG,
                    self.as_inner().to_string()
                )
            }
        }

        impl std::str::FromStr for $t {
            type Err = litl::TaggedDataError;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                let mut parts = s.splitn(2, '_');
                let tag = parts
                    .next()
                    .ok_or(litl::TaggedDataError::ExpectedTagPresent)?;
                if tag != <Self as litl::NestedTaggedData>::TAG {
                    return Err(litl::TaggedDataError::WrongTag);
                }
                let inner_str = parts
                    .next()
                    .ok_or(litl::TaggedDataError::ExpectedDataPresent)?;
                let inner = <Self as litl::NestedTaggedData>::Inner::from_str(inner_str)?;
                Ok(Self::from_inner(inner))
            }
        }

        impl serde::Serialize for $t {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                serializer.serialize_str(&format!("{}", self))
            }
        }

        impl<'de> serde::Deserialize<'de> for $t {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                let s = String::deserialize(deserializer)?;
                std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
            }
        }
    };
}

#[macro_export]
macro_rules! impl_serde_enum_of_distinct_tagged_values {
    ($enum:ty, $($prefix:expr => ($variant:ident, $inner:ident)),+) => {
        impl std::fmt::Display for $enum {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    $(
                        Self::$variant(inner) => inner.fmt(f),
                    )+
                }
            }
        }

        impl std::str::FromStr for $enum {
            type Err = litl::TaggedDataError;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                $(
                    if s.starts_with($prefix) {
                        let inner = <$inner as std::str::FromStr>::from_str(s)?;
                        return Ok(Self::$variant(inner));
                    }
                )+
                Err(litl::TaggedDataError::WrongTag)
            }
        }

        impl serde::Serialize for $enum {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                serializer.serialize_str(&format!("{}", self))
            }
        }

        impl<'de> serde::Deserialize<'de> for $enum {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                let s = String::deserialize(deserializer)?;
                std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
            }
        }
    };
}