use std::{borrow::Cow, str::from_utf8};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use bounded_static_derive::ToStatic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
core::{impl_try_from, AString, IString},
error::{ValidationError, ValidationErrorKind},
mailbox::error::MailboxOtherError,
utils::indicators::is_list_char,
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "String"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct ListCharString<'a>(pub(crate) Cow<'a, str>);
impl<'a> ListCharString<'a> {
pub fn validate(value: impl AsRef<[u8]>) -> Result<(), ValidationError> {
let value = value.as_ref();
if value.is_empty() {
return Err(ValidationError::new(ValidationErrorKind::Empty));
}
if let Some(at) = value.iter().position(|b| !is_list_char(*b)) {
return Err(ValidationError::new(ValidationErrorKind::InvalidByteAt {
byte: value[at],
at,
}));
};
Ok(())
}
pub fn unvalidated<C>(inner: C) -> Self
where
C: Into<Cow<'a, str>>,
{
let inner = inner.into();
#[cfg(debug_assertions)]
Self::validate(inner.as_bytes()).unwrap();
Self(inner)
}
}
impl<'a> TryFrom<&'a str> for ListCharString<'a> {
type Error = ValidationError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::validate(value)?;
Ok(Self(Cow::Borrowed(value)))
}
}
impl<'a> TryFrom<String> for ListCharString<'a> {
type Error = ValidationError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::validate(&value)?;
Ok(Self(Cow::Owned(value)))
}
}
impl<'a> AsRef<[u8]> for ListCharString<'a> {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum ListMailbox<'a> {
Token(ListCharString<'a>),
String(IString<'a>),
}
impl<'a> TryFrom<&'a str> for ListMailbox<'a> {
type Error = ValidationError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
if s.is_empty() {
return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
}
if let Ok(lcs) = ListCharString::try_from(s) {
return Ok(ListMailbox::Token(lcs));
}
Ok(ListMailbox::String(s.try_into()?))
}
}
impl<'a> TryFrom<String> for ListMailbox<'a> {
type Error = ValidationError;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.is_empty() {
return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
}
if let Ok(lcs) = ListCharString::try_from(s.clone()) {
return Ok(ListMailbox::Token(lcs));
}
Ok(ListMailbox::String(s.try_into()?))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub enum Mailbox<'a> {
Inbox,
Other(MailboxOther<'a>),
}
impl_try_from!(AString<'a>, 'a, &'a [u8], Mailbox<'a>);
impl_try_from!(AString<'a>, 'a, Vec<u8>, Mailbox<'a>);
impl_try_from!(AString<'a>, 'a, &'a str, Mailbox<'a>);
impl_try_from!(AString<'a>, 'a, String, Mailbox<'a>);
impl<'a> From<AString<'a>> for Mailbox<'a> {
fn from(value: AString<'a>) -> Self {
match from_utf8(value.as_ref()) {
Ok(value) if value.to_ascii_lowercase() == "inbox" => Self::Inbox,
_ => Self::Other(MailboxOther::try_from(value).unwrap()),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "AString<'a>"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct MailboxOther<'a>(pub(crate) AString<'a>);
impl<'a> MailboxOther<'a> {
pub fn validate(value: impl AsRef<[u8]>) -> Result<(), MailboxOtherError> {
if value.as_ref().to_ascii_lowercase() == b"inbox" {
return Err(MailboxOtherError::Reserved);
}
Ok(())
}
pub fn inner(&self) -> &AString {
&self.0
}
pub fn unvalidated(value: AString<'a>) -> Self {
#[cfg(debug_assertions)]
Self::validate(&value).unwrap();
Self(value)
}
}
macro_rules! impl_try_from_mailbox_other {
($from:ty) => {
impl<'a> TryFrom<$from> for MailboxOther<'a> {
type Error = MailboxOtherError;
fn try_from(value: $from) -> Result<Self, Self::Error> {
let astring = AString::try_from(value)?;
Self::validate(&astring)?;
Ok(Self(astring))
}
}
};
}
impl_try_from_mailbox_other!(&'a [u8]);
impl_try_from_mailbox_other!(Vec<u8>);
impl_try_from_mailbox_other!(&'a str);
impl_try_from_mailbox_other!(String);
impl<'a> TryFrom<AString<'a>> for MailboxOther<'a> {
type Error = MailboxOtherError;
fn try_from(value: AString<'a>) -> Result<Self, Self::Error> {
Self::validate(&value)?;
Ok(Self(value))
}
}
impl<'a> AsRef<[u8]> for MailboxOther<'a> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
pub mod error {
use thiserror::Error;
use crate::error::ValidationError;
#[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
pub enum MailboxOtherError {
#[error(transparent)]
Literal(#[from] ValidationError),
#[error("Reserved: Please use one of the typed variants")]
Reserved,
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use super::*;
#[cfg(feature = "serde")]
use crate::core::AtomExt;
use crate::core::{AString, IString, Literal, LiteralMode};
#[test]
fn test_conversion_mailbox() {
let tests = [
("inbox", Mailbox::Inbox),
("inboX", Mailbox::Inbox),
("Inbox", Mailbox::Inbox),
("InboX", Mailbox::Inbox),
("INBOX", Mailbox::Inbox),
(
"INBO²",
Mailbox::Other(MailboxOther(AString::String(IString::Literal(Literal {
data: Cow::Borrowed("INBO²".as_bytes()),
mode: LiteralMode::Sync,
})))),
),
];
for (test, expected) in tests {
let got = Mailbox::try_from(test).unwrap();
assert_eq!(expected, got);
let got = Mailbox::try_from(String::from(test)).unwrap();
assert_eq!(expected, got);
}
}
#[test]
fn test_conversion_mailbox_failing() {
let tests = ["\x00", "A\x00", "\x00A"];
for test in tests {
assert!(Mailbox::try_from(test).is_err());
assert!(Mailbox::try_from(String::from(test)).is_err());
}
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialization_list_char_string() {
let valid_input = r#""OneWord""#;
let invalid_input = r#""Two Words""#;
let list_char_string = serde_json::from_str::<ListCharString>(valid_input)
.expect("valid input should deserialize successfully");
assert_eq!(list_char_string, ListCharString(Cow::Borrowed("OneWord")));
let err = serde_json::from_str::<ListCharString>(invalid_input)
.expect_err("invalid input should not deserialize successfully");
assert_eq!(
err.to_string(),
r"Validation failed: Invalid byte b'\x20' at index 3"
);
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialization_mailbox_other() {
let valid_input = r#"{ "Atom": "other" }"#;
let invalid_input = r#"{ "Atom": "inbox" }"#;
let mailbox_other = serde_json::from_str::<MailboxOther>(valid_input)
.expect("valid input should deserialize successfully");
assert_eq!(
mailbox_other,
MailboxOther(AString::Atom(AtomExt(Cow::Borrowed("other"))))
);
let err = serde_json::from_str::<MailboxOther>(invalid_input)
.expect_err("invalid input should not deserialize successfully");
assert_eq!(
err.to_string(),
r"Reserved: Please use one of the typed variants"
);
}
}