lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

model.rs (23300B)


      1 use crate::error::RadrootsSimplexChatProtoError;
      2 use crate::version::RadrootsSimplexChatVersionRange;
      3 use alloc::boxed::Box;
      4 use alloc::collections::BTreeMap;
      5 use alloc::string::{String, ToString};
      6 use base64::Engine as _;
      7 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
      8 use serde::de::Error as _;
      9 use serde::{Deserialize, Deserializer, Serialize, Serializer};
     10 use serde_json::{Map, Value};
     11 
     12 pub type RadrootsSimplexChatObject = Map<String, Value>;
     13 
     14 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
     15 pub struct RadrootsSimplexChatBase64Url(String);
     16 
     17 impl RadrootsSimplexChatBase64Url {
     18     pub fn new(value: impl Into<String>) -> Result<Self, RadrootsSimplexChatProtoError> {
     19         let value = value.into();
     20         URL_SAFE_NO_PAD.decode(value.as_bytes()).map_err(|_| {
     21             RadrootsSimplexChatProtoError::InvalidBase64Url {
     22                 field: "base64url",
     23                 value: value.clone(),
     24             }
     25         })?;
     26         Ok(Self(value))
     27     }
     28 
     29     pub fn parse_field(
     30         value: String,
     31         field: &'static str,
     32     ) -> Result<Self, RadrootsSimplexChatProtoError> {
     33         URL_SAFE_NO_PAD.decode(value.as_bytes()).map_err(|_| {
     34             RadrootsSimplexChatProtoError::InvalidBase64Url {
     35                 field,
     36                 value: value.clone(),
     37             }
     38         })?;
     39         Ok(Self(value))
     40     }
     41 
     42     pub fn as_str(&self) -> &str {
     43         &self.0
     44     }
     45 }
     46 
     47 impl Serialize for RadrootsSimplexChatBase64Url {
     48     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     49     where
     50         S: Serializer,
     51     {
     52         serializer.serialize_str(self.as_str())
     53     }
     54 }
     55 
     56 impl<'de> Deserialize<'de> for RadrootsSimplexChatBase64Url {
     57     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     58     where
     59         D: Deserializer<'de>,
     60     {
     61         let value = <String as Deserialize>::deserialize(deserializer)?;
     62         Self::new(value).map_err(D::Error::custom)
     63     }
     64 }
     65 
     66 #[derive(Debug, Clone, PartialEq, Eq)]
     67 pub enum RadrootsSimplexChatPeerType {
     68     Human,
     69     Bot,
     70     Unknown(String),
     71 }
     72 
     73 impl Serialize for RadrootsSimplexChatPeerType {
     74     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     75     where
     76         S: Serializer,
     77     {
     78         serializer.serialize_str(match self {
     79             Self::Human => "human",
     80             Self::Bot => "bot",
     81             Self::Unknown(value) => value,
     82         })
     83     }
     84 }
     85 
     86 impl<'de> Deserialize<'de> for RadrootsSimplexChatPeerType {
     87     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     88     where
     89         D: Deserializer<'de>,
     90     {
     91         let value = <String as Deserialize>::deserialize(deserializer)?;
     92         Ok(match value.as_str() {
     93             "human" => Self::Human,
     94             "bot" => Self::Bot,
     95             _ => Self::Unknown(value),
     96         })
     97     }
     98 }
     99 
    100 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    101 pub struct RadrootsSimplexChatProfile {
    102     #[serde(rename = "displayName")]
    103     pub display_name: String,
    104     #[serde(rename = "fullName")]
    105     pub full_name: String,
    106     #[serde(skip_serializing_if = "Option::is_none")]
    107     pub image: Option<String>,
    108     #[serde(rename = "shortDescr", skip_serializing_if = "Option::is_none")]
    109     pub short_descr: Option<String>,
    110     #[serde(rename = "contactLink", skip_serializing_if = "Option::is_none")]
    111     pub contact_link: Option<String>,
    112     #[serde(rename = "peerType", skip_serializing_if = "Option::is_none")]
    113     pub peer_type: Option<RadrootsSimplexChatPeerType>,
    114     #[serde(skip_serializing_if = "Option::is_none")]
    115     pub preferences: Option<Value>,
    116     #[serde(flatten, default)]
    117     pub extra: RadrootsSimplexChatObject,
    118 }
    119 
    120 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    121 pub struct RadrootsSimplexChatMessageRef {
    122     #[serde(rename = "msgId", skip_serializing_if = "Option::is_none")]
    123     pub msg_id: Option<RadrootsSimplexChatBase64Url>,
    124     #[serde(rename = "sentAt")]
    125     pub sent_at: String,
    126     pub sent: bool,
    127     #[serde(rename = "memberId", skip_serializing_if = "Option::is_none")]
    128     pub member_id: Option<RadrootsSimplexChatBase64Url>,
    129     #[serde(flatten, default)]
    130     pub extra: RadrootsSimplexChatObject,
    131 }
    132 
    133 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    134 pub struct RadrootsSimplexChatMention {
    135     #[serde(rename = "memberId")]
    136     pub member_id: RadrootsSimplexChatBase64Url,
    137     #[serde(flatten, default)]
    138     pub extra: RadrootsSimplexChatObject,
    139 }
    140 
    141 #[derive(Debug, Clone, PartialEq)]
    142 pub enum RadrootsSimplexChatLinkContent {
    143     Page {
    144         extra: RadrootsSimplexChatObject,
    145     },
    146     Image {
    147         extra: RadrootsSimplexChatObject,
    148     },
    149     Video {
    150         duration: Option<i64>,
    151         extra: RadrootsSimplexChatObject,
    152     },
    153     Unknown {
    154         content_type: String,
    155         raw: RadrootsSimplexChatObject,
    156     },
    157 }
    158 
    159 impl Serialize for RadrootsSimplexChatLinkContent {
    160     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    161     where
    162         S: Serializer,
    163     {
    164         let mut object = RadrootsSimplexChatObject::new();
    165         match self {
    166             Self::Page { extra } => {
    167                 object.insert(String::from("type"), Value::String(String::from("page")));
    168                 object.extend(extra.clone());
    169             }
    170             Self::Image { extra } => {
    171                 object.insert(String::from("type"), Value::String(String::from("image")));
    172                 object.extend(extra.clone());
    173             }
    174             Self::Video { duration, extra } => {
    175                 object.insert(String::from("type"), Value::String(String::from("video")));
    176                 if let Some(duration) = duration {
    177                     object.insert(String::from("duration"), Value::from(*duration));
    178                 }
    179                 object.extend(extra.clone());
    180             }
    181             Self::Unknown { raw, .. } => {
    182                 object = raw.clone();
    183             }
    184         }
    185         object.serialize(serializer)
    186     }
    187 }
    188 
    189 impl<'de> Deserialize<'de> for RadrootsSimplexChatLinkContent {
    190     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    191     where
    192         D: Deserializer<'de>,
    193     {
    194         let mut raw = <RadrootsSimplexChatObject as Deserialize>::deserialize(deserializer)?;
    195         let content_type = match raw.remove("type") {
    196             Some(Value::String(value)) => value,
    197             Some(_) => return Err(D::Error::custom("invalid link content type")),
    198             None => return Err(D::Error::custom("missing link content type")),
    199         };
    200 
    201         Ok(match content_type.as_str() {
    202             "page" => Self::Page { extra: raw },
    203             "image" => Self::Image { extra: raw },
    204             "video" => {
    205                 let duration = match raw.remove("duration") {
    206                     Some(Value::Number(value)) => value.as_i64(),
    207                     Some(_) => return Err(D::Error::custom("invalid duration")),
    208                     None => None,
    209                 };
    210                 Self::Video {
    211                     duration,
    212                     extra: raw,
    213                 }
    214             }
    215             _ => {
    216                 raw.insert(String::from("type"), Value::String(content_type.clone()));
    217                 Self::Unknown { content_type, raw }
    218             }
    219         })
    220     }
    221 }
    222 
    223 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    224 pub struct RadrootsSimplexChatLinkPreview {
    225     pub uri: String,
    226     pub title: String,
    227     pub description: String,
    228     pub image: RadrootsSimplexChatBase64Url,
    229     #[serde(skip_serializing_if = "Option::is_none")]
    230     pub content: Option<RadrootsSimplexChatLinkContent>,
    231     #[serde(flatten, default)]
    232     pub extra: RadrootsSimplexChatObject,
    233 }
    234 
    235 #[derive(Debug, Clone, PartialEq)]
    236 pub enum RadrootsSimplexChatContent {
    237     Text {
    238         text: String,
    239         extra: RadrootsSimplexChatObject,
    240     },
    241     Link {
    242         text: String,
    243         preview: RadrootsSimplexChatLinkPreview,
    244         extra: RadrootsSimplexChatObject,
    245     },
    246     Image {
    247         text: String,
    248         image: RadrootsSimplexChatBase64Url,
    249         extra: RadrootsSimplexChatObject,
    250     },
    251     Video {
    252         text: String,
    253         image: RadrootsSimplexChatBase64Url,
    254         duration: i64,
    255         extra: RadrootsSimplexChatObject,
    256     },
    257     Voice {
    258         text: String,
    259         duration: i64,
    260         extra: RadrootsSimplexChatObject,
    261     },
    262     File {
    263         text: String,
    264         extra: RadrootsSimplexChatObject,
    265     },
    266     Unknown {
    267         content_type: String,
    268         text: Option<String>,
    269         raw: RadrootsSimplexChatObject,
    270     },
    271 }
    272 
    273 impl Serialize for RadrootsSimplexChatContent {
    274     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    275     where
    276         S: Serializer,
    277     {
    278         let mut object = RadrootsSimplexChatObject::new();
    279         match self {
    280             Self::Text { text, extra } => {
    281                 object.insert(String::from("type"), Value::String(String::from("text")));
    282                 object.insert(String::from("text"), Value::String(text.clone()));
    283                 object.extend(extra.clone());
    284             }
    285             Self::Link {
    286                 text,
    287                 preview,
    288                 extra,
    289             } => {
    290                 object.insert(String::from("type"), Value::String(String::from("link")));
    291                 object.insert(String::from("text"), Value::String(text.clone()));
    292                 object.insert(
    293                     String::from("preview"),
    294                     serde_json::to_value(preview).map_err(serde::ser::Error::custom)?,
    295                 );
    296                 object.extend(extra.clone());
    297             }
    298             Self::Image { text, image, extra } => {
    299                 object.insert(String::from("type"), Value::String(String::from("image")));
    300                 object.insert(String::from("text"), Value::String(text.clone()));
    301                 object.insert(
    302                     String::from("image"),
    303                     Value::String(image.as_str().to_string()),
    304                 );
    305                 object.extend(extra.clone());
    306             }
    307             Self::Video {
    308                 text,
    309                 image,
    310                 duration,
    311                 extra,
    312             } => {
    313                 object.insert(String::from("type"), Value::String(String::from("video")));
    314                 object.insert(String::from("text"), Value::String(text.clone()));
    315                 object.insert(
    316                     String::from("image"),
    317                     Value::String(image.as_str().to_string()),
    318                 );
    319                 object.insert(String::from("duration"), Value::from(*duration));
    320                 object.extend(extra.clone());
    321             }
    322             Self::Voice {
    323                 text,
    324                 duration,
    325                 extra,
    326             } => {
    327                 object.insert(String::from("type"), Value::String(String::from("voice")));
    328                 object.insert(String::from("text"), Value::String(text.clone()));
    329                 object.insert(String::from("duration"), Value::from(*duration));
    330                 object.extend(extra.clone());
    331             }
    332             Self::File { text, extra } => {
    333                 object.insert(String::from("type"), Value::String(String::from("file")));
    334                 object.insert(String::from("text"), Value::String(text.clone()));
    335                 object.extend(extra.clone());
    336             }
    337             Self::Unknown { raw, .. } => {
    338                 object = raw.clone();
    339             }
    340         }
    341 
    342         object.serialize(serializer)
    343     }
    344 }
    345 
    346 impl<'de> Deserialize<'de> for RadrootsSimplexChatContent {
    347     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    348     where
    349         D: Deserializer<'de>,
    350     {
    351         let mut raw = <RadrootsSimplexChatObject as Deserialize>::deserialize(deserializer)?;
    352         let content_type = match raw.remove("type") {
    353             Some(Value::String(value)) => value,
    354             Some(_) => return Err(D::Error::custom("invalid content type")),
    355             None => return Err(D::Error::custom("missing content type")),
    356         };
    357 
    358         Ok(match content_type.as_str() {
    359             "text" => Self::Text {
    360                 text: expect_string::<D>(&mut raw, "text")?,
    361                 extra: raw,
    362             },
    363             "link" => Self::Link {
    364                 text: expect_string::<D>(&mut raw, "text")?,
    365                 preview: serde_json::from_value(
    366                     expect_value(&mut raw, "preview").map_err(D::Error::custom)?,
    367                 )
    368                 .map_err(D::Error::custom)?,
    369                 extra: raw,
    370             },
    371             "image" => Self::Image {
    372                 text: expect_string::<D>(&mut raw, "text")?,
    373                 image: expect_base64url::<D>(&mut raw, "image")?,
    374                 extra: raw,
    375             },
    376             "video" => Self::Video {
    377                 text: expect_string::<D>(&mut raw, "text")?,
    378                 image: expect_base64url::<D>(&mut raw, "image")?,
    379                 duration: expect_i64::<D>(&mut raw, "duration")?,
    380                 extra: raw,
    381             },
    382             "voice" => Self::Voice {
    383                 text: expect_string::<D>(&mut raw, "text")?,
    384                 duration: expect_i64::<D>(&mut raw, "duration")?,
    385                 extra: raw,
    386             },
    387             "file" => Self::File {
    388                 text: expect_string::<D>(&mut raw, "text")?,
    389                 extra: raw,
    390             },
    391             _ => {
    392                 let text = match raw.get("text") {
    393                     Some(Value::String(value)) => Some(value.clone()),
    394                     _ => None,
    395                 };
    396                 raw.insert(String::from("type"), Value::String(content_type.clone()));
    397                 Self::Unknown {
    398                     content_type,
    399                     text,
    400                     raw,
    401                 }
    402             }
    403         })
    404     }
    405 }
    406 
    407 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    408 pub struct RadrootsSimplexChatFileDescription {
    409     #[serde(rename = "fileDescrText")]
    410     pub file_descr_text: String,
    411     #[serde(rename = "fileDescrPartNo")]
    412     pub file_descr_part_no: i64,
    413     #[serde(rename = "fileDescrComplete")]
    414     pub file_descr_complete: bool,
    415     #[serde(flatten, default)]
    416     pub extra: RadrootsSimplexChatObject,
    417 }
    418 
    419 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    420 pub struct RadrootsSimplexChatFileInvitation {
    421     #[serde(rename = "fileName")]
    422     pub file_name: String,
    423     #[serde(rename = "fileSize")]
    424     pub file_size: u32,
    425     #[serde(rename = "fileDigest", skip_serializing_if = "Option::is_none")]
    426     pub file_digest: Option<RadrootsSimplexChatBase64Url>,
    427     #[serde(rename = "fileConnReq", skip_serializing_if = "Option::is_none")]
    428     pub file_conn_req: Option<String>,
    429     #[serde(rename = "fileDescr", skip_serializing_if = "Option::is_none")]
    430     pub file_descr: Option<RadrootsSimplexChatFileDescription>,
    431     #[serde(flatten, default)]
    432     pub extra: RadrootsSimplexChatObject,
    433 }
    434 
    435 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    436 pub struct RadrootsSimplexChatQuotedMessage {
    437     #[serde(rename = "msgRef")]
    438     pub msg_ref: RadrootsSimplexChatMessageRef,
    439     pub content: RadrootsSimplexChatContent,
    440     #[serde(flatten, default)]
    441     pub extra: RadrootsSimplexChatObject,
    442 }
    443 
    444 #[derive(Debug, Clone, PartialEq)]
    445 pub enum RadrootsSimplexChatForwardMarker {
    446     Flag,
    447     Object(RadrootsSimplexChatObject),
    448 }
    449 
    450 #[derive(Debug, Clone, PartialEq)]
    451 pub enum RadrootsSimplexChatScope {
    452     Member {
    453         member_id: RadrootsSimplexChatBase64Url,
    454         extra: RadrootsSimplexChatObject,
    455     },
    456     Unknown(Value),
    457 }
    458 
    459 impl Serialize for RadrootsSimplexChatScope {
    460     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    461     where
    462         S: Serializer,
    463     {
    464         match self {
    465             Self::Member { member_id, extra } => {
    466                 let mut data = RadrootsSimplexChatObject::new();
    467                 data.insert(
    468                     String::from("memberId"),
    469                     Value::String(member_id.as_str().to_string()),
    470                 );
    471                 data.extend(extra.clone());
    472 
    473                 let mut object = RadrootsSimplexChatObject::new();
    474                 object.insert(String::from("type"), Value::String(String::from("member")));
    475                 object.insert(String::from("data"), Value::Object(data));
    476                 object.serialize(serializer)
    477             }
    478             Self::Unknown(value) => value.serialize(serializer),
    479         }
    480     }
    481 }
    482 
    483 impl<'de> Deserialize<'de> for RadrootsSimplexChatScope {
    484     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    485     where
    486         D: Deserializer<'de>,
    487     {
    488         let value = Value::deserialize(deserializer)?;
    489         let original = value.clone();
    490         let Value::Object(mut object) = value else {
    491             return Ok(Self::Unknown(original));
    492         };
    493 
    494         let Some(Value::String(scope_type)) = object.remove("type") else {
    495             return Ok(Self::Unknown(original));
    496         };
    497 
    498         if scope_type != "member" {
    499             return Ok(Self::Unknown(original));
    500         }
    501 
    502         let Some(Value::Object(mut data)) = object.remove("data") else {
    503             return Ok(Self::Unknown(original));
    504         };
    505 
    506         let member_id = expect_base64url::<D>(&mut data, "memberId")?;
    507         Ok(Self::Member {
    508             member_id,
    509             extra: data,
    510         })
    511     }
    512 }
    513 
    514 #[derive(Debug, Clone, PartialEq)]
    515 pub enum RadrootsSimplexChatContainerKind {
    516     Simple,
    517     Quote(Box<RadrootsSimplexChatQuotedMessage>),
    518     Comment(RadrootsSimplexChatMessageRef),
    519     Forward(RadrootsSimplexChatForwardMarker),
    520 }
    521 
    522 #[derive(Debug, Clone, PartialEq)]
    523 pub struct RadrootsSimplexChatMessageContainer {
    524     pub kind: RadrootsSimplexChatContainerKind,
    525     pub content: RadrootsSimplexChatContent,
    526     pub mentions: BTreeMap<String, RadrootsSimplexChatMention>,
    527     pub file: Option<RadrootsSimplexChatFileInvitation>,
    528     pub ttl: Option<i64>,
    529     pub live: Option<bool>,
    530     pub scope: Option<RadrootsSimplexChatScope>,
    531     pub extra: RadrootsSimplexChatObject,
    532 }
    533 
    534 #[derive(Debug, Clone, PartialEq)]
    535 pub struct RadrootsSimplexChatMessageContentReference {
    536     pub msg_id: RadrootsSimplexChatBase64Url,
    537     pub content: RadrootsSimplexChatContent,
    538 }
    539 
    540 #[derive(Debug, Clone, PartialEq)]
    541 pub struct RadrootsSimplexChatNoParamsEvent {
    542     pub extra: RadrootsSimplexChatObject,
    543 }
    544 
    545 #[derive(Debug, Clone, PartialEq)]
    546 pub struct RadrootsSimplexChatContactEvent {
    547     pub profile: RadrootsSimplexChatProfile,
    548     pub contact_req_id: Option<RadrootsSimplexChatBase64Url>,
    549     pub welcome_msg_id: Option<RadrootsSimplexChatBase64Url>,
    550     pub request_message: Option<RadrootsSimplexChatMessageContentReference>,
    551     pub extra: RadrootsSimplexChatObject,
    552 }
    553 
    554 #[derive(Debug, Clone, PartialEq)]
    555 pub struct RadrootsSimplexChatInfoEvent {
    556     pub profile: RadrootsSimplexChatProfile,
    557     pub extra: RadrootsSimplexChatObject,
    558 }
    559 
    560 #[derive(Debug, Clone, PartialEq)]
    561 pub struct RadrootsSimplexChatProbeEvent {
    562     pub probe: RadrootsSimplexChatBase64Url,
    563     pub extra: RadrootsSimplexChatObject,
    564 }
    565 
    566 #[derive(Debug, Clone, PartialEq)]
    567 pub struct RadrootsSimplexChatProbeCheckEvent {
    568     pub probe_hash: RadrootsSimplexChatBase64Url,
    569     pub extra: RadrootsSimplexChatObject,
    570 }
    571 
    572 #[derive(Debug, Clone, PartialEq)]
    573 pub struct RadrootsSimplexChatMsgNewEvent {
    574     pub container: RadrootsSimplexChatMessageContainer,
    575 }
    576 
    577 #[derive(Debug, Clone, PartialEq)]
    578 pub struct RadrootsSimplexChatFileDescriptionEvent {
    579     pub msg_id: RadrootsSimplexChatBase64Url,
    580     pub file_descr: RadrootsSimplexChatFileDescription,
    581     pub extra: RadrootsSimplexChatObject,
    582 }
    583 
    584 #[derive(Debug, Clone, PartialEq)]
    585 pub struct RadrootsSimplexChatMsgUpdateEvent {
    586     pub msg_id: RadrootsSimplexChatBase64Url,
    587     pub content: RadrootsSimplexChatContent,
    588     pub mentions: BTreeMap<String, RadrootsSimplexChatMention>,
    589     pub ttl: Option<i64>,
    590     pub live: Option<bool>,
    591     pub scope: Option<RadrootsSimplexChatScope>,
    592     pub extra: RadrootsSimplexChatObject,
    593 }
    594 
    595 #[derive(Debug, Clone, PartialEq)]
    596 pub struct RadrootsSimplexChatDeleteEvent {
    597     pub msg_id: RadrootsSimplexChatBase64Url,
    598     pub member_id: Option<RadrootsSimplexChatBase64Url>,
    599     pub scope: Option<RadrootsSimplexChatScope>,
    600     pub extra: RadrootsSimplexChatObject,
    601 }
    602 
    603 #[derive(Debug, Clone, PartialEq)]
    604 pub struct RadrootsSimplexChatFileAcceptEvent {
    605     pub file_name: String,
    606     pub extra: RadrootsSimplexChatObject,
    607 }
    608 
    609 #[derive(Debug, Clone, PartialEq)]
    610 pub struct RadrootsSimplexChatFileAcceptInvitationEvent {
    611     pub msg_id: RadrootsSimplexChatBase64Url,
    612     pub file_conn_req: Option<String>,
    613     pub file_name: String,
    614     pub extra: RadrootsSimplexChatObject,
    615 }
    616 
    617 #[derive(Debug, Clone, PartialEq)]
    618 pub struct RadrootsSimplexChatFileCancelEvent {
    619     pub msg_id: RadrootsSimplexChatBase64Url,
    620     pub extra: RadrootsSimplexChatObject,
    621 }
    622 
    623 #[derive(Debug, Clone, PartialEq)]
    624 pub enum RadrootsSimplexChatEvent {
    625     Contact(RadrootsSimplexChatContactEvent),
    626     Info(RadrootsSimplexChatInfoEvent),
    627     InfoProbe(RadrootsSimplexChatProbeEvent),
    628     InfoProbeCheck(RadrootsSimplexChatProbeCheckEvent),
    629     InfoProbeOk(RadrootsSimplexChatProbeEvent),
    630     MsgNew(Box<RadrootsSimplexChatMsgNewEvent>),
    631     MsgFileDescr(RadrootsSimplexChatFileDescriptionEvent),
    632     MsgUpdate(RadrootsSimplexChatMsgUpdateEvent),
    633     MsgDel(RadrootsSimplexChatDeleteEvent),
    634     FileAcpt(RadrootsSimplexChatFileAcceptEvent),
    635     FileAcptInv(RadrootsSimplexChatFileAcceptInvitationEvent),
    636     FileCancel(RadrootsSimplexChatFileCancelEvent),
    637     DirectDel(RadrootsSimplexChatNoParamsEvent),
    638     Ok(RadrootsSimplexChatNoParamsEvent),
    639     Unknown {
    640         event: String,
    641         params: RadrootsSimplexChatObject,
    642     },
    643 }
    644 
    645 #[derive(Debug, Clone, PartialEq)]
    646 pub struct RadrootsSimplexChatMessage {
    647     pub version: Option<RadrootsSimplexChatVersionRange>,
    648     pub msg_id: Option<RadrootsSimplexChatBase64Url>,
    649     pub event: RadrootsSimplexChatEvent,
    650 }
    651 
    652 pub(crate) fn expect_value(
    653     map: &mut RadrootsSimplexChatObject,
    654     field: &'static str,
    655 ) -> Result<Value, RadrootsSimplexChatProtoError> {
    656     map.remove(field)
    657         .ok_or(RadrootsSimplexChatProtoError::MissingField(field))
    658 }
    659 
    660 fn expect_string<'de, D>(
    661     map: &mut RadrootsSimplexChatObject,
    662     field: &'static str,
    663 ) -> Result<String, D::Error>
    664 where
    665     D: Deserializer<'de>,
    666 {
    667     match expect_value(map, field).map_err(D::Error::custom)? {
    668         Value::String(value) => Ok(value),
    669         _ => Err(D::Error::custom(
    670             RadrootsSimplexChatProtoError::InvalidField(field),
    671         )),
    672     }
    673 }
    674 
    675 fn expect_i64<'de, D>(
    676     map: &mut RadrootsSimplexChatObject,
    677     field: &'static str,
    678 ) -> Result<i64, D::Error>
    679 where
    680     D: Deserializer<'de>,
    681 {
    682     match expect_value(map, field).map_err(D::Error::custom)? {
    683         Value::Number(value) => value
    684             .as_i64()
    685             .ok_or_else(|| D::Error::custom(RadrootsSimplexChatProtoError::InvalidField(field))),
    686         _ => Err(D::Error::custom(
    687             RadrootsSimplexChatProtoError::InvalidField(field),
    688         )),
    689     }
    690 }
    691 
    692 fn expect_base64url<'de, D>(
    693     map: &mut RadrootsSimplexChatObject,
    694     field: &'static str,
    695 ) -> Result<RadrootsSimplexChatBase64Url, D::Error>
    696 where
    697     D: Deserializer<'de>,
    698 {
    699     expect_string::<D>(map, field).and_then(|value| {
    700         RadrootsSimplexChatBase64Url::parse_field(value, field).map_err(D::Error::custom)
    701     })
    702 }