diff --git a/src/error.rs b/src/error.rs index 54aa3f5..3216225 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,6 +23,7 @@ pub enum Error { #[cfg(feature = "async")] InvalidHeaderValue(InvalidHeaderValue), EmptyResponse, + UnknownPriority, } impl std::error::Error for Error {} @@ -39,7 +40,8 @@ impl fmt::Display for Error { Self::Url(e) => write!(f, "{}", e), #[cfg(feature = "async")] Self::InvalidHeaderValue(e) => write!(f, "{}", e), - Self::EmptyResponse => write!(f, "Empty Response"), + Self::EmptyResponse => write!(f, "Empty response"), + Self::UnknownPriority => write!(f, "Unknown priority"), } } } diff --git a/src/payload/priority.rs b/src/payload/priority.rs index d3af204..e9f6884 100644 --- a/src/payload/priority.rs +++ b/src/payload/priority.rs @@ -1,8 +1,12 @@ // Copyright (c) 2022 Yuki Kishimoto // Distributed under the MIT software license -use serde::de::Error; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::error::Error; #[repr(u8)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -15,13 +19,72 @@ pub enum Priority { Min = 1, } +impl fmt::Display for Priority { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl Priority { + pub fn from_u8(priority: u8) -> Result { + match priority { + 5 => Ok(Priority::Max), + 4 => Ok(Priority::High), + 3 => Ok(Priority::Default), + 2 => Ok(Priority::Low), + 1 => Ok(Priority::Min), + _ => Err(Error::UnknownPriority), + } + } + + /// Convert to `u8` + #[inline] + pub fn as_u8(&self) -> u8 { + *self as u8 + } + + /// Convert to `&str` + pub fn as_str(&self) -> &str { + match self { + Self::Max => "max", + Self::High => "high", + Self::Default => "default", + Self::Low => "low", + Self::Min => "min", + } + } +} + +impl FromStr for Priority { + type Err = Error; + + fn from_str(priority: &str) -> Result { + match priority { + "max" | "urgent" => Ok(Self::Max), + "high" => Ok(Self::High), + "default" => Ok(Self::Default), + "low" => Ok(Self::Low), + "min" => Ok(Self::Min), + _ => Err(Error::UnknownPriority), + } + } +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum NumberOrString { + Number(u8), + String(String), +} + impl Serialize for Priority { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let p: u8 = *self as u8; - p.serialize(serializer) + // According to ntfy docs, the priority in the JSON payload must be a number. + // https://docs.ntfy.sh/subscribe/api/#json-message-format + serializer.serialize_u8(self.as_u8()) } } @@ -30,13 +93,65 @@ impl<'de> Deserialize<'de> for Priority { where D: Deserializer<'de>, { - match u8::deserialize(deserializer)? { - 5 => Ok(Priority::Max), - 4 => Ok(Priority::High), - 3 => Ok(Priority::Default), - 2 => Ok(Priority::Low), - 1 => Ok(Priority::Min), - o => Err(Error::custom(format_args!("Invalid value: {}", o))), + match NumberOrString::deserialize(deserializer)? { + NumberOrString::Number(priority) => { + Priority::from_u8(priority).map_err(de::Error::custom) + } + NumberOrString::String(priority) => { + Self::from_str(&priority).map_err(de::Error::custom) + } } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize() { + let json = serde_json::json!(5); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Max); + + let json = serde_json::json!(4); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::High); + + let json = serde_json::json!(3); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Default); + + let json = serde_json::json!(2); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Low); + + let json = serde_json::json!(1); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Min); + + let json = serde_json::json!("urgent"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Max); + + let json = serde_json::json!("max"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Max); + + let json = serde_json::json!("high"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::High); + + let json = serde_json::json!("default"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Default); + + let json = serde_json::json!("low"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Low); + + let json = serde_json::json!("min"); + let priority: Priority = serde_json::from_value(json).unwrap(); + assert_eq!(priority, Priority::Min); + } +}