use crate::message::{TryFromByteError, TryFromIndexError};
use std::{
collections::HashMap,
ffi::{CStr, CString},
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum MacroStage {
Connect,
Helo,
Mail,
Rcpt,
Data,
Eoh,
Eom,
}
impl MacroStage {
pub fn all() -> impl DoubleEndedIterator<Item = Self> {
use MacroStage::*;
[Connect, Helo, Mail, Rcpt, Data, Eoh, Eom].into_iter()
}
pub fn all_sorted_by_index() -> impl DoubleEndedIterator<Item = Self> {
use MacroStage::*;
[Connect, Helo, Mail, Rcpt, Data, Eom, Eoh].into_iter()
}
}
impl From<MacroStage> for i32 {
fn from(stage: MacroStage) -> Self {
match stage {
MacroStage::Connect => 0,
MacroStage::Helo => 1,
MacroStage::Mail => 2,
MacroStage::Rcpt => 3,
MacroStage::Data => 4,
MacroStage::Eoh => 6,
MacroStage::Eom => 5,
}
}
}
impl TryFrom<i32> for MacroStage {
type Error = TryFromIndexError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Connect),
1 => Ok(Self::Helo),
2 => Ok(Self::Mail),
3 => Ok(Self::Rcpt),
4 => Ok(Self::Data),
6 => Ok(Self::Eoh),
5 => Ok(Self::Eom),
value => Err(TryFromIndexError::new(value)),
}
}
}
impl From<MacroStage> for u8 {
fn from(stage: MacroStage) -> Self {
match stage {
MacroStage::Connect => b'C',
MacroStage::Helo => b'H',
MacroStage::Mail => b'M',
MacroStage::Rcpt => b'R',
MacroStage::Data => b'T',
MacroStage::Eoh => b'N',
MacroStage::Eom => b'E',
}
}
}
impl TryFrom<u8> for MacroStage {
type Error = TryFromByteError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
b'C' => Ok(Self::Connect),
b'H' => Ok(Self::Helo),
b'M' => Ok(Self::Mail),
b'R' => Ok(Self::Rcpt),
b'T' => Ok(Self::Data),
b'N' => Ok(Self::Eoh),
b'E' => Ok(Self::Eom),
value => Err(TryFromByteError::new(value)),
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct Macros(HashMap<MacroStage, HashMap<CString, CString>>);
impl Macros {
pub(crate) fn new() -> Self {
Self(
MacroStage::all()
.map(|stage| (stage, Default::default()))
.collect(),
)
}
pub(crate) fn clone_internal(&self) -> Self {
Self(self.0.clone())
}
pub fn get(&self, name: &CStr) -> Option<&CStr> {
let alt_name;
let alt_name = match *name.to_bytes() {
[x] => {
alt_name = [b'{', x, b'}', 0];
Some(CStr::from_bytes_with_nul(&alt_name[..4]).unwrap())
}
[b'{', x, b'}'] => {
alt_name = [x, 0, 0, 0];
Some(CStr::from_bytes_with_nul(&alt_name[..2]).unwrap())
}
_ => None,
};
MacroStage::all().rev().find_map(|stage| {
let m = &self.0[&stage];
m.get(name)
.or_else(|| alt_name.and_then(|name| m.get(name)))
.map(|v| v.as_ref())
})
}
pub fn to_hash_map(&self) -> HashMap<CString, CString> {
MacroStage::all()
.flat_map(|stage| self.0[&stage].clone())
.collect()
}
pub(crate) fn insert(&mut self, stage: MacroStage, entries: HashMap<CString, CString>) {
self.0.insert(stage, entries);
}
pub(crate) fn clear(&mut self) {
for s in MacroStage::all() {
self.0.get_mut(&s).unwrap().clear();
}
}
pub(crate) fn clear_after(&mut self, stage: MacroStage) {
for s in MacroStage::all().rev().take_while(|&s| s != stage) {
self.0.get_mut(&s).unwrap().clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use byte_strings::c_str;
#[test]
fn macro_lookup() {
let mut macros = Macros::new();
macros.insert(
MacroStage::Connect,
HashMap::from([
(c_str!("a").into(), c_str!("b").into()),
(c_str!("{x}").into(), c_str!("y").into()),
(c_str!("{name}").into(), c_str!("value").into()),
]),
);
assert_eq!(macros.get(c_str!("n")), None);
assert_eq!(macros.get(c_str!("{none}")), None);
assert_eq!(macros.get(c_str!("{name}")), Some(c_str!("value")));
assert_eq!(macros.get(c_str!("a")), Some(c_str!("b")));
assert_eq!(macros.get(c_str!("{a}")), Some(c_str!("b")));
assert_eq!(macros.get(c_str!("x")), Some(c_str!("y")));
assert_eq!(macros.get(c_str!("{x}")), Some(c_str!("y")));
}
#[test]
fn to_hash_map_ok() {
let mut macros = Macros::new();
macros.insert(
MacroStage::Connect,
HashMap::from([
(c_str!("{name1}").into(), c_str!("connect1").into()),
(c_str!("{name2}").into(), c_str!("connect2").into()),
]),
);
macros.insert(
MacroStage::Mail,
HashMap::from([
(c_str!("{name2}").into(), c_str!("mail2").into()),
(c_str!("{name3}").into(), c_str!("mail3").into()),
]),
);
assert_eq!(
macros.to_hash_map(),
HashMap::from([
(c_str!("{name1}").into(), c_str!("connect1").into()),
(c_str!("{name2}").into(), c_str!("mail2").into()),
(c_str!("{name3}").into(), c_str!("mail3").into()),
]),
);
macros.clear_after(MacroStage::Helo);
assert_eq!(
macros.to_hash_map(),
HashMap::from([
(c_str!("{name1}").into(), c_str!("connect1").into()),
(c_str!("{name2}").into(), c_str!("connect2").into()),
]),
);
}
}