ranges 0.4.0

This crate provides a generic alternative to core/std ranges, set-operations to work with them and a range set that can efficiently store them with the least amount of memory possible.
Documentation
#![allow(box_pointers, clippy::tests_outside_test_module, clippy::std_instead_of_core)]

use std::{
    convert::{TryFrom, TryInto},
    error::Error,
    ops::{Bound, Range, RangeBounds, RangeInclusive},
};

use ranges::{Domain, GenericIterator, GenericRange, Iterable};

#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct TwoDigitInt(u8);

impl TwoDigitInt {
    const MIN: Self = Self(10);
    const MAX: Self = Self(99);
}

impl TryFrom<u8> for TwoDigitInt {
    type Error = Box<dyn Error>;

    fn try_from(val: u8) -> Result<Self, Self::Error> {
        if &val < Self::MIN.as_ref() || Self::MAX.as_ref() < &val {
            Err("input out of bounds".into())
        } else {
            Ok(Self(val))
        }
    }
}

impl AsRef<u8> for TwoDigitInt {
    fn as_ref(&self) -> &u8 {
        &self.0
    }
}

impl Domain for TwoDigitInt {
    const DISCRETE: bool = true;

    fn minimum() -> Bound<Self> {
        Bound::Included(Self::MIN)
    }

    fn maximum() -> Bound<Self> {
        Bound::Included(Self::MAX)
    }

    fn predecessor(&self) -> Option<Self> {
        (self.as_ref() - 1).try_into().ok()
    }

    fn successor(&self) -> Option<Self> {
        (self.as_ref() + 1).try_into().ok()
    }
}

impl Iterable for TwoDigitInt {
    type Output = Self;

    fn next(&self) -> Option<Self::Output> {
        self.successor()
    }
}

fn try_map_bound<F, T, U, E>(bound: Bound<T>, f: F) -> Result<Bound<U>, E>
where
    F: FnOnce(T) -> Result<U, E>,
{
    match bound {
        Bound::Included(t) => Ok(Bound::Included(f(t)?)),
        Bound::Excluded(t) => Ok(Bound::Excluded(f(t)?)),
        Bound::Unbounded => Ok(Bound::Unbounded),
    }
}

trait RangeBoundsExt<T>: RangeBounds<T>
where
    T: Clone,
{
    fn try_map<F, U, E>(&self, f: F) -> Result<(Bound<U>, Bound<U>), E>
    where
        F: FnOnce(T) -> Result<U, E> + Clone,
    {
        Ok((
            try_map_bound(self.start_bound().cloned(), f.clone())?,
            try_map_bound(self.end_bound().cloned(), f)?,
        ))
    }
}

impl<T: Clone, B: RangeBounds<T>> RangeBoundsExt<T> for B {}

struct TwoDigitIntRange(GenericRange<TwoDigitInt>);

impl TwoDigitIntRange {
    fn from<R: TryInto<Self>>(range: R) -> Result<Self, R::Error> {
        range.try_into()
    }
}

macro_rules! impl_try_from {
    ( $range_ty:ident ) => {
        impl<T> TryFrom<$range_ty<T>> for TwoDigitIntRange
        where
            T: TryInto<TwoDigitInt, Error = Box<dyn Error>> + Clone,
        {
            type Error = <T as TryInto<TwoDigitInt>>::Error;

            fn try_from(range: $range_ty<T>) -> Result<Self, Self::Error> {
                Ok(Self(range.try_map(|t| t.try_into())?.into()))
            }
        }
    };
}

impl_try_from!(Range);
impl_try_from!(RangeInclusive);

impl IntoIterator for TwoDigitIntRange {
    type Item = TwoDigitInt;
    type IntoIter = TwoDigitIntRangeIter;

    fn into_iter(self) -> Self::IntoIter {
        TwoDigitIntRangeIter(self.0.into_iter())
    }
}

struct TwoDigitIntRangeIter(GenericIterator<TwoDigitInt>);

impl Iterator for TwoDigitIntRangeIter {
    type Item = TwoDigitInt;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

#[test]
fn empty() {
    let mut iter = TwoDigitIntRange::from(50..50)
        .unwrap()
        .into_iter()
        .map(|i| i.as_ref().to_owned());
    assert_eq!(iter.next(), None);
}

#[test]
fn open() {
    let mut iter = TwoDigitIntRange::from(10..12)
        .unwrap()
        .into_iter()
        .map(|i| i.as_ref().to_owned());
    assert_eq!(iter.next(), Some(10));
    assert_eq!(iter.next(), Some(11));
    assert_eq!(iter.next(), None);
}

#[test]
fn closed() {
    let mut iter = TwoDigitIntRange::from(97..=99)
        .unwrap()
        .into_iter()
        .map(|i| i.as_ref().to_owned());
    assert_eq!(iter.next(), Some(97));
    assert_eq!(iter.next(), Some(98));
    assert_eq!(iter.next(), Some(99));
    assert_eq!(iter.next(), None);
}

#[test]
#[should_panic(expected = "Result::unwrap()` on an `Err` value: \"input out of bounds\"")]
fn invalid() {
    TwoDigitIntRange::from(5..15).unwrap();
}