use crate::types::login_url::LoginUrl;
use serde::{Deserialize, Serialize};
use serde_json::{Error as JsonError, Value as JsonValue};
use std::{error::Error as StdError, fmt};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct InlineKeyboardMarkup {
inline_keyboard: Vec<Vec<InlineKeyboardButton>>,
}
impl InlineKeyboardMarkup {
pub fn from_vec(inline_keyboard: Vec<Vec<InlineKeyboardButton>>) -> Self {
InlineKeyboardMarkup { inline_keyboard }
}
pub fn row(mut self, row: Vec<InlineKeyboardButton>) -> Self {
self.inline_keyboard.push(row);
self
}
pub(crate) fn serialize(&self) -> Result<String, InlineKeyboardError> {
serde_json::to_string(self).map_err(InlineKeyboardError::SerializeMarkup)
}
}
impl From<Vec<Vec<InlineKeyboardButton>>> for InlineKeyboardMarkup {
fn from(keyboard: Vec<Vec<InlineKeyboardButton>>) -> InlineKeyboardMarkup {
InlineKeyboardMarkup::from_vec(keyboard)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InlineKeyboardButton {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
callback_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
switch_inline_query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
switch_inline_query_current_chat: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
callback_game: Option<JsonValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pay: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
login_url: Option<LoginUrl>,
}
impl InlineKeyboardButton {
pub fn with_url<S: Into<String>>(text: S, url: S) -> Self {
InlineKeyboardButton {
text: text.into(),
url: Some(url.into()),
callback_data: None,
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: None,
pay: None,
login_url: None,
}
}
pub fn with_callback_data<S: Into<String>>(text: S, callback_data: S) -> Self {
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: Some(callback_data.into()),
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: None,
pay: None,
login_url: None,
}
}
pub fn with_callback_data_struct<S: Into<String>, D: Serialize>(
text: S,
callback_data: &D,
) -> Result<Self, InlineKeyboardError> {
Ok(InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: Some(
serde_json::to_string(callback_data).map_err(InlineKeyboardError::SerializeCallbackData)?,
),
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: None,
pay: None,
login_url: None,
})
}
pub fn with_switch_inline_query<S: Into<String>>(text: S, switch_inline_query: S) -> Self {
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: None,
switch_inline_query: Some(switch_inline_query.into()),
switch_inline_query_current_chat: None,
callback_game: None,
pay: None,
login_url: None,
}
}
pub fn with_switch_inline_query_current_chat<S: Into<String>>(
text: S,
switch_inline_query_current_chat: S,
) -> Self {
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: None,
switch_inline_query: None,
switch_inline_query_current_chat: Some(switch_inline_query_current_chat.into()),
callback_game: None,
pay: None,
login_url: None,
}
}
pub fn with_callback_game<S: Into<String>>(text: S) -> Self {
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: None,
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: Some(serde_json::json!({})),
pay: None,
login_url: None,
}
}
pub fn with_pay<S: Into<String>>(text: S) -> Self {
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: None,
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: None,
pay: Some(true),
login_url: None,
}
}
pub fn with_login_url<T, U>(text: T, login_url: U) -> Self
where
T: Into<String>,
U: Into<LoginUrl>,
{
InlineKeyboardButton {
text: text.into(),
url: None,
callback_data: None,
switch_inline_query: None,
switch_inline_query_current_chat: None,
callback_game: None,
pay: None,
login_url: Some(login_url.into()),
}
}
}
#[derive(Debug)]
pub enum InlineKeyboardError {
SerializeCallbackData(JsonError),
SerializeMarkup(JsonError),
}
impl StdError for InlineKeyboardError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
use self::InlineKeyboardError::*;
Some(match self {
SerializeCallbackData(err) => err,
SerializeMarkup(err) => err,
})
}
}
impl fmt::Display for InlineKeyboardError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
use self::InlineKeyboardError::*;
match self {
SerializeCallbackData(err) => write!(out, "failed to serialize callback data: {}", err),
SerializeMarkup(err) => write!(out, "failed to serialize markup: {}", err),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ReplyMarkup;
#[derive(Serialize)]
struct CallbackData {
value: String,
}
#[test]
fn serialize() {
let callback_data = CallbackData {
value: String::from("cdstruct"),
};
let markup: ReplyMarkup = vec![vec![
InlineKeyboardButton::with_url("url", "tg://user?id=1"),
InlineKeyboardButton::with_callback_data("cd", "cd"),
InlineKeyboardButton::with_callback_data_struct("cd", &callback_data).unwrap(),
InlineKeyboardButton::with_switch_inline_query("siq", "siq"),
InlineKeyboardButton::with_switch_inline_query_current_chat("siqcc", "siqcc"),
InlineKeyboardButton::with_callback_game("cg"),
InlineKeyboardButton::with_pay("pay"),
InlineKeyboardButton::with_login_url("login url", "http://example.com"),
]]
.into();
let data = serde_json::to_value(&markup).unwrap();
assert_eq!(
data,
serde_json::json!({
"inline_keyboard": [
[
{"text":"url","url":"tg://user?id=1"},
{"text":"cd","callback_data":"cd"},
{"text":"cd","callback_data":"{\"value\":\"cdstruct\"}"},
{"text":"siq","switch_inline_query":"siq"},
{"text":"siqcc","switch_inline_query_current_chat":"siqcc"},
{"text":"cg","callback_game":{}},
{"text":"pay","pay":true},
{"text":"login url","login_url":{"url":"http://example.com"}}
]
]
})
);
}
#[test]
fn deserialize() {
let buttons: Vec<InlineKeyboardButton> = serde_json::from_value(serde_json::json!(
[
{"text":"url","url":"tg://user?id=1"},
{"text":"cd","callback_data":"cd"},
{"text":"cd","callback_data":"{\"value\":\"cdstruct\"}"},
{"text":"siq","switch_inline_query":"siq"},
{"text":"siqcc","switch_inline_query_current_chat":"siqcc"},
{"text":"cg","callback_game":{}},
{"text":"pay","pay":true},
{"text":"login url","login_url":{"url":"http://example.com"}}
]
))
.unwrap();
assert_eq!(buttons.len(), 8);
assert_eq!(buttons[0].text, "url");
assert_eq!(buttons[1].text, "cd");
assert_eq!(buttons[2].text, "cd");
assert_eq!(buttons[3].text, "siq");
assert_eq!(buttons[4].text, "siqcc");
assert_eq!(buttons[5].text, "cg");
assert_eq!(buttons[6].text, "pay");
assert_eq!(buttons[7].text, "login url");
}
}