zulip-api 0.0.8

Client library for interacting with the Zulip API
Documentation
use std::borrow::Cow;

use serde::{Deserialize, Serialize};

use crate::error::Error;
use crate::result::Result;

/// Represents an operand used to filter searches and messages.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Operand {
    Integer(u64),
    IntegerList(Vec<u64>),
    Text(String),
    TextList(Vec<String>),
}

impl Operand {
    /// Creates a new [Operand].
    pub const fn new() -> Self {
        Self::Integer(0)
    }

    /// Convenience function to get the variant name's string representation.
    pub(crate) const fn variant_name(&self) -> &'static str {
        match self {
            Self::Integer(_) => "Operand::Integer",
            Self::IntegerList(_) => "Operand::IntegerList",
            Self::Text(_) => "Operand::Text",
            Self::TextList(_) => "Operand::TextList",
        }
    }

    /// Attempts to get the integer variant.
    pub fn as_integer(&self) -> Result<u64> {
        match self {
            Self::Integer(v) => Ok(*v),
            o => Err(Error::invalid_field(
                "operand",
                o.variant_name(),
                self.variant_name(),
            )),
        }
    }

    /// Creates a new integer list variant.
    pub fn integer_list<'c, C: Into<Cow<'c, [u64]>>>(val: C) -> Self {
        Self::IntegerList(C::into(val).into())
    }

    /// Attempts to get the integer list variant.
    pub fn as_integer_list(&self) -> Result<&[u64]> {
        match self {
            Self::IntegerList(v) => Ok(v.as_slice()),
            o => Err(Error::invalid_field(
                "operand",
                o.variant_name(),
                self.variant_name(),
            )),
        }
    }

    /// Creates a new text variant.
    pub fn text<'c, C: Into<Cow<'c, str>>>(val: C) -> Self {
        Self::Text(C::into(val).into())
    }

    /// Attempts to get the text variant.
    pub fn as_text(&self) -> Result<&str> {
        match self {
            Self::Text(v) => Ok(v.as_str()),
            o => Err(Error::invalid_field(
                "operand",
                o.variant_name(),
                self.variant_name(),
            )),
        }
    }

    /// Creates a new text list variant.
    pub fn text_list<'c, C: Into<Cow<'c, [String]>>>(val: C) -> Self {
        Self::TextList(C::into(val).into())
    }

    /// Attempts to get the text list variant.
    pub fn as_text_list(&self) -> Result<&[String]> {
        match self {
            Self::TextList(v) => Ok(v.as_slice()),
            o => Err(Error::invalid_field(
                "operand",
                o.variant_name(),
                self.variant_name(),
            )),
        }
    }
}

impl Default for Operand {
    fn default() -> Self {
        Self::new()
    }
}

impl From<u64> for Operand {
    fn from(val: u64) -> Self {
        Self::Integer(val)
    }
}

impl From<Vec<u64>> for Operand {
    fn from(val: Vec<u64>) -> Self {
        Self::IntegerList(val)
    }
}

impl From<&[u64]> for Operand {
    fn from(val: &[u64]) -> Self {
        Self::IntegerList(val.into())
    }
}

impl<const N: usize> From<&[u64; N]> for Operand {
    fn from(val: &[u64; N]) -> Self {
        Self::IntegerList(val.into())
    }
}

impl<const N: usize> From<[u64; N]> for Operand {
    fn from(val: [u64; N]) -> Self {
        Self::IntegerList(val.into_iter().collect())
    }
}

impl From<&str> for Operand {
    fn from(val: &str) -> Self {
        Self::Text(val.into())
    }
}

impl From<String> for Operand {
    fn from(val: String) -> Self {
        Self::Text(val)
    }
}

impl From<Vec<String>> for Operand {
    fn from(val: Vec<String>) -> Self {
        Self::TextList(val)
    }
}

impl From<&[String]> for Operand {
    fn from(val: &[String]) -> Self {
        Self::TextList(val.into())
    }
}

impl From<&[&str]> for Operand {
    fn from(val: &[&str]) -> Self {
        Self::TextList(val.iter().map(|&s| String::from(s)).collect())
    }
}

impl<const N: usize> From<&[String; N]> for Operand {
    fn from(val: &[String; N]) -> Self {
        Self::TextList(val.into())
    }
}

impl<const N: usize> From<&[&str; N]> for Operand {
    fn from(val: &[&str; N]) -> Self {
        Self::TextList(val.iter().map(|&s| String::from(s)).collect())
    }
}

impl<const N: usize> From<[String; N]> for Operand {
    fn from(val: [String; N]) -> Self {
        Self::TextList(val.into())
    }
}

impl<const N: usize> From<[&str; N]> for Operand {
    fn from(val: [&str; N]) -> Self {
        Self::TextList(val.into_iter().map(String::from).collect())
    }
}

impl std::fmt::Display for Operand {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        serde_json::to_string(self)
            .map_err(|_| std::fmt::Error)
            .and_then(|s| write!(f, "{s}"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_operand() {
        (0..=u64::BITS)
            .map(|r| ((1u128 << r) - 1) as u64)
            .enumerate()
            .for_each(|(i, int)| {
                let list_len = i + 1;
                let int_list = vec![int; list_len];
                let text = format!("{int}");
                let text_list = vec![text.clone(); list_len];

                let op_int = Operand::Integer(int);
                let op_int_list = Operand::IntegerList(int_list.clone());
                let op_text = Operand::Text(text.clone());
                let op_text_list = Operand::TextList(text_list.clone());

                assert_eq!(op_int.as_integer(), Ok(int));
                assert_eq!(op_int_list.as_integer_list(), Ok(int_list.as_slice()));
                assert_eq!(op_text.as_text(), Ok(text.as_str()));
                assert_eq!(op_text_list.as_text_list(), Ok(text_list.as_slice()));

                [op_int, op_int_list, op_text, op_text_list]
                    .into_iter()
                    .for_each(|op| {
                        let op_json = format!("{op}");

                        assert_eq!(serde_json::to_string(&op).unwrap(), op_json);
                        assert_eq!(serde_json::from_str::<Operand>(&op_json).unwrap(), op);
                    });
            });
    }
}