emailaddr 0.1.1

Type-safe, zero-copy, validated email addresses with generic storage, IDNA domain support, and no_std/no-alloc compatibility.
Documentation
use arbitrary::{Arbitrary, Result, Unstructured};

use std::{borrow::Cow, boxed::Box, rc::Rc, string::String, sync::Arc, vec::Vec};

use crate::{Buffer, EmailAddr};

const ATEXT: &[u8] =
  b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~";

fn arbitrary_atext(u: &mut Unstructured<'_>) -> Result<char> {
  let idx = u.int_in_range(0..=ATEXT.len() - 1)?;
  Ok(ATEXT[idx] as char)
}

fn arbitrary_alnum(u: &mut Unstructured<'_>) -> Result<char> {
  let idx = u.int_in_range(0..=61)?;
  Ok(match idx {
    0..=25 => (b'a' + idx as u8) as char,
    26..=51 => (b'A' + (idx as u8 - 26)) as char,
    _ => (b'0' + (idx as u8 - 52)) as char,
  })
}

fn arbitrary_label_char(u: &mut Unstructured<'_>) -> Result<char> {
  if u.arbitrary()? {
    arbitrary_alnum(u)
  } else {
    Ok('-')
  }
}

fn arbitrary_local(u: &mut Unstructured<'_>) -> Result<String> {
  let segment_count: usize = u.int_in_range(1..=3)?;
  let mut local = String::new();

  for segment in 0..segment_count {
    if segment > 0 {
      local.push('.');
    }

    let len: usize = u.int_in_range(1..=12)?;
    for _ in 0..len {
      local.push(arbitrary_atext(u)?);
    }
  }

  Ok(local)
}

fn arbitrary_domain(u: &mut Unstructured<'_>) -> Result<String> {
  let label_count: usize = u.int_in_range(1..=3)?;
  let mut domain = String::new();

  for label in 0..label_count {
    if label > 0 {
      domain.push('.');
    }

    let len: usize = u.int_in_range(1..=20)?;
    domain.push(arbitrary_alnum(u)?);

    for _ in 1..len.saturating_sub(1) {
      domain.push(arbitrary_label_char(u)?);
    }

    if len > 1 {
      domain.push(arbitrary_alnum(u)?);
    }
  }

  Ok(domain)
}

fn arbitrary_email(u: &mut Unstructured<'_>) -> Result<EmailAddr<Buffer>> {
  let mut email = arbitrary_local(u)?;
  email.push('@');
  email.push_str(&arbitrary_domain(u)?);
  EmailAddr::<Buffer>::try_from(email.as_str()).map_err(|_| arbitrary::Error::IncorrectFormat)
}

macro_rules! impl_arbitrary_from_buffer {
  ($($ty:ty),+ $(,)?) => {
    $(
      impl<'a> Arbitrary<'a> for EmailAddr<$ty> {
        fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
          arbitrary_email(u).map(|addr| EmailAddr::from_inner(addr.storage.into()))
        }
      }
    )+
  };
}

impl<'a> Arbitrary<'a> for EmailAddr<Buffer> {
  fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
    arbitrary_email(u)
  }
}

impl_arbitrary_from_buffer!(
  String,
  Box<str>,
  Rc<str>,
  Arc<str>,
  Vec<u8>,
  Box<[u8]>,
  Rc<[u8]>,
  Arc<[u8]>,
  Cow<'a, str>,
  Cow<'a, [u8]>,
);

#[cfg(feature = "smol_str_0_3")]
impl_arbitrary_from_buffer!(smol_str_0_3::SmolStr);

#[cfg(feature = "bytes_1")]
impl_arbitrary_from_buffer!(bytes_1::Bytes);

#[cfg(feature = "tinyvec_1")]
impl<'a, const N: usize> Arbitrary<'a> for EmailAddr<tinyvec_1::TinyVec<[u8; N]>> {
  fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
    arbitrary_email(u).map(|addr| EmailAddr::from_inner(addr.storage.into()))
  }
}

#[cfg(feature = "smallvec_1")]
impl<'a, const N: usize> Arbitrary<'a> for EmailAddr<smallvec_1::SmallVec<[u8; N]>> {
  fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
    arbitrary_email(u).map(|addr| EmailAddr::from_inner(addr.storage.into()))
  }
}

#[cfg(feature = "triomphe_0_1")]
const _: () = {
  use triomphe_0_1::Arc;

  impl_arbitrary_from_buffer!(Arc<str>, Arc<[u8]>);
};