zulip-api 0.0.8

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

use crate::{field_access, ReactionType};

/// Represents the request to the `Reactions` Zulip API.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Request {
    message_id: u64,
    emoji_name: String,
    emoji_code: Option<String>,
    reaction_type: Option<ReactionType>,
}

impl Request {
    /// Creates a new [Request].
    pub const fn new() -> Self {
        Self {
            message_id: 0,
            emoji_name: String::new(),
            emoji_code: None,
            reaction_type: None,
        }
    }
}

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

field_access! {
    Request {
        /// The target message's ID.
        message_id: u64,
    }
}

field_access! {
    Request {
        /// The target emoji's human-readable name.
        ///
        /// To find an emoji's name, hover over a message to reveal
        /// three icons on the right, then click the smiley face icon.
        /// Images of available reaction emojis appear. Hover over the
        /// emoji you want, and note that emoji's text name.
        emoji_name: cow { str },
    }
}

field_access! {
    Request {
        /// A unique identifier, defining the specific emoji codepoint requested,
        /// within the namespace of the `reaction_type`.
        ///
        /// For most API clients, you won't need this, but it's important
        /// for Zulip apps to handle rare corner cases when
        /// adding/removing votes on an emoji reaction added previously by
        /// another user.
        ///
        /// If the existing reaction was added when the Zulip server was
        /// using a previous version of the emoji data mapping between
        /// Unicode codepoints and human-readable names, sending the
        /// `emoji_code` in the data for the original reaction allows the
        /// Zulip server to correctly interpret your upvote as an upvote
        /// rather than a reaction with a "different" emoji.
        emoji_code: option_cow_deref { str, String },
    }
}

field_access! {
    Request {
        /// A string indicating the type of emoji. Each emoji `reaction_type`
        /// has an independent namespace for values of `emoji_code`.
        ///
        /// If an API client is adding/removing a vote on an existing reaction,
        /// it should pass this parameter using the value the server provided
        /// for the existing reaction for specificity. Supported values:
        ///
        /// - `unicode_emoji` : In this namespace, `emoji_code` will be a
        ///   dash-separated hex encoding of the sequence of Unicode codepoints
        ///   that define this emoji in the Unicode specification.
        ///
        /// - `realm_emoji` : In this namespace, `emoji_code` will be the ID of
        ///   the uploaded [custom emoji](/help/custom-emoji).
        ///
        /// - `zulip_extra_emoji` : These are special emoji included with Zulip.
        ///   In this namespace, `emoji_code` will be the name of the emoji (e.g.
        ///   "zulip").
        reaction_type: option { ReactionType },
    }
}

impl std::fmt::Display for Request {
    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_request_minimal() {
        let message_id = 1;
        let emoji_name = "+1";

        let req = Request::new()
            .with_message_id(message_id)
            .with_emoji_name(emoji_name);
        let req_json = req.to_string();

        assert_eq!(serde_json::to_string(&req).unwrap(), req_json);
        assert_eq!(serde_json::from_str::<Request>(&req_json).unwrap(), req);

        assert_eq!(req.message_id(), message_id);
        assert_eq!(req.emoji_name(), emoji_name);
        assert_eq!(req.emoji_code(), None);
        assert_eq!(req.reaction_type(), None);
    }

    #[test]
    fn test_request_full() {
        let message_id = 1;
        let emoji_name = "+1";
        let emoji_code = "U+1F44D";
        let reaction_type = ReactionType::UnicodeEmoji;

        let req = Request::new()
            .with_message_id(message_id)
            .with_emoji_name(emoji_name)
            .with_emoji_code(emoji_code)
            .with_reaction_type(reaction_type);
        let req_json = req.to_string();

        assert_eq!(serde_json::to_string(&req).unwrap(), req_json);
        assert_eq!(serde_json::from_str::<Request>(&req_json).unwrap(), req);

        assert_eq!(req.message_id(), message_id);
        assert_eq!(req.emoji_name(), emoji_name);
        assert_eq!(req.emoji_code(), Some(emoji_code));
        assert_eq!(req.reaction_type(), Some(reaction_type));
    }
}