From 69d503e6109a916754b49a342b72cbc83f534d72 Mon Sep 17 00:00:00 2001
From: Waffle <waffle.lapkin@gmail.com>
Date: Fri, 23 Oct 2020 21:32:57 +0300
Subject: [PATCH] add internal impl_payload macro

---
 src/local_macros.rs          | 201 +++++++++++++++++++++++++++++++++++
 src/payloads/get_me.rs       |  33 ++----
 src/payloads/mod.rs          |   3 +-
 src/payloads/send_message.rs | 138 ++++++------------------
 4 files changed, 245 insertions(+), 130 deletions(-)

diff --git a/src/local_macros.rs b/src/local_macros.rs
index 85c421f6..6cf4d543 100644
--- a/src/local_macros.rs
+++ b/src/local_macros.rs
@@ -107,6 +107,207 @@ macro_rules! req_future {
     };
 }
 
+/// Declares an item with a doc attribute computed by some macro expression.
+/// This allows documentation to be dynamically generated based on input.
+/// Necessary to work around https://github.com/rust-lang/rust/issues/52607.
+#[macro_use]
+macro_rules! calculated_doc {
+    (
+        $(
+            #[doc = $doc:expr]
+            $thing:item
+        )*
+    ) => (
+        $(
+            #[doc = $doc]
+            $thing
+        )*
+    );
+}
+
+/// Declare payload type, implement `Payload` trait amd ::new method for it, 
+/// declare setters trait and implement it for all type which have payload.
+#[macro_use]
+macro_rules! impl_payload {
+    (
+        $(
+            #[ $($method_meta:tt)* ]
+        )*
+        $vi:vis $Method:ident ($Setters:ident) => $Ret:ty {
+            $(
+                required {
+                    $(
+                        $(
+                            #[ $($field_meta:tt)* ]
+                        )*
+                        $v:vis $fields:ident : $FTy:ty $([$into:ident])?
+                        ,
+                    )*
+                }
+            )?
+
+            $(
+                optional {
+                    $(
+                        $(
+                            #[ $($opt_field_meta:tt)* ]
+                        )*
+                        $opt_v:vis $opt_fields:ident : $OptFTy:ty $([$opt_into:ident])?
+                    ),*
+                    $(,)?
+                }
+            )?
+        }
+    ) => {
+        $(
+            #[ $($method_meta)* ]
+        )*
+        $vi struct $Method {
+            $(
+                $(
+                    $(
+                        #[ $($field_meta)* ]
+                    )*
+                    $v $fields : $FTy,
+                )*
+            )?
+            $(
+                $(
+                    $(
+                        #[ $($opt_field_meta)* ]
+                    )*
+                    $opt_v $opt_fields : core::option::Option<$OptFTy>,
+                )*
+            )?
+        }
+
+        impl $Method {
+            $vi fn new($($($fields : impl_payload!(@into? $FTy $([$into])?)),*)?) -> Self {
+                Self {
+                    $(
+                        $(
+                            $fields: $fields $(.$into())?,
+                        )*
+                    )?
+                    $(
+                        $(
+                            $opt_fields: None,
+                        )*
+                    )?
+                }
+            }
+        }
+
+        impl $crate::requests::Payload for $Method {
+            type Output = $Ret;
+
+            const NAME: &'static str = stringify!($Method);
+        }
+
+        calculated_doc! {
+            #[doc = concat!(
+                "Setters for fields of [`",
+                stringify!($Method),
+                "`]"
+            )]
+            $vi trait $Setters: $crate::requests::HasPayload<Payload = $Method> + ::core::marker::Sized {
+                $(
+                    $(
+                        impl_payload! { @setter $Method $fields : $FTy $([$into])? }
+                    )*
+                )?
+                $(
+                    $(
+                        impl_payload! { @setter_opt $Method $opt_fields : $OptFTy $([$opt_into])? }
+                    )*
+                )?
+            }
+        }
+
+        impl<P> $Setters for P where P: crate::requests::HasPayload<Payload = $Method> {}
+    };
+    (@setter_opt $Method:ident $field:ident : $FTy:ty [into]) => {
+        calculated_doc! {
+            #[doc = concat!(
+                "Setter for [`",
+                stringify!($field),
+                "`](",
+                stringify!($Method),
+                "::",
+                stringify!($field),
+                ") field."
+            )]
+            fn $field<T>(mut self, value: T) -> Self
+            where
+                T: Into<$FTy>,
+            {
+                self.payload_mut().$field = Some(value.into());
+                self
+            }
+        }
+    };
+    (@setter_opt $Method:ident $field:ident : $FTy:ty) => {
+        calculated_doc! {
+            #[doc = concat!(
+                "Setter for [`",
+                stringify!($field),
+                "`](",
+                stringify!($Method),
+                "::",
+                stringify!($field),
+                ") field."
+            )]
+            fn $field(mut self, value: $FTy) -> Self {
+                self.payload_mut().$field = Some(value);
+                self
+            }
+        }
+    };
+    (@setter $Method:ident $field:ident : $FTy:ty [into]) => {
+        calculated_doc! {
+            #[doc = concat!(
+                "Setter for [`",
+                stringify!($field),
+                "`](",
+                stringify!($Method),
+                "::",
+                stringify!($field),
+                ") field."
+            )]
+            fn $field<T>(mut self, value: T) -> Self
+            where
+                T: Into<$FTy>,
+            {
+                self.payload_mut().$field = value.into();
+                self
+            }
+        }
+    };
+    (@setter $Method:ident $field:ident : $FTy:ty) => {
+        calculated_doc! {
+            #[doc = concat!(
+                "Setter for [`",
+                stringify!($field),
+                "`](",
+                stringify!($Method),
+                "::",
+                stringify!($field),
+                ") field."
+            )]
+            fn $field(mut self, value: $FTy) -> Self {
+                self.payload_mut().$field = value;
+                self
+            }
+        }
+    };
+    (@into? $T:ty [into]) => {
+        impl ::core::convert::Into<$T>
+    };
+    (@into? $T:ty) => {
+        $T
+    };
+}
+
 #[macro_use]
 macro_rules! serde_or_unknown {
     (
diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs
index cabd6f22..4df0b815 100644
--- a/src/payloads/get_me.rs
+++ b/src/payloads/get_me.rs
@@ -1,29 +1,12 @@
 use serde::{Deserialize, Serialize};
 
-use crate::{
-    requests::{HasPayload, Payload},
-    types::User,
-};
+use crate::types::User;
 
-/// A filter method for testing your bot's auth token. Requires no parameters.
-/// Returns basic information about the bot in form of a [`User`] object.
-///
-/// [`User`]: crate::types::User
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)]
-pub struct GetMe {}
-
-impl GetMe {
-    pub const fn new() -> Self {
-        GetMe {}
-    }
+impl_payload! {
+    /// A filter method for testing your bot's auth token. Requires no parameters.
+    /// Returns basic information about the bot in form of a [`User`] object.
+    ///
+    /// [`User`]: crate::types::User
+    #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)]
+    pub GetMe (GetMeSetters) => User {}
 }
-
-impl Payload for GetMe {
-    type Output = User;
-
-    const NAME: &'static str = "getMe";
-}
-
-pub trait GetMeSetters: HasPayload<Payload = GetMe> + Sized {}
-
-impl<P> GetMeSetters for P where P: HasPayload<Payload = GetMe> {}
diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs
index 22276002..33da224a 100644
--- a/src/payloads/mod.rs
+++ b/src/payloads/mod.rs
@@ -1,4 +1,5 @@
-/// Payloads - data types sended to relegram
+//! Payloads - data types sended to relegram
+
 pub mod setters;
 
 mod get_me;
diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs
index c2ed2e00..5262fb0d 100644
--- a/src/payloads/send_message.rs
+++ b/src/payloads/send_message.rs
@@ -1,112 +1,42 @@
 use serde::{Deserialize, Serialize};
 
-use crate::{
-    requests::{HasPayload, Payload},
-    types::{ChatId, Message, ParseMode, ReplyMarkup},
-};
+use crate::types::{ChatId, Message, ParseMode, ReplyMarkup};
 
-/// Use this method to send text messages.
-///
-/// On success, the sent [`Message`] is returned.
-///
-/// [`Message`]: crate::types::Message
-#[serde_with_macros::skip_serializing_none]
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
-pub struct SendMessage {
-    ///	Unique identifier for the target chat or username of the target channel
-    /// (in the format `@channelusername`)
-    pub chat_id: ChatId,
-    /// Text of the message to be sent
-    pub text: String,
-    /// Send [Markdown] or [HTML], if you want Telegram apps to show
-    /// [bold, italic, fixed-width text or inline URLs] in your bot's message.
+impl_payload! {
+    /// Use this method to send text messages.
     ///
-    /// [Markdown]: crate::types::ParseMode::Markdown
-    /// [HTML]: crate::types::ParseMode::HTML
-    /// [bold, italic, fixed-width text or inline URLs]:
-    /// crate::types::ParseMode
-    pub parse_mode: Option<ParseMode>,
-    /// Disables link previews for links in this message
-    pub disable_web_page_preview: Option<bool>,
-    /// Sends the message silently.
-    /// Users will receive a notification with no sound.
-    pub disable_notification: Option<bool>,
-    /// If the message is a reply, [id] of the original message
+    /// On success, the sent [`Message`] is returned.
     ///
-    /// [id]: crate::types::Message::id
-    pub reply_to_message_id: Option<i32>,
-    /// Additional interface options.
-    pub reply_markup: Option<ReplyMarkup>,
-}
-
-impl Payload for SendMessage {
-    type Output = Message;
-
-    const NAME: &'static str = "sendMessage";
-}
-
-impl SendMessage {
-    pub fn new<C, T>(chat_id: C, text: T) -> Self
-    where
-        C: Into<ChatId>,
-        T: Into<String>,
-    {
-        SendMessage {
-            chat_id: chat_id.into(),
-            text: text.into(),
-            parse_mode: None,
-            disable_web_page_preview: None,
-            disable_notification: None,
-            reply_to_message_id: None,
-            reply_markup: None,
+    /// [`Message`]: crate::types::Message
+    #[serde_with_macros::skip_serializing_none]
+    #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
+    pub SendMessage (SendMessageSetters) => Message {
+        required {
+            ///	Unique identifier for the target chat or username of the target channel
+            /// (in the format `@channelusername`)
+            pub chat_id: ChatId [into],
+            /// Text of the message to be sent
+            pub text: String [into],
+        }
+        optional {
+            /// Send [Markdown] or [HTML], if you want Telegram apps to show
+            /// [bold, italic, fixed-width text or inline URLs] in your bot's message.
+            ///
+            /// [Markdown]: crate::types::ParseMode::Markdown
+            /// [HTML]: crate::types::ParseMode::HTML
+            /// [bold, italic, fixed-width text or inline URLs]: crate::types::ParseMode
+            pub parse_mode: ParseMode,
+            /// Disables link previews for links in this message
+            pub disable_web_page_preview: bool,
+            /// Sends the message silently.
+            /// Users will receive a notification with no sound.
+            pub disable_notification: bool,
+            /// If the message is a reply, [id] of the original message
+            ///
+            /// [id]: crate::types::Message::id
+            pub reply_to_message_id: i32,
+            /// Additional interface options.
+            pub reply_markup: ReplyMarkup,
         }
     }
 }
-
-pub trait SendMessageSetters: HasPayload<Payload = SendMessage> + Sized {
-    fn chat_id<T>(mut self, value: T) -> Self
-    where
-        T: Into<ChatId>,
-    {
-        self.payload_mut().chat_id = value.into();
-        self
-    }
-
-    fn text<T>(mut self, value: T) -> Self
-    where
-        T: Into<String>, // TODO: into?
-    {
-        self.payload_mut().text = value.into();
-        self
-    }
-
-    fn parse_mode(mut self, value: ParseMode) -> Self {
-        self.payload_mut().parse_mode = Some(value);
-        self
-    }
-
-    fn disable_web_page_preview(mut self, value: bool) -> Self {
-        self.payload_mut().disable_web_page_preview = Some(value);
-        self
-    }
-
-    fn disable_notification(mut self, value: bool) -> Self {
-        self.payload_mut().disable_notification = Some(value);
-        self
-    }
-
-    fn reply_to_message_id(mut self, value: i32) -> Self {
-        self.payload_mut().reply_to_message_id = Some(value);
-        self
-    }
-
-    fn reply_markup<T>(mut self, value: T) -> Self
-    where
-        T: Into<ReplyMarkup>,
-    {
-        self.payload_mut().reply_markup = Some(value.into());
-        self
-    }
-}
-
-impl<P> SendMessageSetters for P where P: HasPayload<Payload = SendMessage> {}