zulip-api 0.0.8

Client library for interacting with the Zulip API
Documentation
use serde::{Deserialize, Serialize};

use crate::error::Error;

/// Represents a filter anchor to provide a baseline for searching.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Anchor {
    /// Filter relative to the newest message.
    Newest,
    /// Filter relative to the oldest message.
    Oldest,
    /// Filter relative to the first unread message.
    FirstUnread,
    /// Filter relative to the provided message ID.
    #[serde(untagged)]
    MessageId(u64),
}

impl Anchor {
    /// Represents the string value of the newest [Anchor].
    pub const NEWEST: &str = "newest";
    /// Represents the string value of the oldest [Anchor].
    pub const OLDEST: &str = "oldest";
    /// Represents the string value of the first unread [Anchor].
    pub const FIRST_UNREAD: &str = "first_unread";

    /// Creates a new [Anchor].
    pub const fn new() -> Self {
        Self::Newest
    }

    /// Attempts to parse the provided string into an [Anchor].
    pub fn try_from_str(val: &str) -> Result<Self, Error> {
        match val.to_lowercase().as_str() {
            Self::NEWEST => Ok(Self::Newest),
            Self::OLDEST => Ok(Self::Oldest),
            Self::FIRST_UNREAD => Ok(Self::FirstUnread),
            s => s
                .parse::<u64>()
                .map(Self::MessageId)
                .map_err(|err| Error::io_other(format!("{err}"))),
        }
    }
}

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

impl From<Anchor> for String {
    fn from(val: Anchor) -> Self {
        val.to_string()
    }
}

impl From<&Anchor> for String {
    fn from(val: &Anchor) -> Self {
        val.to_string()
    }
}

impl TryFrom<&str> for Anchor {
    type Error = Error;

    fn try_from(val: &str) -> Result<Self, Self::Error> {
        Self::try_from_str(val)
    }
}

impl TryFrom<String> for Anchor {
    type Error = Error;

    fn try_from(val: String) -> Result<Self, Self::Error> {
        Self::try_from_str(val.as_str())
    }
}

impl std::fmt::Display for Anchor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Newest => write!(f, "{}", Self::NEWEST),
            Self::Oldest => write!(f, "{}", Self::OLDEST),
            Self::FirstUnread => write!(f, "{}", Self::FIRST_UNREAD),
            Self::MessageId(id) => write!(f, "{id}"),
        }
    }
}

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

    #[test]
    fn test_anchor_strings() {
        [Anchor::Newest, Anchor::Oldest, Anchor::FirstUnread]
            .into_iter()
            .for_each(|anchor| {
                let anchor_json = format!(r#""{anchor}""#);

                assert_eq!(serde_json::to_string(&anchor).unwrap(), anchor_json);
                assert_eq!(
                    serde_json::from_str::<Anchor>(&anchor_json).unwrap(),
                    anchor
                );
            });
    }

    #[test]
    fn test_anchor_ints() {
        (0..=u64::BITS)
            .map(|r| Anchor::MessageId(((1u128 << r) - 1) as u64))
            .for_each(|anchor| {
                let anchor_json = anchor.to_string();

                assert_eq!(serde_json::to_string(&anchor).unwrap(), anchor_json);
                assert_eq!(
                    serde_json::from_str::<Anchor>(&anchor_json).unwrap(),
                    anchor
                );
            });
    }
}