macro_rules! impl_identifier_traits {
($ty:ident, $expecting:expr) => {
impl_identifier_traits!($ty, $expecting, field = 0);
};
($ty:ident, $expecting:expr, field = $field:tt) => {
impl TryFrom<String> for $ty {
type Error = $crate::error::IdentifierError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::new(&s)
}
}
impl TryFrom<&str> for $ty {
type Error = $crate::error::IdentifierError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::new(s)
}
}
impl AsRef<str> for $ty {
#[inline]
fn as_ref(&self) -> &str {
&self.$field
}
}
impl std::borrow::Borrow<str> for $ty {
#[inline]
fn borrow(&self) -> &str {
&self.$field
}
}
impl std::ops::Deref for $ty {
type Target = str;
#[inline]
fn deref(&self) -> &str {
&self.$field
}
}
impl std::fmt::Display for $ty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.$field)
}
}
impl std::str::FromStr for $ty {
type Err = $crate::error::IdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl From<$ty> for String {
fn from(id: $ty) -> String {
String::from(id.$field)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for $ty {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.$field)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = $ty;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str($expecting)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<$ty, E> {
$ty::new(v).map_err(|e| {
$crate::identifiers::trace_identifier_deser_error(
stringify!($ty),
v,
&e,
);
serde::de::Error::custom(e)
})
}
}
d.deserialize_str(Visitor)
}
}
};
}
macro_rules! bdew_ascii_identifier {
(
$(#[$meta:meta])*
$ty:ident,
prefix = $prefix:expr,
schema = $schema_fn:literal,
schema_meta = $schema_meta:expr,
pattern = $pattern:literal,
expecting = $expecting:expr,
example = ($example_base:literal, $example_full:literal),
check_fn = $check_fn:ident $(,)?
) => {
$(#[$meta])*
#[doc = concat!("use rubo4e::identifiers::", stringify!($ty), ";")]
#[doc = concat!("let id = ", stringify!($ty), "::new(\"", $example_full, "\").unwrap();")]
#[doc = concat!("assert_eq!(id.to_string(), \"", $example_full, "\");")]
#[doc = concat!("let id = ", stringify!($ty), "::from_base(\"", $example_base, "\").unwrap();")]
#[doc = concat!("assert_eq!(id.as_ref(), \"", $example_full, "\");")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "validate", derive(garde::Validate))]
#[cfg_attr(feature = "validate", garde(allow_unvalidated))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(schema_with = $schema_fn))]
#[cfg_attr(feature = "schemars", schemars(description = $schema_meta.description))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(
value_type = String,
pattern = $pattern,
example = $example_full,
description = $schema_meta.description
))]
pub struct $ty(#[cfg_attr(feature = "validate", garde(custom($check_fn)))] Box<str>);
#[cfg(feature = "validate")]
fn $check_fn(value: &str, _: &()) -> Result<(), garde::Error> {
$crate::identifiers::checksum::validate_ascii_id(value, $prefix)
.map_err(garde::Error::from)
}
impl $ty {
pub const CODETYP: &'static str = match std::str::from_utf8($prefix) {
Ok(s) => s,
Err(_) => panic!("Codetyp prefix must be valid UTF-8"),
};
#[doc = concat!("Creates a new `", stringify!($ty), "` after full validation.")]
#[doc = concat!("- [`IdentifierError::InvalidFormat`] if `s` does not start with `\"", $example_base, "\"`'s Codetyp.")]
#[must_use = "the validated identifier is returned; ignoring it discards the result"]
pub fn new(s: &str) -> Result<Self, $crate::error::IdentifierError> {
$crate::identifiers::checksum::validate_ascii_id(s, $prefix)?;
Ok(Self(Box::from(s)))
}
#[doc = concat!("Builds a `", stringify!($ty), "` from its 10-character base by computing")]
pub fn from_base(base: &str) -> Result<Self, $crate::error::IdentifierError> {
let full =
$crate::identifiers::checksum::compute_ascii_id_from_base(base, $prefix)?;
Ok(Self(full.into_boxed_str()))
}
pub fn check_digit(base: &str) -> Result<u8, $crate::error::IdentifierError> {
let full =
$crate::identifiers::checksum::compute_ascii_id_from_base(base, $prefix)?;
Ok(full.as_bytes()[10] - b'0')
}
#[must_use]
pub fn base(&self) -> &str {
&self.0[..10]
}
}
impl_identifier_traits!($ty, $expecting);
};
}
macro_rules! eic_restricted_identifier {
(
$(#[$meta:meta])*
$ty:ident,
eic_type = $eic_type:expr,
schema = $schema_fn:literal,
schema_meta = $schema_meta:expr,
pattern = $pattern:literal,
expecting = $expecting:expr,
example = $example:literal,
check_fn = $check_fn:ident $(,)?
) => {
$(#[$meta])*
#[doc = concat!("use rubo4e::identifiers::{", stringify!($ty), ", EicCode, EicType};")]
#[doc = concat!("let id = ", stringify!($ty), "::new(\"", $example, "\").unwrap();")]
#[doc = concat!("assert_eq!(id.to_string(), \"", $example, "\");")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::EIC_TYPE, ", stringify!($eic_type), ");")]
#[doc = concat!("assert_eq!(eic.eic_type(), ", stringify!($eic_type), ");")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "validate", derive(garde::Validate))]
#[cfg_attr(feature = "validate", garde(allow_unvalidated))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(schema_with = $schema_fn))]
#[cfg_attr(feature = "schemars", schemars(description = $schema_meta.description))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(
value_type = String,
pattern = $pattern,
example = $example,
description = $schema_meta.description
))]
pub struct $ty(#[cfg_attr(feature = "validate", garde(custom($check_fn)))] Box<str>);
#[cfg(feature = "validate")]
fn $check_fn(value: &str, _: &()) -> Result<(), garde::Error> {
$ty::validate(value).map_err(garde::Error::from)
}
impl $ty {
pub const EIC_TYPE: $crate::identifiers::EicType = $eic_type;
fn validate(s: &str) -> Result<(), $crate::error::IdentifierError> {
let eic = $crate::identifiers::EicCode::new(s)?;
if eic.eic_type() != Self::EIC_TYPE {
return Err($crate::error::IdentifierError::InvalidFormat {
description: format!(
"{} requires EIC object type '{}' ({}) at position 3, found '{}' ({})",
stringify!($ty),
Self::EIC_TYPE.as_char(),
Self::EIC_TYPE.description(),
eic.type_char(),
eic.eic_type().description(),
)
.into(),
});
}
Ok(())
}
#[doc = concat!("Creates a new `", stringify!($ty), "` after full EIC validation,")]
#[doc = concat!("requiring object type `", stringify!($eic_type), "` at position 3.")]
#[must_use = "the validated identifier is returned; ignoring it discards the result"]
pub fn new(s: &str) -> Result<Self, $crate::error::IdentifierError> {
Self::validate(s)?;
Ok(Self(Box::from(s)))
}
#[doc = concat!("Builds a `", stringify!($ty), "` from its 15-character prefix by")]
pub fn from_prefix(prefix: &str) -> Result<Self, $crate::error::IdentifierError> {
let full = $crate::identifiers::EicCode::complete_prefix(prefix)?;
Self::new(&full)
}
#[must_use]
pub fn to_eic_code(&self) -> $crate::identifiers::EicCode {
$crate::identifiers::EicCode::new(&self.0)
.expect(concat!(stringify!($ty), " is always a valid EicCode"))
}
}
impl From<$ty> for $crate::identifiers::EicCode {
fn from(id: $ty) -> Self {
id.to_eic_code()
}
}
impl TryFrom<$crate::identifiers::EicCode> for $ty {
type Error = $crate::error::IdentifierError;
fn try_from(eic: $crate::identifiers::EicCode) -> Result<Self, Self::Error> {
Self::new(eic.as_ref())
}
}
impl_identifier_traits!($ty, $expecting);
};
}