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> {}