use rvk::objects::Integer;
use serde_derive::Serialize;
use std::{
convert::TryFrom,
fmt::{Display, Error, Formatter},
str::FromStr,
};
#[derive(Debug, Serialize, Clone)]
pub struct Keyboard {
buttons: Vec<Vec<Button>>,
one_time: bool,
}
impl Default for Keyboard {
fn default() -> Self {
Self {
buttons: Vec::new(),
one_time: false,
}
}
}
impl Keyboard {
pub fn new(buttons: Vec<Vec<Button>>, one_time: bool) -> Self {
Self { buttons, one_time }
}
pub fn buttons(&self) -> &Vec<Vec<Button>> {
&self.buttons
}
pub fn one_time(&self) -> bool {
self.one_time
}
}
#[derive(Debug, Serialize, Clone)]
pub struct Button {
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<Color>,
action: Action,
}
impl Button {
#[deprecated(since = "2.0.0", note = "please use `text` instead")]
pub fn new(label: &str, color: Color, payload: Option<String>) -> Self {
Button::text(label, color, payload)
}
pub fn text(label: &str, color: Color, payload: Option<String>) -> Self {
Self {
color: Some(color),
action: Action::Text {
label: label.into(),
payload,
},
}
}
pub fn location(payload: Option<String>) -> Self {
Self {
color: None,
action: Action::Location { payload },
}
}
pub fn vk_pay(hash: impl Into<String>) -> Self {
Self {
color: None,
action: Action::VKPay { hash: hash.into() },
}
}
pub fn open_app(
app_id: Integer,
owner_id: Option<Integer>,
label: impl Into<String>,
hash: impl Into<String>,
) -> Self {
Self {
color: None,
action: Action::OpenApp {
app_id,
owner_id,
label: label.into(),
hash: hash.into(),
},
}
}
pub fn color(&self) -> Option<Color> {
self.color
}
pub fn action(&self) -> &Action {
&self.action
}
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "lowercase")]
#[serde(tag = "type")]
pub enum Action {
Text {
label: String,
#[serde(skip_serializing_if = "Option::is_none")]
payload: Option<String>,
},
Location {
#[serde(skip_serializing_if = "Option::is_none")]
payload: Option<String>,
},
VKPay {
hash: String,
},
#[serde(rename = "open_app")]
OpenApp {
app_id: Integer,
owner_id: Option<Integer>,
label: String,
hash: String,
},
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Color {
Primary,
Secondary,
Negative,
Positive,
}
impl Default for Color {
fn default() -> Self {
Color::Secondary
}
}
impl Display for Color {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
f.write_str(match self {
Color::Primary => "primary",
Color::Secondary => "secondary",
Color::Negative => "negative",
Color::Positive => "positive",
})
}
}
#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord)]
pub struct ColorFromStrError(String);
impl Display for ColorFromStrError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "unknown color: `{}`", self.0)
}
}
impl FromStr for Color {
type Err = ColorFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"primary" => Ok(Color::Primary),
"secondary" => Ok(Color::Secondary),
"negative" => Ok(Color::Negative),
"positive" => Ok(Color::Positive),
_ => Err(ColorFromStrError(s.into())),
}
}
}
impl TryFrom<&str> for Color {
type Error = <Color as FromStr>::Err;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod color {
use super::*;
fn test_display_parse(expected_str: &str, expected_color: Color) {
let color: Color = expected_str
.parse()
.expect(&format!("could not parse color: `{}`", expected_str));
assert_eq!(color, expected_color);
let str = format!("{}", color);
assert_eq!(str, expected_str);
}
#[test]
fn display_and_parse() {
test_display_parse("primary", Color::Primary);
test_display_parse("secondary", Color::Secondary);
test_display_parse("negative", Color::Negative);
test_display_parse("positive", Color::Positive);
}
#[test]
#[should_panic(expected = "unknown color")]
fn unknown() {
panic!("{}", "foo".parse::<Color>().unwrap_err());
}
}
mod keyboard {
use super::*;
use serde_json::json;
#[test]
fn empty() -> Result<(), serde_json::Error> {
let kbd = Keyboard::new(vec![], false);
assert_eq!(
serde_json::to_value(&kbd)?,
json!({
"one_time": false,
"buttons": [],
})
);
Ok(())
}
#[test]
fn full() -> Result<(), serde_json::Error> {
let payload = serde_json::to_string(&json!({"payload": "json"}))?;
let kbd = Keyboard::new(
vec![
vec![
Button::text("1", Color::Secondary, None),
Button::text("2", Color::Primary, Some(payload.clone())),
Button::text("3", Color::Negative, None),
Button::text("4", Color::Positive, None),
],
vec![Button::location(None)],
vec![Button::vk_pay("action=transfer-to-group&group_id=1&aid=10")],
vec![Button::open_app(1, Some(1), "My App", "test")],
],
true,
);
assert_eq!(
serde_json::to_value(&kbd)?,
json!({
"buttons":[
[
{"color":"secondary","action":{"type":"text","label":"1"}},
{"color":"primary","action":{"type":"text","label":"2","payload":payload}},
{"color":"negative","action":{"type":"text","label":"3"}},
{"color":"positive","action":{"type":"text","label":"4"}}
],
[{"action":{"type":"location"}}],
[{"action":{"type":"vkpay", "hash": "action=transfer-to-group&group_id=1&aid=10"}}],
[{"action":{"type":"open_app", "app_id": 1, "owner_id": 1, "label": "My App", "hash": "test"}}]
],
"one_time":true
})
);
Ok(())
}
}
}