Add reference implementation of payloads

This commit is contained in:
Waffle 2019-11-05 19:32:22 +03:00
parent 7e821f2401
commit fa220a6883
9 changed files with 440 additions and 1 deletions

View file

@ -17,6 +17,7 @@ pin-project = "0.4.0-alpha.7"
futures-preview = "0.3.0-alpha.19"
async-trait = "0.1.13"
thiserror = "1.0.2"
serde_with_macros = "1.0.1"
[features]
default = []

View file

@ -21,9 +21,16 @@ pub trait Payload: DynMethod {
/// this type has _little_ overhead, so prefer using [json], [multipart] or
/// [simple] requests when possible.
///
/// See [GetMe], [GetUpdates], [SendMessage] and [SendAnimation] for reference
/// implementations.
///
/// [json]: crate::requests::json::Request
/// [multipart]: crate::requests::multipart::Request
/// [simple]: crate::requests::simple::Request
/// [GetMe]: crate::requests::payloads::GetMe
/// [GetUpdates]: crate::requests::payloads::GetUpdates
/// [SendMessage]: crate::requests::payloads::SendMessage
/// [SendAnimation]: crate::requests::payloads::SendAnimation
#[must_use = "requests do nothing until sent"]
pub struct Request<'b, O> {
bot: &'b Bot,

View file

@ -9,7 +9,11 @@ pub trait Payload: Serialize + Method {}
///
/// Note: params will be sent to telegram using [`application/json`]
///
/// See [GetUpdates] and [SendMessage] for reference implementations.
///
/// [`application/json`]: https://core.telegram.org/bots/api#making-requests
/// [GetUpdates]: crate::requests::payloads::GetUpdates
/// [SendMessage]: crate::requests::payloads::SendMessage
#[must_use = "requests do nothing until sent"]
pub struct Request<'b, P> {
bot: &'b Bot,

View file

@ -12,7 +12,10 @@ pub trait Payload: Method {
///
/// Note: params will be sent to telegram using [`multipart/form-data`]
///
/// See [SendAnimation] for reference implementation.
///
/// [`multipart/form-data`]: https://core.telegram.org/bots/api#making-requests
/// [SendAnimation]: crate::requests::payloads::SendAnimation
#[must_use = "requests do nothing until sent"]
pub struct Request<'b, P> {
bot: &'b Bot,

View file

@ -0,0 +1,23 @@
use crate::{
requests::{Method, dynamic},
types::User,
};
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)]
/// 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
pub struct GetMe;
impl Method for GetMe {
type Output = User;
const METHOD: &'static str = "getMe";
}
impl dynamic::Payload for GetMe {
fn kind(&self) -> dynamic::Kind {
dynamic::Kind::Simple
}
}

View file

@ -0,0 +1,122 @@
use async_trait::async_trait;
use crate::{
requests::{ResponseResult, json, Method, dynamic},
types::Update,
};
/// Use this method to receive incoming updates using long polling ([wiki]).
/// An array ([`Vec`]) of [`Update`]s is returned.
///
/// **Notes:**
/// 1. This method will not work if an outgoing webhook is set up.
/// 2. In order to avoid getting duplicate updates,
/// recalculate offset after each server response.
///
/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
/// [Update]: crate::types::Update
/// [Vec]: std::alloc::Vec
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub struct GetUpdates {
/// Identifier of the first update to be returned. Must be greater by one
/// than the highest among the identifiers of previously received updates.
/// By default, updates starting with the earliest unconfirmed update are
/// returned. An update is considered confirmed as soon as [`GetUpdates`] is
/// called with an [`offset`] higher than its [`id`]. The negative offset
/// can be specified to retrieve updates starting from `-offset` update from
/// the end of the updates queue. All previous updates will forgotten.
///
/// [`GetUpdates`]: self::GetUpdates
/// [`offset`]: self::GetUpdates::offset
/// [`id`]: crate::types::Update::id
pub offset: Option<i32>,
/// Limits the number of updates to be retrieved.
/// Values between `1`—`100` are accepted. Defaults to `100`.
pub limit: Option<u8>,
/// Timeout in seconds for long polling. Defaults to `0`,
/// i.e. usual short polling. Should be positive, short polling should be
/// used for testing purposes only.
pub timeout: Option<u32>,
/// List the types of updates you want your bot to receive.
/// For example, specify [[`Message`], [`EditedChannelPost`],
/// [`CallbackQuery`]] to only receive updates of these types.
/// See [`AllowedUpdate`] for a complete list of available update types.
///
/// Specify an empty list to receive all updates regardless of type
/// (default). If not specified, the previous setting will be used.
///
/// **Note:**
/// This parameter doesn't affect updates created before the call to the
/// [`GetUpdates`], so unwanted updates may be received for a short period
/// of time.
///
/// [`Message`]: self::AllowedUpdate::Message
/// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost
/// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery
/// [`AllowedUpdate`]: self::AllowedUpdate
/// [`GetUpdates`]: self::GetUpdates
pub allowed_updates: Option<Vec<AllowedUpdate>>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AllowedUpdate {
Message,
EditedMessage,
ChannelPost,
EditedChannelPost,
InlineQuery,
ChosenInlineResult,
CallbackQuery,
}
impl Method for GetUpdates {
type Output = Vec<Update>;
const METHOD: &'static str = "getUpdates";
}
impl json::Payload for GetUpdates {}
impl dynamic::Payload for GetUpdates {
fn kind(&self) -> dynamic::Kind {
dynamic::Kind::Json(serde_json::to_string(self).unwrap())
}
}
impl GetUpdates {
pub fn new() -> Self {
Self {
offset: None,
limit: None,
timeout: None,
allowed_updates: None,
}
}
}
impl json::Request<'_, GetUpdates> {
pub fn offset(mut self, value: i32) -> Self {
self.payload.offset = Some(value);
self
}
pub fn limit(mut self, value: u8) -> Self {
self.payload.limit = Some(value);
self
}
pub fn timeout(mut self, value: u32) -> Self {
self.payload.timeout = Some(value);
self
}
pub fn allowed_updates<T>(mut self, value: T) -> Self
where
T: Into<Vec<AllowedUpdate>>, // TODO: into or other trait?
{
self.payload.allowed_updates = Some(value.into());
self
}
}

View file

@ -0,0 +1,162 @@
use reqwest::multipart::Form;
use crate::{
requests::{ResponseResult, multipart, Method, dynamic, form_builder::FormBuilder},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video
/// without sound).
///
/// On success, the sent Message is returned.
///
/// Bots can currently send animation files of up to 50 MB in size, this limit
/// may be changed in the future.
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub struct SendAnimation {
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`)
pub chat_id: ChatId,
/// Animation to send.
pub animation: InputFile,
/// Duration of sent animation in seconds
pub duration: Option<u32>,
/// Animation width
pub width: Option<u32>,
/// Animation height
pub height: Option<u32>,
/// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. The thumbnail should be in JPEG
/// format and less than 200 kB in size. A thumbnails width and height
/// should not exceed 320. Ignored if the file is not uploaded using
/// [`InputFile::File`]. Thumbnails cant be reused and can be only
/// uploaded as a new file, with [`InputFile::File`]
///
/// [`InputFile::File`]: crate::types::InputFile::File
pub thumb: Option<InputFile>,
/// Animation caption, `0`-`1024` characters
pub caption: Option<String>,
/// Send [Markdown] or [HTML], if you want Telegram apps to show
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
///
/// [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>,
/// 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
///
/// [id]: crate::types::Message::id
pub reply_to_message_id: Option<i32>,
/// Additional interface options
pub reply_markup: Option<ReplyMarkup>,
}
impl Method for SendAnimation {
type Output = Message;
const METHOD: &'static str = "sendAnimation";
}
impl multipart::Payload for SendAnimation {
fn payload(&self) -> Form {
FormBuilder::new()
.add("chat_id", &self.chat_id)
.add("animation", &self.animation)
.add("duration", &self.duration)
.add("width", &self.width)
.add("height", &self.height)
.add("thumb", &self.thumb)
.add("caption", &self.caption)
.add("parse_mode", &self.parse_mode)
.add("disable_notification", &self.disable_notification)
.add("reply_to_message_id", &self.reply_to_message_id)
.add("reply_markup", &self.reply_markup)
.build()
}
}
impl dynamic::Payload for SendAnimation {
fn kind(&self) -> dynamic::Kind {
dynamic::Kind::Multipart(multipart::Payload::payload(self))
}
}
impl SendAnimation {
pub fn new<C>(chat_id: C, animation: InputFile) -> Self
where
C: Into<ChatId>,
{
Self {
chat_id: chat_id.into(),
animation,
duration: None,
width: None,
height: None,
thumb: None,
caption: None,
parse_mode: None,
disable_notification: None,
reply_to_message_id: None,
reply_markup: None,
}
}
}
impl multipart::Request<'_, SendAnimation> {
pub fn chat_id<T>(mut self, value: T) -> Self
where
T: Into<ChatId>,
{
self.payload.chat_id = value.into();
self
}
pub fn duration(mut self, value: u32) -> Self {
self.payload.duration = Some(value);
self
}
pub fn width(mut self, value: u32) -> Self {
self.payload.width = Some(value);
self
}
pub fn height(mut self, value: u32) -> Self {
self.payload.height = Some(value);
self
}
pub fn thumb(mut self, value: InputFile) -> Self {
self.payload.thumb = Some(value);
self
}
pub fn caption<T>(mut self, value: T) -> Self
where
T: Into<String>,
{
self.payload.caption = Some(value.into());
self
}
pub fn parse_mode(mut self, value: ParseMode) -> Self {
self.payload.parse_mode = Some(value);
self
}
pub fn disable_notification(mut self, value: bool) -> Self {
self.payload.disable_notification = Some(value);
self
}
pub fn reply_to_message_id(mut self, value: i32) -> Self {
self.payload.reply_to_message_id = Some(value);
self
}
pub fn reply_markup<T>(mut self, value: T) -> Self
where
T: Into<ReplyMarkup>,
{
self.payload.reply_markup = Some(value.into());
self
}
}

View file

@ -0,0 +1,115 @@
use crate::{
requests::{ResponseResult, json, Method, dynamic},
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.
///
/// [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
///
/// [id]: crate::types::Message::id
pub reply_to_message_id: Option<i32>,
/// Additional interface options.
pub reply_markup: Option<ReplyMarkup>,
}
impl Method for SendMessage {
type Output = Message;
const METHOD: &'static str = "sendMessage";
}
impl json::Payload for SendMessage {}
impl dynamic::Payload for SendMessage {
fn kind(&self) -> dynamic::Kind {
dynamic::Kind::Json(serde_json::to_string(self).unwrap())
}
}
impl SendMessage {
pub fn new<C, S>(chat_id: C, text: S) -> Self
where
C: Into<ChatId>,
S: Into<String>, // TODO: into?
{
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,
}
}
}
impl json::Request<'_, SendMessage> {
pub fn chat_id<T>(mut self, value: T) -> Self
where
T: Into<ChatId>,
{
self.payload.chat_id = value.into();
self
}
pub fn text<T>(mut self, value: T) -> Self
where
T: Into<String>, // TODO: into?
{
self.payload.text = value.into();
self
}
pub fn parse_mode(mut self, value: ParseMode) -> Self {
self.payload.parse_mode = Some(value);
self
}
pub fn disable_web_page_preview(mut self, value: bool) -> Self {
self.payload.disable_web_page_preview = Some(value);
self
}
pub fn disable_notification(mut self, value: bool) -> Self {
self.payload.disable_notification = Some(value.into());
self
}
pub fn reply_to_message_id(mut self, value: i32) -> Self {
self.payload.reply_to_message_id = Some(value);
self
}
pub fn reply_markup<T>(mut self, value: T) -> Self
where
T: Into<ReplyMarkup>,
{
self.payload.reply_markup = Some(value.into());
self
}
}

View file

@ -9,7 +9,9 @@ use std::marker::PhantomData;
///
/// NOTE: Currently where is only one request without params - [GetMe]
///
/// [GetMe]: // TODO
/// See [GetMe] for reference implementation.
///
/// [GetMe]: crate::requests::payloads::GetMe
#[must_use = "requests do nothing until sent"]
pub struct Request<'b, M> {
bot: &'b Bot,