tc-value 0.1.9

Tinychain's representation of a value which can be collated and stored. Unstable.
Documentation
use std::fmt;
use std::ops;

use async_trait::async_trait;
use collate::Collate;
use destream::{de, en};
use futures::TryFutureExt;
use safecast::{TryCastFrom, TryCastInto};

use tc_error::*;
use tcgeneric::{label, Id, Label, Map};

use super::{Value, ValueCollator};

/// The prefix of an inclusive [`Bound`]
pub const IN: Label = label("in");

/// The prefix of an exclusive [`Bound`]
pub const EX: Label = label("ex");

/// An optional inclusive or exclusive bound
#[derive(Clone, Eq, PartialEq)]
pub enum Bound {
    In(Value),
    Ex(Value),
    Un,
}

impl<'en> en::IntoStream<'en> for Bound {
    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        match self {
            Self::In(v) => single_entry(Id::from(IN), v, encoder),
            Self::Ex(v) => single_entry(Id::from(EX), v, encoder),
            Self::Un => Value::None.into_stream(encoder),
        }
    }
}

impl<'en> en::ToStream<'en> for Bound {
    fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        match self {
            Self::In(v) => single_entry(Id::from(IN), v, encoder),
            Self::Ex(v) => single_entry(Id::from(EX), v, encoder),
            Self::Un => Value::None.to_stream(encoder),
        }
    }
}

impl TryCastFrom<Map<Value>> for Bound {
    fn can_cast_from(value: &Map<Value>) -> bool {
        value.len() == 1 && (value.contains_key(&IN.into()) || value.contains_key(&EX.into()))
    }

    fn opt_cast_from(mut value: Map<Value>) -> Option<Self> {
        if value.len() == 1 {
            if let Some(value) = value.remove(&IN.into()) {
                Some(Self::In(value))
            } else if let Some(value) = value.remove(&EX.into()) {
                Some(Self::Ex(value))
            } else {
                None
            }
        } else {
            None
        }
    }
}

impl From<Bound> for ops::Bound<Value> {
    fn from(bound: Bound) -> Self {
        match bound {
            Bound::In(value) => Self::Included(value),
            Bound::Ex(value) => Self::Excluded(value),
            Bound::Un => Self::Unbounded,
        }
    }
}

impl fmt::Display for Bound {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::In(value) => write!(f, "include({})", value),
            Self::Ex(value) => write!(f, "exclude({})", value),
            Self::Un => write!(f, "unbounded"),
        }
    }
}

/// A range comprising a start and end [`Bound`]
#[derive(Clone, Eq, PartialEq)]
pub struct Range {
    pub start: Bound,
    pub end: Bound,
}

impl Range {
    /// Return true if the given `Range` is within this `Range`.
    pub fn contains_range(&self, inner: &Self, collator: &ValueCollator) -> bool {
        use std::cmp::Ordering::*;

        match &self.start {
            Bound::Un => {}
            Bound::In(outer) => match &inner.start {
                Bound::Un => return false,
                Bound::In(inner) => {
                    if collator.compare(inner, outer) == Less {
                        return false;
                    }
                }
                Bound::Ex(inner) => {
                    if collator.compare(inner, outer) != Greater {
                        return false;
                    }
                }
            },
            Bound::Ex(outer) => match &inner.start {
                Bound::Un => return false,
                Bound::In(inner) => {
                    if collator.compare(inner, outer) != Greater {
                        return false;
                    }
                }
                Bound::Ex(inner) => {
                    if collator.compare(inner, outer) == Less {
                        return false;
                    }
                }
            },
        }

        match &self.end {
            Bound::Un => {}
            Bound::In(outer) => match &inner.end {
                Bound::Un => return false,
                Bound::In(inner) => {
                    if collator.compare(inner, outer) == Greater {
                        return false;
                    }
                }
                Bound::Ex(inner) => {
                    if collator.compare(inner, outer) != Less {
                        return false;
                    }
                }
            },
            Bound::Ex(outer) => match &inner.end {
                Bound::Un => return false,
                Bound::In(inner) => {
                    if collator.compare(inner, outer) != Less {
                        return false;
                    }
                }
                Bound::Ex(inner) => {
                    if collator.compare(inner, outer) == Greater {
                        return false;
                    }
                }
            },
        }

        true
    }

    /// Return true if the given [`Value`] is within this `Range`.
    pub fn contains_value(&self, value: &Value, collator: &ValueCollator) -> bool {
        use std::cmp::Ordering::*;

        match &self.start {
            Bound::Un => {}
            Bound::In(outer) => {
                if collator.compare(value, outer) == Less {
                    return false;
                }
            }
            Bound::Ex(outer) => {
                if collator.compare(value, outer) != Greater {
                    return false;
                }
            }
        }

        match &self.end {
            Bound::Un => {}
            Bound::In(outer) => {
                if collator.compare(value, outer) == Greater {
                    return false;
                }
            }
            Bound::Ex(outer) => {
                if collator.compare(value, outer) != Less {
                    return false;
                }
            }
        }

        true
    }
}

impl Default for Range {
    fn default() -> Self {
        Self {
            start: Bound::Un,
            end: Bound::Un,
        }
    }
}

#[async_trait]
impl de::FromStream for Range {
    type Context = ();

    async fn from_stream<D: de::Decoder>(cxt: (), decoder: &mut D) -> Result<Self, D::Error> {
        let start = if let Ok(map) = Map::<Value>::from_stream(cxt, decoder).await {
            map.try_cast_into(|v| TCError::bad_request("invalid Range", v))
                .map_err(de::Error::custom)
        } else {
            Value::from_stream(cxt, decoder)
                .map_ok(|v| if v.is_none() { Bound::Un } else { Bound::In(v) })
                .await
        }?;

        let end = if let Ok(map) = Map::<Value>::from_stream(cxt, decoder).await {
            map.try_cast_into(|v| TCError::bad_request("invalid Range", v))
                .map_err(de::Error::custom)
        } else {
            Value::from_stream(cxt, decoder)
                .map_ok(|v| if v.is_none() { Bound::Un } else { Bound::Ex(v) })
                .await
        }?;

        Ok(Range { start, end })
    }
}

impl<'en> en::IntoStream<'en> for Range {
    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        match (self.start, self.end) {
            (Bound::In(start), Bound::Ex(end)) => (start, end).into_stream(encoder),
            (Bound::Un, Bound::Ex(end)) => ((), end).into_stream(encoder),
            (Bound::In(start), Bound::Un) => (start, ()).into_stream(encoder),
            (start, end) => (start, end).into_stream(encoder),
        }
    }
}

impl<'en> en::ToStream<'en> for Range {
    fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        use en::IntoStream;

        match (&self.start, &self.end) {
            (Bound::In(start), Bound::Ex(end)) => (start, end).into_stream(encoder),
            (Bound::Un, Bound::Ex(end)) => ((), end).into_stream(encoder),
            (Bound::In(start), Bound::Un) => (start, ()).into_stream(encoder),
            (start, end) => (start, end).into_stream(encoder),
        }
    }
}

impl From<Range> for (ops::Bound<Value>, ops::Bound<Value>) {
    fn from(range: Range) -> Self {
        (range.start.into(), range.end.into())
    }
}

impl fmt::Display for Range {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (&self.start, &self.end) {
            (Bound::Ex(start), Bound::Ex(end)) => write!(f, "({}, {})", start, end),
            (Bound::Ex(start), Bound::In(end)) => write!(f, "({}, {}]", start, end),
            (Bound::Ex(start), Bound::Un) => write!(f, "({}...]", start),

            (Bound::In(start), Bound::Ex(end)) => write!(f, "[{}, {})", start, end),
            (Bound::In(start), Bound::In(end)) => write!(f, "[{}, {}]", start, end),
            (Bound::In(start), Bound::Un) => write!(f, "[{}...]", start),

            (Bound::Un, Bound::Ex(end)) => write!(f, "[...{})", end),
            (Bound::Un, Bound::In(end)) => write!(f, "[...{}]", end),
            (Bound::Un, Bound::Un) => f.write_str("[...]"),
        }
    }
}

fn single_entry<
    'en,
    K: en::IntoStream<'en> + 'en,
    V: en::IntoStream<'en> + 'en,
    E: en::Encoder<'en>,
>(
    key: K,
    value: V,
    encoder: E,
) -> Result<E::Ok, E::Error> {
    use en::EncodeMap;

    let mut map = encoder.encode_map(Some(1))?;
    map.encode_entry(key, value)?;
    map.end()
}