cri-ref 0.0.2

Embedded-friendly equivalents of URIs
Documentation
//! Traits for parts of CRIs / CRI references, which are typically used as GATs of 
//!
//! These are shared between the accessor and the iterator interfaces.

use crate::characterclasses::{AsciiSet, HOST_UE};

/// A scheme as is part of every CRI.
pub trait Scheme {
    /// Give the CRI registered negative integer for the used scheme, if one exits.
    ///
    /// ## Open questions
    ///
    /// More correct would be an nint type that has precisely nint semantics, 
    fn to_cri_id(&self) -> Option<i16>;

    /// Give the textual representation of the scheme
    ///
    /// ## Open questions
    ///
    /// Should this be fallible? Not being, this makes it hard for a Scheme implementation to carry
    /// unknown schemes around.
    fn to_text_scheme(&self) -> &str;

    fn equals(&self, other: impl Scheme) -> bool {
        match (self.to_cri_id(), other.to_cri_id()) {
            (Some(s), Some(o)) => s == o,
            _ => self.to_text_scheme() == other.to_text_scheme(),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Authority {
    HostPort,
    NoAuthoritySlashless,
    NoAuthoritySlashStart,
}

/// The limited list of shapes a [Host] can have.
///
/// This is the main method for reading out a [Host], although it may later change into three
/// `fn as_variant(&self) -> &variant_type` methods (but these can't be matched as easily).
#[derive(Debug)]
pub enum HostRef<'a, HostItem: TextOrPet<HOST_UE>, HostIter: Clone + IntoIterator<Item=HostItem>> {
    IPv4(&'a no_std_net::Ipv4Addr),
    IPv6 { address: &'a no_std_net::Ipv6Addr, zone: Option<&'a str> },
    Hostname(HostIter),
}

impl<'a, HostItem: TextOrPet<HOST_UE>, HostIter: Clone + IntoIterator<Item=HostItem>> HostRef<'a, HostItem, HostIter> {
    pub fn format_uri_host(&self, w: &mut impl core::fmt::Write) -> core::fmt::Result {
        match self {
            HostRef::IPv4(a) => write!(w, "{}", a)?,
            HostRef::IPv6 { address, zone } => {
                write!(w, "[{address}")?;
                if let Some(zone) = zone {
                    write!(w, "%25{zone}")?;
                }
                write!(w, "]")?;
            }
            HostRef::Hostname(s) => {
                let mut first = true;
                for component in s.clone().into_iter() {
                    if first {
                        first = false;
                    } else {
                        write!(w, ".")?;
                    }
                    write!(w, "{}", component.to_uri_component())?;
                }
            }
        }
        Ok(())
    }
}

/// The host copmoonent of a CRI, which may take one of the three shapes of [HostRef].
///
/// It is only present in a CRI that has an authority component.
pub trait Host {
    type HostItem<'a>: TextOrPet<HOST_UE> where Self: 'a;
    type HostIter<'a>: Clone + IntoIterator<Item=Self::HostItem<'a>> where Self: 'a;

    fn as_ref(&self) -> HostRef<'_, Self::HostItem<'_>, Self::HostIter<'_>>;

    fn format_uri_host(&self, w: &mut impl core::fmt::Write) -> core::fmt::Result {
        self.as_ref().format_uri_host(w)
    }

    fn equals(&self, other: &impl Host) -> bool {
        // Could also be implemented as PartialEq on HostRef
        match (self.as_ref(), other.as_ref()) {
            (HostRef::IPv4(s), HostRef::IPv4(o)) => s == o,
            (HostRef::IPv6 { address: sa, zone: sz }, HostRef::IPv6 { address: oa, zone: oz }) => sa == oa && sz == oz,
            (HostRef::Hostname(s), HostRef::Hostname(o)) => TextOrPet::iter_equals(s.into_iter(), o.into_iter()),
            _ => false,
        }
    }
}

impl Scheme for ! {
    fn to_cri_id(&self) -> Option<i16> {
        *self
    }

    fn to_text_scheme(&self) -> &str {
        *self
    }
}

/// A text (or PET) component of a CRI
///
/// It does not need to implement Display on its own (but rather delegates it to an
/// `.to_uri_component()` function) in order to allow implementing a more IRI-ish Display (suitable
/// for printing). on the TextOrPet; if that is not desired, `.to_uri_component()` can return &self.
pub trait TextOrPet<const UNESCAPED: AsciiSet> {
    type UriEncoded<'a>: core::fmt::Display where Self: 'a;

    fn to_uri_component(&self) -> Self::UriEncoded<'_>;

    fn contains_unescaped(&self, needle: char) -> bool {
        use core::fmt::Write;

        struct SeenCharacter {
            seen: bool,
            needle: char,
        }

        impl core::fmt::Write for SeenCharacter {
            fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
                self.seen |= s.contains(self.needle);
                Ok(())
            }
        }

        let mut result = SeenCharacter { seen: false, needle };

        write!(result, "{}", self.to_uri_component())
            .expect("SeenCharacter never fails writes");

        result.seen
    }

    fn equals(&self, other: &impl TextOrPet<UNESCAPED>) -> bool {
        // FIXME outrageously inefficient
        let s = format!("{}", self.to_uri_component());
        let o = format!("{}", other.to_uri_component());
        s == o
    }

    fn iter_equals<O: TextOrPet<UNESCAPED>>(mut s: impl Iterator<Item=Self>, mut o: impl Iterator<Item=O>) -> bool where Self: Sized /* FIXME: really necessary? */ {
        loop {
            match (s.next().as_ref(), o.next().as_ref()) {
                (None, None) => { return true; }
                (Some(si), Some(oi)) => if !si.equals(oi) { return false; }
                _ => { return false; }
            }
        }
    }

    // TBD find good type here
    // fn to_cri_items(&self, );
}