Fix Sticker and StickerSet with StickerFormatFlags type

This commit is contained in:
Сырцев Вадим Игоревич 2024-06-09 18:32:57 +03:00
parent c24fdf173e
commit af4af72c2a
2 changed files with 102 additions and 71 deletions

View file

@ -36,12 +36,15 @@ pub struct Sticker {
#[serde(flatten)] #[serde(flatten)]
pub kind: StickerKind, pub kind: StickerKind,
/// Format of this sticker - raster/`.webp`, animated/`.tgs` or /// Format flags of this sticker:
/// video/`.webm`. ///
/// `(is_animated, is_video)` == `(false, false)` - raster/`.webp` or
/// `is_animated == true` - animated/`.tgs` or
/// `is_video == true` - video/`.webm`.
/// ///
/// In other words this represents how the sticker is encoded. /// In other words this represents how the sticker is encoded.
#[serde(flatten)] #[serde(flatten)]
pub format: StickerFormat, pub flags: StickerFormatFlags,
/// Sticker thumbnail in the `.webp` or `.jpg` format. /// Sticker thumbnail in the `.webp` or `.jpg` format.
pub thumbnail: Option<PhotoSize>, pub thumbnail: Option<PhotoSize>,
@ -98,9 +101,17 @@ pub enum StickerType {
CustomEmoji, CustomEmoji,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct StickerFormatFlags {
/// True, if the sticker is animated
pub is_animated: bool,
/// True, if the sticker is a video sticker
pub is_video: bool,
}
/// Format of a [`Sticker`] - regular/webp, animated/tgs or video/webm. /// Format of a [`Sticker`] - regular/webp, animated/tgs or video/webm.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "StickerFormatRaw", into = "StickerFormatRaw")] #[serde(rename_all = "snake_case")]
pub enum StickerFormat { pub enum StickerFormat {
/// Image in `.png` or `.webp` format. /// Image in `.png` or `.webp` format.
Static, Static,
@ -136,36 +147,45 @@ impl Deref for Sticker {
} }
impl Sticker { impl Sticker {
/// Returns the format of the [`Sticker`] based on the [`self.flags`] values
///
/// [`Sticker`]: Sticker
/// [`self.flags`]: Sticker::flags
#[must_use]
pub fn format(&self) -> StickerFormat {
self.flags.format()
}
/// Returns `true` is this is a "normal" raster sticker. /// Returns `true` is this is a "normal" raster sticker.
/// ///
/// Alias to [`self.format.is_raster()`]. /// Alias to [`self.format().is_raster()`].
/// ///
/// [`self.format.is_static()`]: StickerFormat::is_static /// [`self.format().is_static()`]: StickerFormat::is_static
#[must_use] #[must_use]
pub fn is_static(&self) -> bool { pub fn is_static(&self) -> bool {
self.format.is_static() self.format().is_static()
} }
/// Returns `true` is this is an [animated] sticker. /// Returns `true` is this is an [animated] sticker.
/// ///
/// Alias to [`self.format.is_animated()`]. /// Alias to [`self.format().is_animated()`].
/// ///
/// [`self.format.is_animated()`]: StickerFormat::is_animated /// [`self.format().is_animated()`]: StickerFormat::is_animated
/// [animated]: https://telegram.org/blog/animated-stickers /// [animated]: https://telegram.org/blog/animated-stickers
#[must_use] #[must_use]
pub fn is_animated(&self) -> bool { pub fn is_animated(&self) -> bool {
self.format.is_animated() self.format().is_animated()
} }
/// Returns `true` is this is a [video] sticker. /// Returns `true` is this is a [video] sticker.
/// ///
/// Alias to [`self.format.is_video()`]. /// Alias to [`self.format().is_video()`].
/// ///
/// [`self.format.is_video()`]: StickerFormat::is_video /// [`self.format().is_video()`]: StickerFormat::is_video
/// [video]: https://telegram.org/blog/video-stickers-better-reactions /// [video]: https://telegram.org/blog/video-stickers-better-reactions
#[must_use] #[must_use]
pub fn is_video(&self) -> bool { pub fn is_video(&self) -> bool {
self.format.is_video() self.format().is_video()
} }
} }
@ -261,6 +281,17 @@ impl StickerType {
} }
} }
impl StickerFormatFlags {
pub fn format(&self) -> StickerFormat {
match (self.is_animated, self.is_video) {
(false, false) => StickerFormat::Static,
(true, false) => StickerFormat::Animated,
(false, true) => StickerFormat::Video,
(true, true) => panic!("`is_animated` and `is_video` flags present at the same time"),
}
}
}
impl StickerFormat { impl StickerFormat {
/// Returns `true` if the sticker format is [`Static`]. /// Returns `true` if the sticker format is [`Static`].
/// ///
@ -287,42 +318,31 @@ impl StickerFormat {
} }
} }
#[derive(Serialize, Deserialize)]
struct StickerFormatRaw {
is_animated: bool,
is_video: bool,
}
impl TryFrom<StickerFormatRaw> for StickerFormat {
type Error = &'static str;
fn try_from(
StickerFormatRaw { is_animated, is_video }: StickerFormatRaw,
) -> Result<Self, Self::Error> {
let ret = match (is_animated, is_video) {
(false, false) => Self::Static,
(true, false) => Self::Animated,
(false, true) => Self::Video,
(true, true) => return Err("`is_animated` and `is_video` present at the same time"),
};
Ok(ret)
}
}
impl From<StickerFormat> for StickerFormatRaw {
fn from(kind: StickerFormat) -> Self {
match kind {
StickerFormat::Static => Self { is_animated: false, is_video: false },
StickerFormat::Animated => Self { is_animated: true, is_video: false },
StickerFormat::Video => Self { is_animated: false, is_video: true },
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::types::{MaskPoint, Sticker, StickerFormat, StickerType}; use crate::types::{MaskPoint, Sticker, StickerFormat, StickerFormatFlags, StickerType};
#[test]
fn sticker_format_serde() {
// Ser
assert_eq!(serde_json::to_string(&StickerFormat::Static).unwrap(), r#""static""#);
assert_eq!(serde_json::to_string(&StickerFormat::Animated).unwrap(), r#""animated""#);
assert_eq!(serde_json::to_string(&StickerFormat::Video).unwrap(), r#""video""#);
// De
assert_eq!(
serde_json::from_str::<StickerFormat>(r#""static""#).unwrap(),
StickerFormat::Static
);
assert_eq!(
serde_json::from_str::<StickerFormat>(r#""animated""#).unwrap(),
StickerFormat::Animated
);
assert_eq!(
serde_json::from_str::<StickerFormat>(r#""video""#).unwrap(),
StickerFormat::Video
);
}
#[test] #[test]
fn mask_serde() { fn mask_serde() {
@ -412,35 +432,40 @@ mod tests {
} }
#[test] #[test]
fn sticker_format_serde() { fn sticker_format_flags_serde() {
{ {
let json = r#"{"is_animated":false,"is_video":false}"#; let json = r#"{"is_animated":false,"is_video":false}"#;
let fmt: StickerFormat = serde_json::from_str(json).unwrap(); let fmt_flags: StickerFormatFlags = serde_json::from_str(json).unwrap();
assert_eq!(fmt, StickerFormat::Static); assert_eq!(fmt_flags.format(), StickerFormat::Static);
let json2 = serde_json::to_string(&fmt).unwrap(); let json2 = serde_json::to_string(&fmt_flags).unwrap();
assert_eq!(json, json2); assert_eq!(json, json2);
} }
{ {
let json = r#"{"is_animated":true,"is_video":false}"#; let json = r#"{"is_animated":true,"is_video":false}"#;
let fmt: StickerFormat = serde_json::from_str(json).unwrap(); let fmt_flags: StickerFormatFlags = serde_json::from_str(json).unwrap();
assert_eq!(fmt, StickerFormat::Animated); assert_eq!(fmt_flags.format(), StickerFormat::Animated);
let json2 = serde_json::to_string(&fmt).unwrap(); let json2 = serde_json::to_string(&fmt_flags).unwrap();
assert_eq!(json, json2); assert_eq!(json, json2);
} }
{ {
let json = r#"{"is_animated":false,"is_video":true}"#; let json = r#"{"is_animated":false,"is_video":true}"#;
let fmt: StickerFormat = serde_json::from_str(json).unwrap(); let fmt_flags: StickerFormatFlags = serde_json::from_str(json).unwrap();
assert_eq!(fmt, StickerFormat::Video); assert_eq!(fmt_flags.format(), StickerFormat::Video);
let json2 = serde_json::to_string(&fmt).unwrap(); let json2 = serde_json::to_string(&fmt_flags).unwrap();
assert_eq!(json, json2); assert_eq!(json, json2);
} }
}
#[test]
#[should_panic]
fn wrong_sticker_format_flags_serde() {
{ {
let json = r#"{"is_animated":true,"is_video":true}"#; let json = r#"{"is_animated":true,"is_video":true}"#;
let fmt: Result<StickerFormat, _> = serde_json::from_str(json); let fmt_flags: StickerFormatFlags = serde_json::from_str(json).unwrap();
assert!(fmt.is_err()); fmt_flags.format();
} }
} }
} }

View file

@ -2,7 +2,7 @@ use std::ops::Deref;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{PhotoSize, Sticker, StickerFormat, StickerType}; use crate::types::{PhotoSize, Sticker, StickerFormat, StickerFormatFlags, StickerType};
/// This object represents a sticker set. /// This object represents a sticker set.
/// ///
@ -20,9 +20,9 @@ pub struct StickerSet {
#[serde(flatten)] #[serde(flatten)]
pub kind: StickerType, pub kind: StickerType,
/// Sticker format shared by all stickers in this set. /// Sticker format flags shared by all stickers in this set.
#[serde(flatten)] #[serde(flatten)]
pub format: StickerFormat, pub flags: StickerFormatFlags,
/// List of all set stickers. /// List of all set stickers.
pub stickers: Vec<Sticker>, pub stickers: Vec<Sticker>,
@ -50,36 +50,42 @@ impl Deref for StickerSet {
} }
impl StickerSet { impl StickerSet {
/// Returns the format of the stickers in this set
#[must_use]
pub fn format(&self) -> StickerFormat {
self.flags.format()
}
/// Returns `true` is this is a "normal" raster sticker. /// Returns `true` is this is a "normal" raster sticker.
/// ///
/// Alias to [`self.format.is_static()`]. /// Alias to [`self.format().is_static()`].
/// ///
/// [`self.format.is_static()`]: StickerFormat::is_static /// [`self.format().is_static()`]: StickerFormat::is_static
#[must_use] #[must_use]
pub fn is_static(&self) -> bool { pub fn is_static(&self) -> bool {
self.format.is_static() self.format().is_static()
} }
/// Returns `true` is this is an [animated] sticker. /// Returns `true` is this is an [animated] sticker.
/// ///
/// Alias to [`self.format.is_animated()`]. /// Alias to [`self.format().is_animated()`].
/// ///
/// [`self.format.is_animated()`]: StickerFormat::is_animated /// [`self.format().is_animated()`]: StickerFormat::is_animated
/// [animated]: https://telegram.org/blog/animated-stickers /// [animated]: https://telegram.org/blog/animated-stickers
#[must_use] #[must_use]
pub fn is_animated(&self) -> bool { pub fn is_animated(&self) -> bool {
self.format.is_animated() self.format().is_animated()
} }
/// Returns `true` is this is a [video] sticker. /// Returns `true` is this is a [video] sticker.
/// ///
/// Alias to [`self.format.is_video()`]. /// Alias to [`self.format().is_video()`].
/// ///
/// [`self.format.is_video()`]: StickerFormat::is_video /// [`self.format().is_video()`]: StickerFormat::is_video
/// [video]: https://telegram.org/blog/video-stickers-better-reactions /// [video]: https://telegram.org/blog/video-stickers-better-reactions
#[must_use] #[must_use]
pub fn is_video(&self) -> bool { pub fn is_video(&self) -> bool {
self.format.is_video() self.format().is_video()
} }
} }