cri-ref 0.0.2

Embedded-friendly equivalents of URIs
Documentation
use super::{Cri, CriBase, CriRef};
use crate::characterclasses::{PATH_UE, QUERY_UE, FRAGMENT_UE, HOST_UE};
use crate::{traits, accessor};

/// A resolved CRI computed from a base and a reference on demand
#[derive(Debug)]
pub struct RuntimeResolved<'a, B: Cri + ?Sized, R: CriRef> {
    pub(super) base: &'a B,
    pub(super) reference: R,
}

// That's a whole lot of implementation stuff to basically tell the type system not to worry and
// take either ... is there a crate to derive all of these?

#[derive(Clone)]
pub enum Either<B, R> {
    FromBase(B),
    FromReference(R),
}

// Generally we can do everything on the same generic, but Iterator and IntoIterator would overlap
// with generic impls, so occasionally we flip over
//
// Conveniently, we need a phantom parameter there as well anyway.
#[derive(Clone)]
pub enum Either2<B, R, const U: crate::characterclasses::AsciiSet> {
    FromBase(B),
    FromReference(R),
}

// ... but then again it turns out if we take the phantom parameter, we need them on two different
// Either again
#[derive(Clone)]
pub enum Either3<B, R, const U: crate::characterclasses::AsciiSet> {
    FromBase(B),
    FromReference(R),
}

impl<B: traits::Scheme, R: traits::Scheme> traits::Scheme for Either<B, R> {
    fn to_cri_id(&self) -> Option<i16> {
        match self {
            Either::FromBase(b) => b.to_cri_id(),
            Either::FromReference(r) => r.to_cri_id(),
        }
    }

    fn to_text_scheme(&self) -> &str {
        match self {
            Either::FromBase(b) => b.to_text_scheme(),
            Either::FromReference(r) => r.to_text_scheme(),
        }
    }
}

impl<B: core::fmt::Display, R: core::fmt::Display> core::fmt::Display for Either<B, R> {
    fn fmt(&self, w: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
        match self {
            Either::FromBase(b) => b.fmt(w),
            Either::FromReference(r) => r.fmt(w),
        }
    }
}

impl<const U: crate::characterclasses::AsciiSet, B: traits::TextOrPet<U>, R: traits::TextOrPet<U>> traits::TextOrPet<U> for Either<B, R> {
    type UriEncoded<'a> = impl core::fmt::Display where Self: 'a;
    fn to_uri_component(&self) -> Self::UriEncoded<'_> {
        match self {
            Either::FromBase(b) => Either::FromBase(b.to_uri_component()),
            Either::FromReference(r) => Either::FromReference(r.to_uri_component()),
        }
    }
}

impl<const U: crate::characterclasses::AsciiSet, BI: traits::TextOrPet<U>, B: Iterator<Item=BI>, RI: traits::TextOrPet<U>, R: Iterator<Item=RI>> Iterator for Either2<B, R, U> {
    type Item = Either<BI, RI>;
    fn next(&mut self) -> Option<Self::Item> {
        Some(match self {
            Either2::FromBase(b) => Either::FromBase(b.next()?),
            Either2::FromReference(r) => Either::FromReference(r.next()?),
        })
    }
}

impl<const U: crate::characterclasses::AsciiSet, BI: traits::TextOrPet<HOST_UE>, B: Clone + IntoIterator<Item=BI>, RI: traits::TextOrPet<HOST_UE>, R: Clone + IntoIterator<Item=RI>> IntoIterator for Either3<B, R, U> {
    type Item = Either<BI, RI>;
    type IntoIter = Either2<B::IntoIter, R::IntoIter, HOST_UE>;

    fn into_iter(self) -> <Self as IntoIterator>::IntoIter {
        match self {
            Either3::FromBase(b) => Either2::FromBase(b.into_iter()),
            Either3::FromReference(r) => Either2::FromReference(r.into_iter()),
        }
    }
}

impl<B: traits::Host, R: traits::Host> traits::Host for Either<B, R> {
    type HostItem<'a> = Either<B::HostItem<'a>, R::HostItem<'a>> where Self: 'a;
    type HostIter<'a> = Either3<B::HostIter<'a>, R::HostIter<'a>, HOST_UE> where Self: 'a;

    fn as_ref(&self) -> traits::HostRef<'_, Self::HostItem<'_>, Self::HostIter<'_>> {
        match self {
            Either::FromBase(b) => match b.as_ref() {
                traits::HostRef::IPv4(a) => traits::HostRef::IPv4(a),
                traits::HostRef::IPv6 { address, zone } => traits::HostRef::IPv6 { address, zone },
                traits::HostRef::Hostname(iter) => {
                    traits::HostRef::Hostname(Either3::FromBase(iter))
                }
            },
            Either::FromReference(r) => match r.as_ref() {
                traits::HostRef::IPv4(a) => traits::HostRef::IPv4(a),
                traits::HostRef::IPv6 { address, zone } => traits::HostRef::IPv6 { address, zone },
                traits::HostRef::Hostname(iter) => {
                    traits::HostRef::Hostname(Either3::FromReference(iter))
                }
            },
        }
    }
}

// Workaround for https://github.com/rust-lang/rust/issues/34433
struct Chain<I, A: Iterator<Item=I>, B: Iterator<Item=I>> {
    first: Option<A>,
    second: B,
}

impl<I, A: Iterator<Item=I>, B: Iterator<Item=I>> Iterator for Chain<I, A, B> {
    type Item = I;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(item) = self.first.as_mut().and_then(|f| f.next()) {
            Some(item)
        } else {
            self.first = None;
            self.second.next()
        }
    }
}

impl<I, A: ExactSizeIterator + Iterator<Item=I>, B: ExactSizeIterator + Iterator<Item=I>> ExactSizeIterator for Chain<I, A, B> {
    fn len(&self) -> usize {
        self.first.as_ref().map(|f| f.len()).unwrap_or(0) + self.second.len()
    }
}

impl<'a, B: Cri + ?Sized, R: CriRef> RuntimeResolved<'a, B, R> {
    fn query_and_fragment_from_reference(&self) -> bool {
        match self.reference.discard() {
            accessor::Discard::All => true,
            accessor::Discard::Some(n) if n > 0 => true,
            _ => self.reference.path().next().is_some() || self.reference.query().next().is_some(),
        }
    }
}
impl<'a, B: Cri + ?Sized, R: CriRef> CriBase for RuntimeResolved<'a, B, R> {
    type Scheme<'b> = Either<B::Scheme<'b>, R::Scheme<'b>> where Self: 'b;
    type Host<'b> = impl traits::Host where Self: 'b;
    type PathItem<'b> = impl traits::TextOrPet<PATH_UE> where Self: 'b;
    type QueryItem<'b> = impl traits::TextOrPet<QUERY_UE> where Self: 'b;
    type FragmentItem<'b> = impl traits::TextOrPet<FRAGMENT_UE> where Self: 'b;
    type UserInfoItem<'b> = impl traits::TextOrPet<HOST_UE> where Self: 'b;
    type PathIter<'b> = impl Iterator<Item=Self::PathItem<'b>> + ExactSizeIterator where Self: 'b;
    type QueryIter<'b> = impl Iterator<Item=Self::QueryItem<'b>> where Self: 'b;

    fn path(&self) -> Self::PathIter<'_> {
        let base_to_take = match self.reference.discard() {
            accessor::Discard::All => 0,
            accessor::Discard::Some(n) => self.base.path().len().saturating_sub(n.into()),
        };
        let first = self.base.path().take(base_to_take)
            .map(Either::FromBase);
        let second = self.reference.path()
           .map(Either::FromReference);

        Chain { first: Some(first), second }
    }
    fn query(&self) -> Self::QueryIter<'_> {
        if self.query_and_fragment_from_reference() {
            Either2::FromReference(self.reference.query())
        } else {
            Either2::FromBase(self.base.query())
        }
    }
    fn fragment(&self) -> Option<Self::FragmentItem<'_>> {
        Some(if self.query_and_fragment_from_reference() || self.reference.fragment().is_some() {
            Either::FromReference(self.reference.fragment()?)
        } else {
            Either::FromBase(self.base.fragment()?)
        })
    }

    fn userinfo(&self) -> Option<Self::UserInfoItem<'_>> {
        Some(if let Some(_) = self.reference.authority() {
            Either::FromReference(self.reference.userinfo()?)
        } else {
            Either::FromBase(self.base.userinfo()?)
        })
    }
    fn host(&self) -> Self::Host<'_> {
        if let Some(_) = self.reference.authority() {
            Either::FromReference(self.reference.host())
        } else {
            Either::FromBase(self.base.host())
        }
    }
    fn port(&self) -> Option<u16> {
        if let Some(_) = self.reference.authority() {
            self.reference.port()
        } else {
            self.base.port()
        }
    }
}
impl<'a, B: Cri + ?Sized, R: CriRef> Cri for RuntimeResolved<'a, B, R> {
    fn scheme(&self) -> Self::Scheme<'_> {
        self.reference.scheme()
            .map(|r| Either::FromReference(r))
            .unwrap_or_else(|| Either::FromBase(self.base.scheme()))
    }
    fn authority(&self) -> traits::Authority {
        if let Some(ref_authority) = self.reference.authority() {
            ref_authority
        } else {
            self.base.authority()
        }
    }
}