macro_rules! str_check {
($value:expr, $max_allowed_chars:expr) => {{
let value = $value;
let max_allowed_chars = $max_allowed_chars;
if !value.is_ascii() {
Some(format!("value {} has some non-ascii characters", value))
} else {
let num_chars = value.len();
if num_chars == 0 {
Some("value has zero characters".into())
} else if num_chars > max_allowed_chars {
Some(format!(
"value {} has {} chars which is greater than maximum allowed {}",
value, num_chars, max_allowed_chars
))
} else {
None
}
}
}};
}
macro_rules! str_id {
($B:ident, $O:ident, $MAX:ident, $err_func:ident) => {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct $B {
inner: str,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $O {
inner: String,
}
impl $B {
pub fn new(value: &str) -> $crate::result::DmResult<&$B> {
if let Some(err_msg) = str_check!(value, $MAX - 1) {
return Err($err_func(&err_msg));
}
Ok(unsafe { &*(value as *const str as *const $B) })
}
pub fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
}
impl ToOwned for $B {
type Owned = $O;
fn to_owned(&self) -> $O {
$O {
inner: self.inner.to_owned(),
}
}
}
impl std::fmt::Display for $B {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.inner)
}
}
impl $O {
pub fn new(value: String) -> $crate::result::DmResult<$O> {
if let Some(err_msg) = str_check!(&value, $MAX - 1) {
return Err($err_func(&err_msg));
}
Ok($O { inner: value })
}
}
impl AsRef<$B> for $O {
fn as_ref(&self) -> &$B {
self
}
}
impl std::borrow::Borrow<$B> for $O {
fn borrow(&self) -> &$B {
self.deref()
}
}
impl std::ops::Deref for $O {
type Target = $B;
fn deref(&self) -> &$B {
$B::new(&self.inner).expect("inner satisfies all correctness criteria for $B::new")
}
}
};
}
#[cfg(test)]
mod tests {
use std::ops::Deref;
use crate::{core::errors::Error, result::DmError};
fn err_func(err_msg: &str) -> DmError {
DmError::Core(Error::InvalidArgument(err_msg.into()))
}
const TYPE_LEN: usize = 12;
str_id!(Id, IdBuf, TYPE_LEN, err_func);
#[test]
fn test_empty_name() {
assert_matches!(Id::new(""), Err(DmError::Core(Error::InvalidArgument(_))));
assert_matches!(
IdBuf::new("".into()),
Err(DmError::Core(Error::InvalidArgument(_)))
);
}
#[test]
fn test_too_long_name() {
let name = "a".repeat(TYPE_LEN + 1);
assert_matches!(
Id::new(&name),
Err(DmError::Core(Error::InvalidArgument(_)))
);
assert_matches!(
IdBuf::new(name),
Err(DmError::Core(Error::InvalidArgument(_)))
);
}
#[test]
fn test_interface() {
let id = Id::new("id").expect("is valid id");
let id_buf = IdBuf::new("id".into()).expect("is valid id");
assert_eq!(id.as_bytes(), &[105u8, 100u8]);
assert_eq!(id_buf.as_bytes(), &[105u8, 100u8]);
assert_eq!(id.to_owned(), id_buf);
assert_eq!(id.to_string(), (*id).to_string());
assert_eq!(id_buf.to_string(), (*id_buf).to_string());
assert_eq!(id_buf.deref(), id);
assert_eq!(*id_buf, *id);
}
}