#[cfg(not(feature = "std"))]
use core::fmt::Display;
#[cfg(not(feature = "std"))]
use core::fmt::Formatter;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::fmt::Display;
#[cfg(feature = "std")]
use std::fmt::Formatter;
#[cfg(feature = "std")]
type FormatResult = std::fmt::Result;
#[cfg(not(feature = "std"))]
type FormatResult = core::fmt::Result;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum TextEncoding {
Ascii = 0,
#[default]
Utf8,
}
impl Display for TextEncoding {
fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
match self {
Self::Ascii => write!(f, "Ascii"),
Self::Utf8 => write!(f, "Utf8"),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum CommentSupport {
#[default]
Disallowed = 0,
Trailing,
Leading,
LeadingAndTrailing,
}
impl Display for CommentSupport {
fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
match self {
Self::Disallowed => write!(f, "Disallowed"),
Self::Trailing => write!(f, "Trailing"),
Self::Leading => write!(f, "Leading"),
Self::LeadingAndTrailing => write!(f, "LeadingAndTrailing"),
}
}
}
impl CommentSupport {
#[must_use]
#[inline]
pub(crate) fn trim(self, s: &str) -> &str {
match self {
Self::Disallowed => s,
Self::Trailing => {
if !s.ends_with(')') {
return s;
}
s.rfind('(').map_or(s, |pos| &s[..pos])
}
Self::Leading => {
if !s.starts_with('(') {
return s;
}
s.find(')').map_or(s, |pos| &s[pos + 1..])
}
Self::LeadingAndTrailing => Self::Leading.trim(Self::Trailing.trim(s)),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum DisplayNameSupport {
#[default]
Allowed,
Disallowed,
}
impl Display for DisplayNameSupport {
fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
match self {
Self::Allowed => write!(f, "Allowed"),
Self::Disallowed => write!(f, "Disallowed"),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum TrimWhitespace {
None = 0,
Start,
End,
#[default]
Both,
}
impl Display for TrimWhitespace {
fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
match self {
Self::None => write!(f, "None"),
Self::Start => write!(f, "Start"),
Self::End => write!(f, "End"),
Self::Both => write!(f, "Both"),
}
}
}
impl TrimWhitespace {
#[must_use]
#[inline]
pub fn trim_string<'a>(&self, s: &'a str) -> &'a str {
match self {
Self::None => s,
Self::Start => s.trim_start(),
Self::End => s.trim_end(),
Self::Both => s.trim(),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum QuotedSupport {
#[default]
Allowed = 0,
Disallowed,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum DomainSupport {
HostNameOnly = 0,
LocalAndHostName,
#[default]
IpAddressAndHostName,
All,
}
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone)]
#[non_exhaustive]
pub struct DomainValidationOptions {
pub(crate) domain_support: DomainSupport,
pub(crate) text_encoding: TextEncoding,
pub(crate) comments: CommentSupport,
pub(crate) trim_whitespace: TrimWhitespace,
pub(crate) max_length: usize,
pub(crate) max_dns_length: usize,
}
impl DomainValidationOptions {
#[must_use]
pub const fn new() -> Self {
Self {
domain_support: DomainSupport::IpAddressAndHostName,
text_encoding: TextEncoding::Utf8,
comments: CommentSupport::Disallowed,
trim_whitespace: TrimWhitespace::Both,
max_length: 255,
max_dns_length: 63,
}
}
}
impl Default for DomainValidationOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Default, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
#[non_exhaustive]
pub struct DomainValidationOptionsBuilder {
options: DomainValidationOptions,
}
impl DomainValidationOptionsBuilder {
#[must_use]
pub const fn new() -> Self {
Self {
options: DomainValidationOptions::new(),
}
}
#[must_use]
pub const fn with_text_encoding(mut self, encoding: TextEncoding) -> Self {
self.options.text_encoding = encoding;
self
}
#[must_use]
pub const fn with_domain_support(mut self, domain_support: DomainSupport) -> Self {
self.options.domain_support = domain_support;
self
}
#[must_use]
pub const fn with_trim_whitespace(mut self, trim_whitespace: TrimWhitespace) -> Self {
self.options.trim_whitespace = trim_whitespace;
self
}
#[must_use]
pub const fn with_max_length(mut self, max_length: usize) -> Self {
self.options.max_length = max_length;
self
}
#[must_use]
pub const fn with_max_dns_length(mut self, max_dns_length: usize) -> Self {
self.options.max_dns_length = max_dns_length;
self
}
#[must_use]
pub const fn build(self) -> DomainValidationOptions {
self.options
}
}
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone)]
#[non_exhaustive]
pub struct LocalPartValidationOptions {
pub(crate) text_encoding: TextEncoding,
pub(crate) quoted_support: QuotedSupport,
pub(crate) comments: CommentSupport,
pub(crate) trim_whitespace: TrimWhitespace,
pub(crate) max_length: usize,
}
impl LocalPartValidationOptions {
#[must_use]
pub const fn new() -> Self {
Self {
text_encoding: TextEncoding::Utf8,
quoted_support: QuotedSupport::Allowed,
comments: CommentSupport::Disallowed,
trim_whitespace: TrimWhitespace::Both,
max_length: 64,
}
}
}
impl Default for LocalPartValidationOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Clone)]
#[non_exhaustive]
pub struct EmailValidationOptions {
pub(crate) local_part_options: LocalPartValidationOptions,
pub(crate) domain_options: DomainValidationOptions,
pub(crate) display_name_support: DisplayNameSupport,
}
impl EmailValidationOptions {
#[must_use]
pub const fn new() -> Self {
Self {
local_part_options: LocalPartValidationOptions::new(),
domain_options: DomainValidationOptions::new(),
display_name_support: DisplayNameSupport::Allowed,
}
}
}
impl Default for EmailValidationOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Default, Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
#[non_exhaustive]
pub struct ValidationOptionsBuilder {
options: EmailValidationOptions,
}
impl ValidationOptionsBuilder {
#[must_use]
pub const fn new() -> Self {
Self {
options: EmailValidationOptions::new(),
}
}
#[must_use]
pub const fn with_domain_options(mut self, options: DomainValidationOptions) -> Self {
self.options.domain_options = options;
self
}
#[must_use]
pub const fn with_domain_support(mut self, domain_support: DomainSupport) -> Self {
self.options.domain_options.domain_support = domain_support;
self
}
#[must_use]
pub const fn with_display_name_support(
mut self,
display_name_support: DisplayNameSupport,
) -> Self {
self.options.display_name_support = display_name_support;
self
}
#[must_use]
pub const fn with_allow_quoted_strings(mut self, quoted_support: QuotedSupport) -> Self {
self.options.local_part_options.quoted_support = quoted_support;
self
}
#[must_use]
pub const fn with_comments(mut self, comments: CommentSupport) -> Self {
self.options.local_part_options.comments = comments;
self.options.domain_options.comments = comments;
self
}
#[must_use]
pub const fn with_text_encoding(mut self, encoding: TextEncoding) -> Self {
self.options.local_part_options.text_encoding = encoding;
self.options.domain_options.text_encoding = encoding;
self
}
#[must_use]
pub const fn with_trim_whitespace(mut self, trim_whitespace: TrimWhitespace) -> Self {
self.options.local_part_options.trim_whitespace = trim_whitespace;
self.options.domain_options.trim_whitespace = trim_whitespace;
self
}
#[must_use]
pub const fn with_max_domain_length(mut self, length: usize) -> Self {
self.options.domain_options.max_length = length;
self
}
#[must_use]
pub const fn with_max_local_part_length(mut self, length: usize) -> Self {
self.options.local_part_options.max_length = length;
self
}
#[must_use]
pub const fn with_max_dns_length(mut self, length: usize) -> Self {
self.options.domain_options.max_dns_length = length;
self
}
#[must_use]
pub const fn build(self) -> EmailValidationOptions {
self.options
}
}