Merge pull request #156 from teloxide/tolerant_updates_for_all

Make update deserialization fault tolerant by default
This commit is contained in:
Hirrolot 2022-01-12 15:19:24 +07:00 committed by GitHub
commit dad5d5d4b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 220 additions and 166 deletions

View file

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `media_group_id` field to `MediaDocument` and `MediaAudio` ([#139][pr139])
- `caption_entities` method to `InputMediaPhoto` ([#140][pr140])
- `User::is_anonymous` and `User::is_channel` functions ([#151][pr151])
- `UpdateKind::Error` ([#156][pr156])
[pr109]: https://github.com/teloxide/teloxide-core/pull/109
[pr116]: https://github.com/teloxide/teloxide-core/pull/116
@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[pr143]: https://github.com/teloxide/teloxide-core/pull/143
[pr151]: https://github.com/teloxide/teloxide-core/pull/151
[pr155]: https://github.com/teloxide/teloxide-core/pull/155
[pr156]: https://github.com/teloxide/teloxide-core/pull/156
[pr164]: https://github.com/teloxide/teloxide-core/pull/164
### Changed
@ -79,6 +81,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[issue473]: https://github.com/teloxide/teloxide/issues/473
[issue427]: https://github.com/teloxide/teloxide/issues/427
### Removed
- `get_updates_fault_tolerant` method and `SemiparsedVec` ([#156][pr156])
## 0.3.3 - 2021-08-03
### Fixed

View file

@ -106,8 +106,7 @@ where
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => f, fty
approve_chat_join_request, decline_chat_join_request => f, fty
}
}

View file

@ -113,8 +113,7 @@ where
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => f, fty
approve_chat_join_request, decline_chat_join_request => f, fty
}
}

View file

@ -182,8 +182,7 @@ where
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => fwd_erased, fty
approve_chat_join_request, decline_chat_join_request => fwd_erased, fty
}
}
@ -692,8 +691,6 @@ trait ErasableRequester<'a> {
user_id: i64,
target: TargetMessage,
) -> ErasedRequest<'a, GetGameHighScores, Self::Err>;
fn get_updates_fault_tolerant(&self) -> ErasedRequest<'a, GetUpdatesFaultTolerant, Self::Err>;
}
impl<'a, B> ErasableRequester<'a> for B
@ -1401,8 +1398,4 @@ where
) -> ErasedRequest<'a, GetGameHighScores, Self::Err> {
Requester::get_game_high_scores(self, user_id, target).erase()
}
fn get_updates_fault_tolerant(&self) -> ErasedRequest<'a, GetUpdatesFaultTolerant, Self::Err> {
Requester::get_updates_fault_tolerant(self).erase()
}
}

View file

@ -117,8 +117,7 @@ impl<B: Requester> Requester for DefaultParseMode<B> {
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => fid, fty
approve_chat_join_request, decline_chat_join_request => fid, fty
}
}

View file

@ -621,7 +621,7 @@ where
set_sticker_set_thumb, answer_shipping_query, answer_pre_checkout_query,
set_passport_data_errors, send_game, set_game_score, set_game_score_inline,
approve_chat_join_request, decline_chat_join_request,
get_game_high_scores, get_updates_fault_tolerant => fid, ftyid
get_game_high_scores => fid, ftyid
}
}

View file

@ -138,8 +138,7 @@ where
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => fwd_inner, fty
approve_chat_join_request, decline_chat_join_request => fwd_inner, fty
}
}

View file

@ -1106,13 +1106,4 @@ impl Requester for Bot {
{
Self::UnpinAllChatMessages::new(self.clone(), payloads::UnpinAllChatMessages::new(chat_id))
}
type GetUpdatesFaultTolerant = JsonRequest<payloads::GetUpdatesFaultTolerant>;
fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant {
Self::GetUpdatesFaultTolerant::new(
self.clone(),
payloads::GetUpdatesFaultTolerant(payloads::GetUpdates::new()),
)
}
}

View file

@ -1195,13 +1195,4 @@ macro_rules! requester_forward {
$body!(get_game_high_scores this (user_id: i64, target: T))
}
};
(@method get_updates_fault_tolerant $body:ident $ty:ident) => {
type GetUpdatesFaultTolerant = $ty![GetUpdatesFaultTolerant];
fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant {
let this = self;
$body!(get_updates_fault_tolerant this ())
}
};
}

View file

@ -213,7 +213,3 @@ pub use unpin_chat_message::{UnpinChatMessage, UnpinChatMessageSetters};
pub use upload_sticker_file::{UploadStickerFile, UploadStickerFileSetters};
// end of auto generated block
mod get_updates_fault_tolerant;
pub use get_updates_fault_tolerant::GetUpdatesFaultTolerant;

View file

@ -1,18 +0,0 @@
use serde::Serialize;
use crate::{
payloads::GetUpdates,
requests::Payload,
types::{SemiparsedVec, Update},
};
/// The fault tolerant version of [`GetUpdates`].
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)]
#[serde(transparent)]
pub struct GetUpdatesFaultTolerant(pub GetUpdates);
impl Payload for GetUpdatesFaultTolerant {
type Output = SemiparsedVec<Update>;
const NAME: &'static str = GetUpdates::NAME;
}

View file

@ -856,11 +856,6 @@ pub trait Requester {
fn get_game_high_scores<T>(&self, user_id: i64, target: T) -> Self::GetGameHighScores
where
T: Into<TargetMessage>;
type GetUpdatesFaultTolerant: Request<Payload = GetUpdatesFaultTolerant, Err = Self::Err>;
/// For Telegram documentation see [`GetUpdatesFaultTolerant`].
fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant;
}
macro_rules! fty {
@ -901,8 +896,7 @@ macro_rules! forward_all {
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => fwd_deref, fty
approve_chat_join_request, decline_chat_join_request => fwd_deref, fty
}
};
}
@ -998,7 +992,6 @@ where
set_sticker_set_thumb, send_invoice, answer_shipping_query,
answer_pre_checkout_query, set_passport_data_errors, send_game,
set_game_score, set_game_score_inline, get_game_high_scores,
approve_chat_join_request, decline_chat_join_request,
get_updates_fault_tolerant => fwd_either, fty_either
approve_chat_join_request, decline_chat_join_request => fwd_either, fty_either
}
}

View file

@ -214,12 +214,11 @@ mod passport_data;
mod passport_element_error;
mod passport_file;
pub use non_telegram_types::{country_code::*, currency::*, semiparsed_vec::*, until_date::*};
pub use non_telegram_types::{country_code::*, currency::*, until_date::*};
mod non_telegram_types {
pub(super) mod country_code;
pub(super) mod currency;
pub(crate) mod mime;
pub(super) mod semiparsed_vec;
pub(super) mod until_date;
}

View file

@ -1,55 +0,0 @@
use serde::de::DeserializeOwned;
use serde_json::{from_value, Value};
/// A vector of possibly unparsed JSON objects.
///
/// Similar to `Vec<T>` but if it fails to deserialize element, it just saves
/// `Err((serde_json::Value, serde_json::Error))`.
#[derive(Debug, serde::Deserialize)]
#[serde(from = "Vec<serde_json::Value>")]
#[serde(bound = "T: DeserializeOwned")]
pub struct SemiparsedVec<T>(pub Vec<Result<T, (serde_json::Value, serde_json::Error)>>);
impl<T: DeserializeOwned> From<Vec<serde_json::Value>> for SemiparsedVec<T> {
fn from(vec: Vec<Value>) -> Self {
Self(
vec.into_iter()
.map(|val| from_value(val.clone()).map_err(|e| (val, e)))
.collect(),
)
}
}
#[test]
fn test() {
use crate::types::Update;
let x: SemiparsedVec<Update> = serde_json::from_str(
r#"[{
"update_id": 923808447,
"message": {
"message_id": 361678,
"from": {
"id": 218485655,
"is_bot": false,
"first_name": "вафель",
"last_name": "🧇",
"username": "WaffleLapkin",
"language_code": "en"
},
"chat": {
"id": 218485655,
"first_name": "вафель",
"last_name": "🧇",
"username": "WaffleLapkin",
"type": "private"
},
"date": 1595860067,
"text": "s"
}
}]"#,
)
.unwrap();
assert!(x.0.first().unwrap().is_ok())
}

View file

@ -1,12 +1,11 @@
#![allow(clippy::large_enum_variant)]
use serde::{Deserialize, Serialize};
use serde::{de::MapAccess, Deserialize, Serialize, Serializer};
use serde_json::Value;
use crate::types::{
CallbackQuery, Chat, ChatJoinRequest, ChatMemberUpdated, ChosenInlineResult, InlineQuery,
Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, User,
};
use serde_json::Value;
/// This [object] represents an incoming update.
///
@ -30,28 +29,33 @@ pub struct Update {
}
impl Update {
/// Tries to parse `value` into `Update`, logging an error on failure.
///
/// It is used to implement update listeners.
pub fn try_parse(value: &Value) -> Result<Self, serde_json::Error> {
match serde_json::from_value(value.clone()) {
Ok(update) => Ok(update),
Err(error) => {
log::error!(
"Cannot parse an update.\nError: {:?}\nValue: {}\n\
This is a bug in teloxide-core, please open an issue here: \
https://github.com/teloxide/teloxide-core/issues.",
error,
value
);
Err(error)
}
pub fn user(&self) -> Option<&User> {
match &self.kind {
UpdateKind::Message(m) => m.from(),
UpdateKind::EditedMessage(m) => m.from(),
UpdateKind::CallbackQuery(query) => Some(&query.from),
UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from),
UpdateKind::InlineQuery(query) => Some(&query.from),
UpdateKind::ShippingQuery(query) => Some(&query.from),
UpdateKind::PreCheckoutQuery(query) => Some(&query.from),
UpdateKind::PollAnswer(answer) => Some(&answer.user),
_ => None,
}
}
pub fn chat(&self) -> Option<&Chat> {
match &self.kind {
UpdateKind::Message(m) => Some(&m.chat),
UpdateKind::EditedMessage(m) => Some(&m.chat),
UpdateKind::ChannelPost(p) => Some(&p.chat),
UpdateKind::EditedChannelPost(p) => Some(&p.chat),
UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, PartialEq)]
pub enum UpdateKind {
/// New incoming message of any kind — text, photo, sticker, etc.
Message(Message),
@ -113,31 +117,173 @@ pub enum UpdateKind {
/// can_invite_users administrator right in the chat to receive these
/// updates.
ChatJoinRequest(ChatJoinRequest),
/// An error that happened during deserialization.
///
/// This allows `teloxide` to continue working even if telegram adds a new
/// kind of updates.
Error(Value),
}
impl Update {
pub fn user(&self) -> Option<&User> {
match &self.kind {
UpdateKind::Message(m) => m.from(),
UpdateKind::EditedMessage(m) => m.from(),
UpdateKind::CallbackQuery(query) => Some(&query.from),
UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from),
UpdateKind::InlineQuery(query) => Some(&query.from),
UpdateKind::ShippingQuery(query) => Some(&query.from),
UpdateKind::PreCheckoutQuery(query) => Some(&query.from),
UpdateKind::PollAnswer(answer) => Some(&answer.user),
_ => None,
}
}
impl<'de> Deserialize<'de> for UpdateKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
pub fn chat(&self) -> Option<&Chat> {
match &self.kind {
UpdateKind::Message(m) => Some(&m.chat),
UpdateKind::EditedMessage(m) => Some(&m.chat),
UpdateKind::ChannelPost(p) => Some(&p.chat),
UpdateKind::EditedChannelPost(p) => Some(&p.chat),
UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat),
_ => None,
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = UpdateKind;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut tmp = None;
// Try to deserialize a borrowed-str key, or else try deserializing an owned
// string key
let k = map.next_key::<&str>().or_else(|_| {
map.next_key::<String>().map(|k| {
tmp = k;
tmp.as_deref()
})
});
if let Ok(Some(k)) = k {
let res = match k {
"message" => dbg!(map
.next_value::<Message>()
.map(UpdateKind::Message)
.map_err(|_| false)),
"edited_message" => map
.next_value::<Message>()
.map(UpdateKind::EditedMessage)
.map_err(|_| false),
"channel_post" => map
.next_value::<Message>()
.map(UpdateKind::ChannelPost)
.map_err(|_| false),
"edited_channel_post" => map
.next_value::<Message>()
.map(UpdateKind::EditedChannelPost)
.map_err(|_| false),
"inline_query" => map
.next_value::<InlineQuery>()
.map(UpdateKind::InlineQuery)
.map_err(|_| false),
"chosen_inline_result" => map
.next_value::<ChosenInlineResult>()
.map(UpdateKind::ChosenInlineResult)
.map_err(|_| false),
"callback_query" => map
.next_value::<CallbackQuery>()
.map(UpdateKind::CallbackQuery)
.map_err(|_| false),
"shipping_query" => map
.next_value::<ShippingQuery>()
.map(UpdateKind::ShippingQuery)
.map_err(|_| false),
"pre_checkout_query" => map
.next_value::<PreCheckoutQuery>()
.map(UpdateKind::PreCheckoutQuery)
.map_err(|_| false),
"poll" => map
.next_value::<Poll>()
.map(UpdateKind::Poll)
.map_err(|_| false),
"poll_answer" => map
.next_value::<PollAnswer>()
.map(UpdateKind::PollAnswer)
.map_err(|_| false),
"my_chat_member" => map
.next_value::<ChatMemberUpdated>()
.map(UpdateKind::MyChatMember)
.map_err(|_| false),
"chat_member" => map
.next_value::<ChatMemberUpdated>()
.map(UpdateKind::ChatMember)
.map_err(|_| false),
"chat_join_request" => map
.next_value::<ChatJoinRequest>()
.map(UpdateKind::ChatJoinRequest)
.map_err(|_| false),
_ => Err(true),
};
let value_available = match res {
Ok(ok) => return Ok(ok),
Err(e) => e,
};
let mut value = serde_json::Map::new();
value.insert(
k.to_owned(),
if value_available {
map.next_value::<Value>().unwrap_or(Value::Null)
} else {
Value::Null
},
);
while let Ok(Some((k, v))) = map.next_entry::<_, Value>() {
value.insert(k, v);
}
return Ok(UpdateKind::Error(Value::Object(value)));
}
Ok(UpdateKind::Error(Value::Object(<_>::default())))
}
}
deserializer.deserialize_any(Visitor)
}
}
impl Serialize for UpdateKind {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let name = "UpdateKind";
match self {
UpdateKind::Message(v) => s.serialize_newtype_variant(name, 0, "message", v),
UpdateKind::EditedMessage(v) => {
s.serialize_newtype_variant(name, 1, "edited_message", v)
}
UpdateKind::ChannelPost(v) => s.serialize_newtype_variant(name, 2, "channel_post", v),
UpdateKind::EditedChannelPost(v) => {
s.serialize_newtype_variant(name, 3, "edited_channel_post", v)
}
UpdateKind::InlineQuery(v) => s.serialize_newtype_variant(name, 4, "inline_query", v),
UpdateKind::ChosenInlineResult(v) => {
s.serialize_newtype_variant(name, 5, "chosen_inline_result", v)
}
UpdateKind::CallbackQuery(v) => {
s.serialize_newtype_variant(name, 6, "callback_query", v)
}
UpdateKind::ShippingQuery(v) => {
s.serialize_newtype_variant(name, 7, "shipping_query", v)
}
UpdateKind::PreCheckoutQuery(v) => {
s.serialize_newtype_variant(name, 8, "pre_checkout_query", v)
}
UpdateKind::Poll(v) => s.serialize_newtype_variant(name, 9, "poll", v),
UpdateKind::PollAnswer(v) => s.serialize_newtype_variant(name, 10, "poll_answer", v),
UpdateKind::MyChatMember(v) => {
s.serialize_newtype_variant(name, 11, "my_chat_member", v)
}
UpdateKind::ChatMember(v) => s.serialize_newtype_variant(name, 12, "chat_member", v),
UpdateKind::ChatJoinRequest(v) => {
s.serialize_newtype_variant(name, 13, "chat_join_request", v)
}
UpdateKind::Error(v) => v.serialize(s),
}
}
}
@ -330,4 +476,20 @@ mod test {
serde_json::from_str::<Update>(json).unwrap();
}
#[test]
fn new_update_kind_error() {
let json = r#"{
"new_update_kind": {"some_field_idk": 1},
"update_id": 1
}"#;
let Update { kind, .. } = serde_json::from_str(json).unwrap();
match kind {
// Deserialization failed successfully
UpdateKind::Error(_) => {}
_ => panic!("Expected error"),
}
}
}