empress 1.6.0

A D-Bus MPRIS daemon for controlling media players.
use std::borrow::{
    Cow,
    Cow::{Borrowed, Owned},
};

use super::interp::{json, Context, CowValue, StreamError, Value};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Expected {0} argument(s), got {1}")]
    Arity(usize, usize),
    #[error("Type error: expected {0}, got {}", json(.1))]
    Type(&'static str, Value),
    #[error("Expected a pipe input, got nothing")]
    NoTopic,
    #[error("Unexpected pipe input")]
    ExtraTopic,
    #[error("An error occurred while executing the function")]
    Other(#[from] anyhow::Error),
}

impl From<StreamError> for Error {
    fn from(err: StreamError) -> Self {
        match err {
            StreamError::Io(e) => {
                Self::Other(anyhow::Error::new(e).context("I/O error while printing a value"))
            },
            StreamError::Unprintable(v) => Self::Type("a printable value", v),
        }
    }
}

pub type Result<T> = std::result::Result<T, Error>;
pub type Output<'a> = Result<CowValue<'a>>;
pub type Function = for<'a> fn(Input<'a>) -> Output<'a>;

#[allow(missing_debug_implementations)]
pub struct Input<'a> {
    ctx: &'a Context,
    topic: Option<CowValue<'a>>,
    args: Vec<CowValue<'a>>,
}

trait Marshal<'a>: Sized {
    const ARITY: usize;

    fn marshal<I: IntoIterator<IntoIter = J>, J: ExactSizeIterator<Item = CowValue<'a>>>(
        iter: I,
    ) -> Result<Self> {
        let iter = iter.into_iter();

        match iter.len() {
            l if l == Self::ARITY => Self::marshal_unchecked(iter),
            len => Err(Error::Arity(Self::ARITY, len)),
        }
    }

    fn marshal_unchecked<I: ExactSizeIterator<Item = CowValue<'a>>>(iter: I) -> Result<Self>;
}

impl<'a> Marshal<'a> for () {
    const ARITY: usize = 0;

    fn marshal_unchecked<I: ExactSizeIterator<Item = CowValue<'a>>>(mut iter: I) -> Result<Self> {
        if cfg!(debug_assertions) && iter.next().is_some() {
            unreachable!()
        }

        Ok(())
    }
}

impl<'a, H: TryFrom<CowValue<'a>, Error = Error>, T: Marshal<'a>> Marshal<'a> for (H, T) {
    const ARITY: usize = T::ARITY + 1;

    fn marshal_unchecked<I: ExactSizeIterator<Item = CowValue<'a>>>(mut iter: I) -> Result<Self> {
        Ok((
            iter.next().unwrap_or_else(|| unreachable!()).try_into()?,
            T::marshal_unchecked(iter)?,
        ))
    }
}

trait MarshalTopic<'a>: TryFrom<Option<CowValue<'a>>, Error = Error> {}

impl<'a> Input<'a> {
    #[inline]
    pub fn new(ctx: &'a Context, topic: Option<CowValue<'a>>, args: Vec<CowValue<'a>>) -> Self {
        Self { ctx, topic, args }
    }
}

trait MarshalNumber: Sized {
    fn marshal_num(num: &serde_json::Number) -> Result<Self>;
}

impl MarshalNumber for usize {
    fn marshal_num(num: &serde_json::Number) -> Result<Self> {
        num.as_u64()
            .and_then(|n| usize::try_from(n).ok())
            .ok_or_else(|| Error::Type("a usize", Value::Number(num.clone())))
    }
}

impl MarshalNumber for i64 {
    fn marshal_num(num: &serde_json::Number) -> Result<Self> {
        num.as_i64()
            .ok_or_else(|| Error::Type("an i64", Value::Number(num.clone())))
    }
}

impl<'a, T, A> TryFrom<Input<'a>> for (&'a Context, T, A)
where
    T: MarshalTopic<'a>,
    A: Marshal<'a>,
{
    type Error = Error;

    fn try_from(Input { ctx, topic, args }: Input<'a>) -> Result<Self> {
        Ok((ctx, topic.try_into()?, A::marshal(args)?))
    }
}

pub struct NoTopic;
#[repr(transparent)]
pub struct Topic<T>(pub T);

impl<'a> MarshalTopic<'a> for NoTopic {}
impl<'a, T: TryFrom<CowValue<'a>, Error = Error>> MarshalTopic<'a> for Topic<T> {}

impl<'a> TryFrom<Option<CowValue<'a>>> for NoTopic {
    type Error = Error;

    fn try_from(topic: Option<CowValue<'a>>) -> Result<Self> {
        match topic {
            Some(_) => Err(Error::ExtraTopic),
            None => Ok(Self),
        }
    }
}

impl<'a, T: TryFrom<CowValue<'a>, Error = Error>> TryFrom<Option<CowValue<'a>>> for Topic<T> {
    type Error = Error;

    fn try_from(topic: Option<CowValue<'a>>) -> Result<Self> {
        match topic {
            Some(t) => Ok(Self(t.try_into()?)),
            None => Err(Error::NoTopic),
        }
    }
}

#[repr(transparent)]
pub struct Any<'a>(pub CowValue<'a>);

impl<'a> TryFrom<CowValue<'a>> for Any<'a> {
    type Error = Error;

    fn try_from(val: CowValue<'a>) -> Result<Self> { Ok(Self(val)) }
}

pub struct Array<'a>(pub Cow<'a, Vec<Value>>);

impl<'a> TryFrom<CowValue<'a>> for Array<'a> {
    type Error = Error;

    fn try_from(val: CowValue<'a>) -> Result<Self> {
        match val {
            Borrowed(Value::Array(v)) => Ok(Self(Borrowed(v))),
            Owned(Value::Array(v)) => Ok(Self(Owned(v))),
            v => Err(Error::Type("an array", v.into_owned())),
        }
    }
}

pub struct Number<T>(pub T);

impl<'a, T: MarshalNumber> TryFrom<CowValue<'a>> for Number<T> {
    type Error = Error;

    fn try_from(val: CowValue<'a>) -> Result<Self> {
        match val.as_ref() {
            Value::Number(n) => T::marshal_num(n).map(Self),
            _ => Err(Error::Type("a number", val.into_owned())),
        }
    }
}