From 436f99e6b8d2a8c943489e5c98ffd7942664e99a Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Dec 2019 17:35:39 +0300 Subject: [PATCH 1/2] Replace `KeyboardButton`'s optional mutually exclusive fields by an enum --- src/types/keyboard_button.rs | 132 +++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 01929838..2176eb87 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -1,19 +1,141 @@ +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::types::True; + /// This object represents one button of the reply keyboard. For filter text /// buttons String can be used instead of this object to specify text of the -/// button. Optional fields are mutually exclusive. +/// button. +#[serde_with_macros::skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)] pub struct KeyboardButton { /// Text of the button. If none of the optional fields are used, it will /// be sent as a message when the button is pressed pub text: String, - #[serde(skip_serializing_if = "Option::is_none")] + /// Request something from user. + /// - If `Some(Contact)`, the user's phone number will be sent as a contact + /// when the button is pressed. Available in private chats only + /// - If `Some(Location)`, the user's current location will be sent when + /// the button is pressed. Available in private chats only + #[serde(flatten)] + pub request: Option, +} + +// Serialize + Deserialize are implemented by hand +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub enum ButtonRequest { + Location, + Contact, +} + +/// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) +#[serde_with_macros::skip_serializing_none] +#[derive(Serialize, Deserialize)] +struct RawRequest { /// Optional. If True, the user's phone number will be sent as a contact /// when the button is pressed. Available in private chats only - pub request_contact: Option, + #[serde(rename = "request_contact")] + contact: Option, - #[serde(skip_serializing_if = "Option::is_none")] /// Optional. If True, the user's current location will be sent when the /// button is pressed. Available in private chats only - pub request_location: Option, + #[serde(rename = "request_location")] + location: Option, +} + +impl<'de> Deserialize<'de> for ButtonRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = RawRequest::deserialize(deserializer)?; + match raw { + RawRequest { + contact: Some(_), + location: Some(_), + } => Err(D::Error::custom( + "`request_contact` and `request_location` fields are mutually \ + exclusive, but both were provided", + )), + RawRequest { + contact: Some(_), .. + } => Ok(Self::Contact), + RawRequest { + location: Some(_), .. + } => Ok(Self::Location), + _ => Err(D::Error::custom( + "Either one of `request_contact` and `request_location` \ + fields is required", + )), + } + } +} + +impl Serialize for ButtonRequest { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::Contact => RawRequest { + contact: Some(True), + location: None, + } + .serialize(serializer), + Self::Location => RawRequest { + contact: None, + location: Some(True), + } + .serialize(serializer), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_no_request() { + let button = KeyboardButton { + text: String::from(""), + request: None, + }; + let expected = r#"{"text":""}"#; + let actual = serde_json::to_string(&button).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn serialize_request_contact() { + let button = KeyboardButton { + text: String::from(""), + request: Some(ButtonRequest::Contact), + }; + let expected = r#"{"text":"","request_contact":true}"#; + let actual = serde_json::to_string(&button).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn deserialize_no_request() { + let json = r#"{"text":""}"#; + let expected = KeyboardButton { + text: String::from(""), + request: None, + }; + let actual = serde_json::from_str(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn deserialize_request_contact() { + let json = r#"{"text":"","request_contact":true}"#; + let expected = KeyboardButton { + text: String::from(""), + request: Some(ButtonRequest::Contact), + }; + let actual = serde_json::from_str(json).unwrap(); + assert_eq!(expected, actual); + } } From 42b11f572e5c1689740cce082b4084ed81f517a7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Dec 2019 17:48:00 +0300 Subject: [PATCH 2/2] Add `\n` to the end og the `keyboard_button.rs` file --- src/types/keyboard_button.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 07f271c5..522a917e 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -140,4 +140,4 @@ mod tests { let actual = serde_json::from_str(json).unwrap(); assert_eq!(expected, actual); } -} \ No newline at end of file +}