zulip-api 0.0.8

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

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

/// Represents the Zulip server response error code variants.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ErrorCode {
    /// Indicates a general request error.
    BadRequest,
    /// Indicates an invalid API key.
    InvalidApiKey,
    /// Indicates a missing required request variable.
    RequestVariableMissing,
    /// Indicates the requested user is desctivated.
    UserDeactivated,
    /// Indicates the requested organization is desctivated.
    RealmDeactivated,
    /// Indicates the rate limit was exceeded.
    RateLimitHit,
    /// Indicates the requested stream does not exist.
    StreamDoesNotExist,
    /// Indicates a bad narrow filter.
    BadNarrow,
}

impl ErrorCode {
    /// Represents the string value for the general bad request [ErrorCode].
    pub const BAD_REQUEST: &str = "BAD_REQUEST";
    /// Represents the string value for the invalid API key [ErrorCode].
    pub const INVALID_API_KEY: &str = "INVALID_API_KEY";
    /// Represents the string value for the request variable missing [ErrorCode].
    pub const REQUEST_VARIABLE_MISSING: &str = "REQUEST_VARIABLE_MISSING";
    /// Represents the string value for the user deactivated [ErrorCode].
    pub const USER_DEACTIVATED: &str = "USER_DEACTIVATED";
    /// Represents the string value for the realm deactivated [ErrorCode].
    pub const REALM_DEACTIVATED: &str = "REALM_DEACTIVATED";
    /// Represents the string value for the rate limit hit [ErrorCode].
    pub const RATE_LIMIT_HIT: &str = "RATE_LIMIT_HIT";
    /// Represents the string value for the stream does not exist [ErrorCode].
    pub const STREAM_DOES_NOT_EXIST: &str = "STREAM_DOES_NOT_EXIST";
    /// Represents the string value for the bad narrow [ErrorCode].
    pub const BAD_NARROW: &str = "BAD_NARROW";

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

    /// Gets the string representation of the [ErrorCode].
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::BadRequest => Self::BAD_REQUEST,
            Self::InvalidApiKey => Self::INVALID_API_KEY,
            Self::RequestVariableMissing => Self::REQUEST_VARIABLE_MISSING,
            Self::UserDeactivated => Self::USER_DEACTIVATED,
            Self::RealmDeactivated => Self::REALM_DEACTIVATED,
            Self::RateLimitHit => Self::RATE_LIMIT_HIT,
            Self::StreamDoesNotExist => Self::STREAM_DOES_NOT_EXIST,
            Self::BadNarrow => Self::BAD_NARROW,
        }
    }

    /// Attempts to convert a string into an [ErrorCode].
    pub fn try_from_str<S: Into<String>>(val: S) -> Result<Self> {
        match val.into().to_uppercase().as_str() {
            Self::BAD_REQUEST => Ok(Self::BadRequest),
            Self::INVALID_API_KEY => Ok(Self::InvalidApiKey),
            Self::REQUEST_VARIABLE_MISSING => Ok(Self::RequestVariableMissing),
            Self::USER_DEACTIVATED => Ok(Self::UserDeactivated),
            Self::REALM_DEACTIVATED => Ok(Self::RealmDeactivated),
            Self::RATE_LIMIT_HIT => Ok(Self::RateLimitHit),
            Self::STREAM_DOES_NOT_EXIST => Ok(Self::StreamDoesNotExist),
            Self::BAD_NARROW => Ok(Self::BadNarrow),
            s => Err(Error::io_other(format!("invalid ErrorCode: {s}"))),
        }
    }
}

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

impl From<ErrorCode> for &'static str {
    fn from(val: ErrorCode) -> Self {
        val.as_str()
    }
}

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

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

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

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

    fn try_from(val: String) -> Result<Self> {
        Self::try_from_str(val)
    }
}

impl std::fmt::Display for ErrorCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

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

    #[test]
    fn test_error_code() {
        [
            (ErrorCode::BadRequest, ErrorCode::BAD_REQUEST),
            (ErrorCode::InvalidApiKey, ErrorCode::INVALID_API_KEY),
            (
                ErrorCode::RequestVariableMissing,
                ErrorCode::REQUEST_VARIABLE_MISSING,
            ),
            (ErrorCode::UserDeactivated, ErrorCode::USER_DEACTIVATED),
            (ErrorCode::RealmDeactivated, ErrorCode::REALM_DEACTIVATED),
            (ErrorCode::RateLimitHit, ErrorCode::RATE_LIMIT_HIT),
            (
                ErrorCode::StreamDoesNotExist,
                ErrorCode::STREAM_DOES_NOT_EXIST,
            ),
            (ErrorCode::BadNarrow, ErrorCode::BAD_NARROW),
        ]
        .into_iter()
        .for_each(|(code, code_str)| {
            let code_json = format!(r#""{code}""#);
            assert_eq!(code.as_str(), code_str);

            assert_eq!(<&str>::from(code), code_str);
            assert_eq!(String::from(code), code_str.to_string());

            assert_eq!(ErrorCode::try_from_str(code_str), Ok(code));
            assert_eq!(ErrorCode::try_from(code_str), Ok(code));
            assert_eq!(ErrorCode::try_from(code_str.to_string()), Ok(code));

            assert_eq!(serde_json::to_string(&code).unwrap(), code_json);
            assert_eq!(serde_json::from_str::<ErrorCode>(&code_json).unwrap(), code);
        });
    }
}