use std::fmt;
use std::sync::Arc;
use crate::error::Result;
pub trait Mod<Q> {
fn apply(self, q: &mut Q);
}
impl<Q> Mod<Q> for () {
fn apply(self, _q: &mut Q) {}
}
impl<Q, M: Mod<Q>> Mod<Q> for Option<M> {
fn apply(self, q: &mut Q) {
if let Some(m) = self {
m.apply(q);
}
}
}
impl<Q, M: Mod<Q>> Mod<Q> for Vec<M> {
fn apply(self, q: &mut Q) {
for m in self {
m.apply(q);
}
}
}
impl<Q, M: Mod<Q>, const N: usize> Mod<Q> for [M; N] {
fn apply(self, q: &mut Q) {
for m in self {
m.apply(q);
}
}
}
macro_rules! impl_mod_tuple {
($($name:ident),+) => {
#[allow(non_snake_case)]
impl<Q, $($name: Mod<Q>),+> Mod<Q> for ($($name,)+) {
fn apply(self, q: &mut Q) {
let ($($name,)+) = self;
$($name.apply(q);)+
}
}
};
}
impl_mod_tuple!(A);
impl_mod_tuple!(A, B);
impl_mod_tuple!(A, B, C);
impl_mod_tuple!(A, B, C, D);
impl_mod_tuple!(A, B, C, D, E);
impl_mod_tuple!(A, B, C, D, E, F);
impl_mod_tuple!(A, B, C, D, E, F, G);
impl_mod_tuple!(A, B, C, D, E, F, G, H);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub struct ModFn<F>(F);
pub fn mod_fn<F>(f: F) -> ModFn<F> {
ModFn(f)
}
impl<F> fmt::Debug for ModFn<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ModFn")
}
}
impl<Q, F: FnOnce(&mut Q)> Mod<Q> for ModFn<F> {
fn apply(self, q: &mut Q) {
(self.0)(q);
}
}
pub trait BuildMod<Q>: fmt::Debug + Send + Sync {
fn apply(&self, q: &mut Q) -> Result<()>;
}
impl<Q, T: BuildMod<Q> + ?Sized> BuildMod<Q> for Arc<T> {
fn apply(&self, q: &mut Q) -> Result<()> {
(**self).apply(q)
}
}
impl<Q, T: BuildMod<Q> + ?Sized> BuildMod<Q> for Box<T> {
fn apply(&self, q: &mut Q) -> Result<()> {
(**self).apply(q)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
type Q = Vec<&'static str>;
struct Push(&'static str);
impl Mod<Q> for Push {
fn apply(self, q: &mut Q) {
q.push(self.0);
}
}
fn applied<M: Mod<Q>>(m: M) -> Q {
let mut q = Q::new();
m.apply(&mut q);
q
}
#[test]
fn unit_applies_nothing() {
assert_eq!(applied(()), Vec::<&str>::new());
}
#[test]
fn tuples_apply_left_to_right() {
assert_eq!(applied((Push("a"),)), vec!["a"]);
assert_eq!(applied((Push("a"), Push("b"))), vec!["a", "b"]);
assert_eq!(
applied((Push("a"), Push("b"), Push("c"), Push("d"))),
vec!["a", "b", "c", "d"]
);
}
#[test]
fn tuples_reach_arity_sixteen() {
let q = applied((
Push("1"),
Push("2"),
Push("3"),
Push("4"),
Push("5"),
Push("6"),
Push("7"),
Push("8"),
Push("9"),
Push("10"),
Push("11"),
Push("12"),
Push("13"),
Push("14"),
Push("15"),
Push("16"),
));
assert_eq!(q.len(), 16);
assert_eq!(q.first(), Some(&"1"));
assert_eq!(q.last(), Some(&"16"));
}
#[test]
fn tuples_nest_so_arity_is_never_a_ceiling() {
assert_eq!(
applied((Push("a"), (Push("b"), (Push("c"), Push("d"))), Push("e"))),
vec!["a", "b", "c", "d", "e"]
);
}
#[test]
fn tuples_mix_mod_kinds() {
let q = applied((
Push("first"),
None::<Push>,
Some(Push("maybe")),
vec![Push("v1"), Push("v2")],
[Push("a1"), Push("a2")],
(),
mod_fn(|q: &mut Q| q.push("closure")),
));
assert_eq!(q, vec!["first", "maybe", "v1", "v2", "a1", "a2", "closure"]);
}
#[allow(clippy::unnecessary_lazy_evaluations)]
#[test]
fn option_is_how_conditionals_are_written() {
let admin = false;
assert_eq!(applied((!admin).then(|| Push("scoped"))), vec!["scoped"]);
let admin = true;
assert_eq!(
applied((!admin).then(|| Push("scoped"))),
Vec::<&str>::new()
);
}
#[test]
fn nested_options_collapse() {
assert_eq!(applied(Some(Some(Push("deep")))), vec!["deep"]);
assert_eq!(applied(Some(None::<Push>)), Vec::<&str>::new());
}
#[test]
fn empty_collections_apply_nothing() {
assert_eq!(applied(Vec::<Push>::new()), Vec::<&str>::new());
assert_eq!(applied([] as [Push; 0]), Vec::<&str>::new());
}
type BoxedMod = Box<dyn FnOnce(&mut Q)>;
#[test]
fn a_vec_of_erased_mods_is_a_mod() {
let mods: Vec<BoxedMod> = vec![
Box::new(|q: &mut Q| q.push("one")),
Box::new(|q: &mut Q| q.push("two")),
];
let mods: Vec<_> = mods.into_iter().map(mod_fn).collect();
assert_eq!(applied(mods), vec!["one", "two"]);
}
#[derive(Debug)]
struct AppendAtBuild(&'static str);
impl BuildMod<Q> for AppendAtBuild {
fn apply(&self, q: &mut Q) -> Result<()> {
q.push(self.0);
Ok(())
}
}
#[derive(Debug)]
struct Refuse;
impl BuildMod<Q> for Refuse {
fn apply(&self, _q: &mut Q) -> Result<()> {
Err(Error::Incomplete("a schema"))
}
}
#[test]
fn build_mods_run_repeatedly_and_can_fail() {
let mods: Vec<Arc<dyn BuildMod<Q>>> = vec![Arc::new(AppendAtBuild("once"))];
let mut first = Q::new();
let mut second = Q::new();
for m in &mods {
m.apply(&mut first).unwrap();
m.apply(&mut second).unwrap();
}
assert_eq!(first, vec!["once"]);
assert_eq!(
second,
vec!["once"],
"a build mod is not consumed by a build"
);
assert!(Refuse.apply(&mut first).is_err());
}
}