Add dedicated Rgb struct to replace [u8; 3]

This commit is contained in:
Andrey Brusnik 2024-08-27 02:10:11 +04:00
parent 7b2de9ad39
commit 181b30304f
No known key found for this signature in database
GPG key ID: D33232F28CFF442C
13 changed files with 164 additions and 88 deletions

18
Cargo.lock generated
View file

@ -235,6 +235,12 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@ -1668,6 +1674,15 @@ dependencies = [
"windows-registry", "windows-registry",
] ]
[[package]]
name = "rgb"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.8"
@ -2302,6 +2317,7 @@ dependencies = [
"pretty_env_logger", "pretty_env_logger",
"rc-box", "rc-box",
"reqwest", "reqwest",
"rgb",
"ron", "ron",
"serde", "serde",
"serde_json", "serde_json",
@ -2791,7 +2807,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]

View file

@ -49,6 +49,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0` - MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0`
- Some dependencies was bumped: `reqwest` to `0.12.7` and `ron` to `0.8.1` - Some dependencies was bumped: `reqwest` to `0.12.7` and `ron` to `0.8.1`
- `tokio` version was explicitly specified as `1.39` and feature `io-util` was enabled for it - `tokio` version was explicitly specified as `1.39` and feature `io-util` was enabled for it
- `[u8; 3]` sometimes used for RGB values was replaced with dedicated `Rgb` struct: ([#1151][pr1151])
- `serde_rgb` module from `types.rs` file was removed
- `CreateForumTopic`, `ForumTopicCreated` and `ForumTopic` structs now use `Rgb` instead of `[u8; 3]` for `icon_color` field
- Added `rgb` crate dependency to Cargo.toml
- Added `Rgb` struct with `From` implementation for `RGB8` type from popular `rgb` crate
- Support for TBA 7.2 ([#1146](pr1146)) - Support for TBA 7.2 ([#1146](pr1146))
- Remove `flags` field from `StickerSet` struct - Remove `flags` field from `StickerSet` struct
@ -60,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[pr1134]: https://github.com/teloxide/teloxide/pull/1134 [pr1134]: https://github.com/teloxide/teloxide/pull/1134
[pr1146]: https://github.com/teloxide/teloxide/pull/1146 [pr1146]: https://github.com/teloxide/teloxide/pull/1146
[pr1147]: https://github.com/teloxide/teloxide/pull/1147 [pr1147]: https://github.com/teloxide/teloxide/pull/1147
[pr1151]: https://github.com/teloxide/teloxide/pull/1151
### Removed ### Removed

View file

@ -76,6 +76,7 @@ rc-box = "1.1.1"
chrono = { version = "0.4.32", default-features = false } chrono = { version = "0.4.32", default-features = false }
either = "1.6.1" either = "1.6.1"
bitflags = { version = "1.2" } bitflags = { version = "1.2" }
rgb = "0.8.48"
vecrem = { version = "0.1", optional = true } vecrem = { version = "0.1", optional = true }

View file

@ -2754,9 +2754,11 @@ Schema(
), ),
Param( Param(
name: "icon_color", name: "icon_color",
// FIXME: use an Rgb or something ty: RawTy("Rgb"),
ty: u32, descr: Doc(
descr: Doc(md: "Color of the topic icon in RGB format. Currently, must be one of 7322096 (0x6FB9F0), 16766590 (0xFFD67E), 13338331 (0xCB86DB), 9367192 (0x8EEE98), 16749490 (0xFF93B2), or 16478047 (0xFB6F5F)") md: "Color of the topic icon in RGB format. Currently, must be one of 7322096 (`0x6FB9F0`), 16766590 (`0xFFD67E`), 13338331 (`0xCB86DB`), 9367192 (`0x8EEE98`), 16749490 (`0xFF93B2`), or 16478047 (`0xFB6F5F`). To construct color from these values use [`Rgb::from_u32`]",
md_links: {"`Rgb::from_u32`": "crate::types::Rgb::from_u32"}
)
), ),
Param( Param(
name: "icon_custom_emoji_id", name: "icon_custom_emoji_id",

View file

@ -671,7 +671,7 @@ trait ErasableRequester<'a> {
&self, &self,
chat_id: Recipient, chat_id: Recipient,
name: String, name: String,
icon_color: u32, icon_color: Rgb,
icon_custom_emoji_id: String, icon_custom_emoji_id: String,
) -> ErasedRequest<'a, CreateForumTopic, Self::Err>; ) -> ErasedRequest<'a, CreateForumTopic, Self::Err>;
@ -1506,7 +1506,7 @@ where
&self, &self,
chat_id: Recipient, chat_id: Recipient,
name: String, name: String,
icon_color: u32, icon_color: Rgb,
icon_custom_emoji_id: String, icon_custom_emoji_id: String,
) -> ErasedRequest<'a, CreateForumTopic, Self::Err> { ) -> ErasedRequest<'a, CreateForumTopic, Self::Err> {
Requester::create_forum_topic(self, chat_id, name, icon_color, icon_custom_emoji_id).erase() Requester::create_forum_topic(self, chat_id, name, icon_color, icon_custom_emoji_id).erase()

View file

@ -6,7 +6,7 @@ use crate::{
requests::{JsonRequest, MultipartRequest}, requests::{JsonRequest, MultipartRequest},
types::{ types::{
BotCommand, BusinessConnectionId, ChatId, ChatPermissions, InlineQueryResult, InputFile, BotCommand, BusinessConnectionId, ChatId, ChatPermissions, InlineQueryResult, InputFile,
InputMedia, InputSticker, LabeledPrice, MessageId, Recipient, StickerFormat, ThreadId, InputMedia, InputSticker, LabeledPrice, MessageId, Recipient, Rgb, StickerFormat, ThreadId,
UserId, UserId,
}, },
Bot, Bot,
@ -686,7 +686,7 @@ impl Requester for Bot {
&self, &self,
chat_id: C, chat_id: C,
name: N, name: N,
icon_color: u32, icon_color: Rgb,
icon_custom_emoji_id: I, icon_custom_emoji_id: I,
) -> Self::CreateForumTopic ) -> Self::CreateForumTopic
where where

View file

@ -949,11 +949,11 @@ macro_rules! requester_forward {
(@method create_forum_topic $body:ident $ty:ident) => { (@method create_forum_topic $body:ident $ty:ident) => {
type CreateForumTopic = $ty![CreateForumTopic]; type CreateForumTopic = $ty![CreateForumTopic];
fn create_forum_topic<C, N, I>(&self, chat_id: C, name: N, icon_color: u32, icon_custom_emoji_id: I) -> Self::CreateForumTopic where C: Into<Recipient>, fn create_forum_topic<C, N, I>(&self, chat_id: C, name: N, icon_color: Rgb, icon_custom_emoji_id: I) -> Self::CreateForumTopic where C: Into<Recipient>,
N: Into<String>, N: Into<String>,
I: Into<String> { I: Into<String> {
let this = self; let this = self;
$body!(create_forum_topic this (chat_id: C, name: N, icon_color: u32, icon_custom_emoji_id: I)) $body!(create_forum_topic this (chat_id: C, name: N, icon_color: Rgb, icon_custom_emoji_id: I))
} }
}; };
(@method edit_forum_topic $body:ident $ty:ident) => { (@method edit_forum_topic $body:ident $ty:ident) => {

View file

@ -2,7 +2,7 @@
use serde::Serialize; use serde::Serialize;
use crate::types::{ForumTopic, Recipient}; use crate::types::{ForumTopic, Recipient, Rgb};
impl_payload! { impl_payload! {
/// Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the _can\_manage\_topics_ administrator rights. Returns information about the created topic as a `ForumTopic` object. /// Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the _can\_manage\_topics_ administrator rights. Returns information about the created topic as a `ForumTopic` object.
@ -13,8 +13,10 @@ impl_payload! {
pub chat_id: Recipient [into], pub chat_id: Recipient [into],
/// Topic name, 1-128 characters /// Topic name, 1-128 characters
pub name: String [into], pub name: String [into],
/// Color of the topic icon in RGB format. Currently, must be one of 7322096 (0x6FB9F0), 16766590 (0xFFD67E), 13338331 (0xCB86DB), 9367192 (0x8EEE98), 16749490 (0xFF93B2), or 16478047 (0xFB6F5F) /// Color of the topic icon in RGB format. Currently, must be one of 7322096 (`0x6FB9F0`), 16766590 (`0xFFD67E`), 13338331 (`0xCB86DB`), 9367192 (`0x8EEE98`), 16749490 (`0xFF93B2`), or 16478047 (`0xFB6F5F`). To construct color from these values use [`Rgb::from_u32`]
pub icon_color: u32, ///
/// [`Rgb::from_u32`]: crate::types::Rgb::from_u32
pub icon_color: Rgb,
/// Unique identifier of the custom emoji shown as the topic icon. Use `getForumTopicIconStickers` to get all allowed custom emoji identifiers. /// Unique identifier of the custom emoji shown as the topic icon. Use `getForumTopicIconStickers` to get all allowed custom emoji identifiers.
pub icon_custom_emoji_id: String [into], pub icon_custom_emoji_id: String [into],
} }

View file

@ -702,7 +702,7 @@ pub trait Requester {
&self, &self,
chat_id: C, chat_id: C,
name: N, name: N,
icon_color: u32, icon_color: Rgb,
icon_custom_emoji_id: I, icon_custom_emoji_id: I,
) -> Self::CreateForumTopic ) -> Self::CreateForumTopic
where where

View file

@ -128,6 +128,7 @@ pub use reply_markup::*;
pub use reply_parameters::*; pub use reply_parameters::*;
pub use request_id::*; pub use request_id::*;
pub use response_parameters::*; pub use response_parameters::*;
pub use rgb::*;
pub use sent_web_app_message::*; pub use sent_web_app_message::*;
pub use shared_user::*; pub use shared_user::*;
pub use shipping_address::*; pub use shipping_address::*;
@ -262,6 +263,7 @@ mod reply_markup;
mod reply_parameters; mod reply_parameters;
mod request_id; mod request_id;
mod response_parameters; mod response_parameters;
mod rgb;
mod sent_web_app_message; mod sent_web_app_message;
mod shared_user; mod shared_user;
mod shipping_address; mod shipping_address;
@ -544,67 +546,3 @@ pub(crate) mod option_msg_id_as_int {
} }
} }
} }
pub(crate) mod serde_rgb {
use serde::{de::Visitor, Deserializer, Serializer};
pub fn serialize<S: Serializer>(&this: &[u8; 3], s: S) -> Result<S::Ok, S::Error> {
s.serialize_u32(to_u32(this))
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 3], D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = [u8; 3];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an integer represeting an RGB color")
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(from_u32(v))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u32(v.try_into().map_err(|_| E::custom("rgb value doesn't fit u32"))?)
}
}
d.deserialize_u32(V)
}
fn to_u32([r, g, b]: [u8; 3]) -> u32 {
u32::from_be_bytes([0, r, g, b])
}
fn from_u32(rgb: u32) -> [u8; 3] {
let [_, r, g, b] = rgb.to_be_bytes();
[r, g, b]
}
#[test]
fn bytes() {
assert_eq!(to_u32([0xAA, 0xBB, 0xCC]), 0x00AABBCC);
assert_eq!(from_u32(0x00AABBCC), [0xAA, 0xBB, 0xCC]);
}
#[test]
fn json() {
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct Struct {
#[serde(with = "self")]
color: [u8; 3],
}
let json = format!(r#"{{"color":{}}}"#, 0x00AABBCC);
let Struct { color } = serde_json::from_str(&json).unwrap();
assert_eq!(color, [0xAA, 0xBB, 0xCC])
}
}

View file

@ -1,7 +1,7 @@
use crate::types::ThreadId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Rgb, ThreadId};
/// This object represents a forum topic. /// This object represents a forum topic.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated). /// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated).
@ -16,9 +16,7 @@ pub struct ForumTopic {
pub name: String, pub name: String,
/// Color of the topic icon in RGB format. /// Color of the topic icon in RGB format.
// FIXME: use/add a specialized rgb color type? pub icon_color: Rgb,
#[serde(with = "crate::types::serde_rgb")]
pub icon_color: [u8; 3],
/// Unique identifier of the custom emoji shown as the topic icon. /// Unique identifier of the custom emoji shown as the topic icon.
// FIXME: CustomEmojiId // FIXME: CustomEmojiId

View file

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::Rgb;
/// This object represents a service message about a new forum topic created in /// This object represents a service message about a new forum topic created in
/// the chat. /// the chat.
/// ///
@ -11,9 +13,7 @@ pub struct ForumTopicCreated {
pub name: String, pub name: String,
/// Color of the topic icon in RGB format. /// Color of the topic icon in RGB format.
// FIXME: use/add a specialized rgb color type? pub icon_color: Rgb,
#[serde(with = "crate::types::serde_rgb")]
pub icon_color: [u8; 3],
/// Unique identifier of the custom emoji shown as the topic icon. /// Unique identifier of the custom emoji shown as the topic icon.
// FIXME: CustomEmojiId // FIXME: CustomEmojiId
@ -22,7 +22,7 @@ pub struct ForumTopicCreated {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::types::ForumTopicCreated; use super::*;
#[test] #[test]
fn deserialization() { fn deserialization() {
@ -32,7 +32,7 @@ mod tests {
let event = serde_json::from_str::<ForumTopicCreated>(json).unwrap(); let event = serde_json::from_str::<ForumTopicCreated>(json).unwrap();
assert_eq!(event.name, "???"); assert_eq!(event.name, "???");
assert_eq!(event.icon_color, [0x8E, 0xEE, 0x98]); assert_eq!(event.icon_color, Rgb { r: 0x8E, g: 0xEE, b: 0x98 });
assert_eq!(event.icon_custom_emoji_id.as_deref(), Some("5312536423851630001")); assert_eq!(event.icon_custom_emoji_id.as_deref(), Some("5312536423851630001"));
} }
} }

View file

@ -0,0 +1,113 @@
use rgb::RGB8;
use serde::{de::Visitor, Deserialize, Serialize};
/// RGB color format
#[repr(C)]
#[derive(Clone, Copy, Debug)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Rgb {
/// Convert a [`Rgb`] struct into a big endian `u32` representing the RGB
/// color.
///
/// # Example
///
/// ```
/// use teloxide_core::types::Rgb;
/// assert_eq!(Rgb { r: 0xAA, g: 0xBB, b: 0xCC }.to_u32(), 0xAABBCC);
/// ```
///
/// [`Rgb`]: Rgb
pub fn to_u32(self) -> u32 {
u32::from_be_bytes([0, self.r, self.g, self.b])
}
/// Convert a big endian `u32` representing the RGB color into a [`Rgb`]
/// struct.
///
/// # Example
///
/// ```
/// use teloxide_core::types::Rgb;
/// assert_eq!(Rgb::from_u32(0xAABBCC), Rgb { r: 0xAA, g: 0xBB, b: 0xCC });
/// ```
///
/// [`Rgb`]: Rgb
pub fn from_u32(rgb: u32) -> Self {
let [_, r, g, b] = rgb.to_be_bytes();
Rgb { r, g, b }
}
}
impl Serialize for Rgb {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u32(self.to_u32())
}
}
impl<'de> Deserialize<'de> for Rgb {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V;
impl Visitor<'_> for V {
type Value = Rgb;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an integer represeting an RGB color")
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Self::Value::from_u32(v))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_u32(v.try_into().map_err(|_| E::custom("rgb value doesn't fit u32"))?)
}
}
deserializer.deserialize_u32(V)
}
}
impl From<RGB8> for Rgb {
fn from(color: RGB8) -> Self {
Rgb { r: color.r, g: color.g, b: color.b }
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use super::*;
#[test]
fn json() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Struct {
color: Rgb,
}
let json = format!(r#"{{"color":{}}}"#, 0x00AABBCC);
let Struct { color } = serde_json::from_str(&json).unwrap();
assert_eq!(color, Rgb { r: 0xAA, g: 0xBB, b: 0xCC })
}
}