1use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 ClientError, ClientFailure, ClientStatus, ExternalId, InlineId, RandomId, TransactionIdentity,
13 TransactionState, validate_token,
14};
15
16#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct AuthToken(String);
20
21impl AuthToken {
22 pub const MAX_LEN: usize = 8192;
24
25 pub fn try_new(value: impl AsRef<str>) -> Result<Self, ClientError> {
27 Ok(Self(
28 validate_token("auth_token", value.as_ref(), Self::MAX_LEN)?.to_owned(),
29 ))
30 }
31
32 pub fn expose_secret(&self) -> &str {
36 &self.0
37 }
38}
39
40impl fmt::Debug for AuthToken {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.write_str("AuthToken(\"[redacted]\")")
43 }
44}
45
46#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(tag = "type", rename_all = "snake_case")]
49pub enum AuthCredential {
50 AccessToken {
52 token: AuthToken,
54 },
55}
56
57impl AuthCredential {
58 pub fn access_token(&self) -> &AuthToken {
60 match self {
61 Self::AccessToken { token } => token,
62 }
63 }
64}
65
66impl fmt::Debug for AuthCredential {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::AccessToken { .. } => f
70 .debug_struct("AccessToken")
71 .field("token", &"[redacted]")
72 .finish(),
73 }
74 }
75}
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
79#[serde(tag = "type", rename_all = "snake_case")]
80pub enum PeerRef {
81 User {
83 user_id: InlineId,
85 },
86 Chat {
88 chat_id: InlineId,
90 },
91 Thread {
93 thread_id: InlineId,
95 },
96}
97
98#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct ConnectRequest {
101 pub auth: AuthCredential,
103 pub account_namespace: Option<String>,
105}
106
107impl ConnectRequest {
108 pub fn new(auth: AuthCredential) -> Self {
110 Self {
111 auth,
112 account_namespace: None,
113 }
114 }
115
116 pub fn with_account_namespace(mut self, namespace: impl Into<String>) -> Self {
118 self.account_namespace = Some(namespace.into());
119 self
120 }
121}
122
123impl fmt::Debug for ConnectRequest {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 f.debug_struct("ConnectRequest")
126 .field("auth", &self.auth)
127 .field(
128 "account_namespace",
129 &self.account_namespace.as_ref().map(|_| "[redacted]"),
130 )
131 .finish()
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(rename_all = "snake_case")]
138pub enum AuthContactKind {
139 Email,
141 Phone,
143}
144
145impl AuthContactKind {
146 pub const fn as_str(self) -> &'static str {
148 match self {
149 Self::Email => "email",
150 Self::Phone => "phone",
151 }
152 }
153}
154
155#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
157pub struct AuthStartRequest {
158 pub contact: String,
160 pub kind: AuthContactKind,
162 pub device_name: Option<String>,
164}
165
166impl fmt::Debug for AuthStartRequest {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.debug_struct("AuthStartRequest")
169 .field("contact", &"[redacted]")
170 .field("kind", &self.kind)
171 .field("device_name", &self.device_name)
172 .finish()
173 }
174}
175
176#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
178pub struct AuthStartResult {
179 pub existing_user: bool,
181 pub needs_invite_code: bool,
183 pub challenge_token: Option<String>,
185}
186
187impl fmt::Debug for AuthStartResult {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 f.debug_struct("AuthStartResult")
190 .field("existing_user", &self.existing_user)
191 .field("needs_invite_code", &self.needs_invite_code)
192 .field(
193 "challenge_token",
194 &self.challenge_token.as_ref().map(|_| "[redacted]"),
195 )
196 .finish()
197 }
198}
199
200#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct AuthVerifyRequest {
203 pub contact: String,
205 pub kind: AuthContactKind,
207 pub code: String,
209 pub challenge_token: Option<String>,
211 pub device_name: Option<String>,
213 pub account_namespace: Option<String>,
215}
216
217impl fmt::Debug for AuthVerifyRequest {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.debug_struct("AuthVerifyRequest")
220 .field("contact", &"[redacted]")
221 .field("kind", &self.kind)
222 .field("code", &"[redacted]")
223 .field(
224 "challenge_token",
225 &self.challenge_token.as_ref().map(|_| "[redacted]"),
226 )
227 .field("device_name", &self.device_name)
228 .field(
229 "account_namespace",
230 &self.account_namespace.as_ref().map(|_| "[redacted]"),
231 )
232 .finish()
233 }
234}
235
236#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
238pub struct AuthVerifyResult {
239 pub user_id: InlineId,
241 pub account_namespace: String,
243 pub status: ClientStatusSnapshot,
245}
246
247impl fmt::Debug for AuthVerifyResult {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.debug_struct("AuthVerifyResult")
250 .field("user_id", &self.user_id)
251 .field("account_namespace", &"[redacted]")
252 .field("status", &self.status)
253 .finish()
254 }
255}
256
257#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
259pub struct DialogsRequest {
260 pub limit: Option<u32>,
262 pub cursor: Option<String>,
264}
265
266#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
268pub struct HistoryRequest {
269 pub chat_id: InlineId,
271 pub limit: Option<u32>,
273 pub before_message_id: Option<InlineId>,
275 pub after_message_id: Option<InlineId>,
277}
278
279#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
281pub struct ChatParticipantsRequest {
282 pub chat_id: InlineId,
284}
285
286#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
288pub struct AddChatParticipantRequest {
289 pub chat_id: InlineId,
291 pub user_id: InlineId,
293}
294
295#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
297pub struct RemoveChatParticipantRequest {
298 pub chat_id: InlineId,
300 pub user_id: InlineId,
302}
303
304#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
308pub struct UpdateChatInfoRequest {
309 pub chat_id: InlineId,
311 pub title: Option<String>,
313 pub emoji: Option<String>,
315}
316
317#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
319pub struct DeleteChatRequest {
320 pub chat_id: InlineId,
322}
323
324#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
326pub struct ChatCreateParticipant {
327 pub user_id: InlineId,
329}
330
331impl From<InlineId> for ChatCreateParticipant {
332 fn from(user_id: InlineId) -> Self {
333 Self { user_id }
334 }
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
339pub struct CreateDmRequest {
340 pub user_id: InlineId,
342}
343
344#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
346pub struct CreateThreadRequest {
347 pub title: Option<String>,
349 pub space_id: Option<InlineId>,
351 pub description: Option<String>,
353 pub emoji: Option<String>,
355 pub is_public: bool,
357 #[serde(default)]
359 pub participants: Vec<ChatCreateParticipant>,
360}
361
362#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
364pub struct CreateReplyThreadRequest {
365 pub parent_chat_id: InlineId,
367 pub parent_message_id: Option<InlineId>,
369 pub title: Option<String>,
371 pub description: Option<String>,
373 pub emoji: Option<String>,
375 #[serde(default)]
377 pub participants: Vec<ChatCreateParticipant>,
378}
379
380#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
382pub struct SendTextRequest {
383 pub peer: PeerRef,
385 pub text: String,
387 pub external_id: Option<ExternalId>,
389 pub random_id: Option<RandomId>,
391 pub reply_to_message_id: Option<InlineId>,
393}
394
395impl SendTextRequest {
396 pub fn new(peer: PeerRef, text: impl Into<String>) -> Self {
398 Self {
399 peer,
400 text: text.into(),
401 external_id: None,
402 random_id: None,
403 reply_to_message_id: None,
404 }
405 }
406}
407
408impl fmt::Debug for SendTextRequest {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 f.debug_struct("SendTextRequest")
411 .field("peer", &self.peer)
412 .field("text_len", &self.text.len())
413 .field("external_id", &self.external_id)
414 .field("random_id", &self.random_id)
415 .field("reply_to_message_id", &self.reply_to_message_id)
416 .finish()
417 }
418}
419
420#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
422pub struct EditMessageRequest {
423 pub chat_id: InlineId,
425 pub message_id: InlineId,
427 pub text: String,
429 pub external_id: Option<ExternalId>,
431}
432
433impl fmt::Debug for EditMessageRequest {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 f.debug_struct("EditMessageRequest")
436 .field("chat_id", &self.chat_id)
437 .field("message_id", &self.message_id)
438 .field("text_len", &self.text.len())
439 .field("external_id", &self.external_id)
440 .finish()
441 }
442}
443
444#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
446pub struct DeleteMessageRequest {
447 pub chat_id: InlineId,
449 pub message_id: InlineId,
451 pub external_id: Option<ExternalId>,
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
457pub struct ReactRequest {
458 pub chat_id: InlineId,
460 pub message_id: InlineId,
462 pub reaction: String,
464 pub remove: bool,
466 pub external_id: Option<ExternalId>,
468}
469
470#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
472pub struct ReadRequest {
473 pub chat_id: InlineId,
475 pub max_message_id: Option<InlineId>,
477}
478
479#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
481pub struct SetMarkedUnreadRequest {
482 pub chat_id: InlineId,
484 pub unread: bool,
486}
487
488#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
490pub struct UpdateDialogNotificationsRequest {
491 pub chat_id: InlineId,
493 pub mode: Option<DialogNotificationMode>,
495}
496
497#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
499pub struct TypingRequest {
500 pub chat_id: InlineId,
502 pub is_typing: bool,
504}
505
506#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
512pub struct UploadRequest {
513 pub peer: PeerRef,
515 pub kind: MediaKind,
517 pub file_name: Option<String>,
519 pub mime_type: Option<String>,
521 pub size_bytes: Option<u64>,
523 pub caption: Option<String>,
525 pub width: Option<u32>,
527 pub height: Option<u32>,
529 pub duration_ms: Option<u64>,
531 pub external_id: Option<ExternalId>,
533 pub random_id: Option<RandomId>,
535 pub reply_to_message_id: Option<InlineId>,
537}
538
539impl fmt::Debug for UploadRequest {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 f.debug_struct("UploadRequest")
542 .field("peer", &self.peer)
543 .field("kind", &self.kind)
544 .field("file_name", &self.file_name)
545 .field("mime_type", &self.mime_type)
546 .field("size_bytes", &self.size_bytes)
547 .field("caption_len", &self.caption.as_ref().map(String::len))
548 .field("width", &self.width)
549 .field("height", &self.height)
550 .field("duration_ms", &self.duration_ms)
551 .field("external_id", &self.external_id)
552 .field("random_id", &self.random_id)
553 .field("reply_to_message_id", &self.reply_to_message_id)
554 .finish()
555 }
556}
557
558#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
560pub struct ClientStatusSnapshot {
561 pub status: ClientStatus,
563 pub failure: Option<ClientFailure>,
565}
566
567impl ClientStatusSnapshot {
568 pub fn current(status: ClientStatus) -> Self {
570 Self {
571 status,
572 failure: None,
573 }
574 }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
579pub struct DialogRecord {
580 pub chat_id: InlineId,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
584 pub peer_user_id: Option<InlineId>,
585 pub title: Option<String>,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub emoji: Option<String>,
590 pub last_message_id: Option<InlineId>,
592 pub synced_through_message_id: Option<InlineId>,
594 pub unread_count: Option<u32>,
596 #[serde(default, skip_serializing_if = "Option::is_none")]
598 pub space_id: Option<InlineId>,
599 #[serde(default, skip_serializing_if = "Option::is_none")]
601 pub is_public: Option<bool>,
602 #[serde(default, skip_serializing_if = "Option::is_none")]
604 pub archived: Option<bool>,
605 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub pinned: Option<bool>,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
610 pub open: Option<bool>,
611 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub chat_list_hidden: Option<bool>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
616 pub order: Option<String>,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub pinned_order: Option<String>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
622 pub notification_mode: Option<DialogNotificationMode>,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
625 pub follow_mode: Option<DialogFollowMode>,
626 #[serde(default, skip_serializing_if = "Vec::is_empty")]
628 pub pinned_message_ids: Vec<InlineId>,
629}
630
631impl DialogRecord {
632 pub fn new(chat_id: InlineId) -> Self {
634 Self {
635 chat_id,
636 peer_user_id: None,
637 title: None,
638 emoji: None,
639 last_message_id: None,
640 synced_through_message_id: None,
641 unread_count: None,
642 space_id: None,
643 is_public: None,
644 archived: None,
645 pinned: None,
646 open: None,
647 chat_list_hidden: None,
648 order: None,
649 pinned_order: None,
650 notification_mode: None,
651 follow_mode: None,
652 pinned_message_ids: Vec::new(),
653 }
654 }
655}
656
657#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
659#[serde(rename_all = "snake_case")]
660pub enum DialogNotificationMode {
661 All,
663 Mentions,
665 None,
667}
668
669#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
671#[serde(rename_all = "snake_case")]
672pub enum DialogFollowMode {
673 Following,
675}
676
677#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
679pub struct DialogsPage {
680 pub dialogs: Vec<DialogRecord>,
682 #[serde(default)]
684 pub users: Vec<UserRecord>,
685 pub next_cursor: Option<String>,
687}
688
689#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
691pub struct UserRecord {
692 pub user_id: InlineId,
694 pub display_name: Option<String>,
696 pub username: Option<String>,
698 pub first_name: Option<String>,
700 pub last_name: Option<String>,
702 pub avatar_url: Option<String>,
704 pub is_bot: Option<bool>,
706}
707
708#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
710pub struct SpaceRecord {
711 pub space_id: InlineId,
713 pub name: String,
715 pub creator: bool,
717 pub date: i64,
719 pub is_public: Option<bool>,
721}
722
723#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
725#[serde(rename_all = "snake_case")]
726pub enum SpaceMemberRole {
727 Owner,
729 Admin,
731 Member,
733}
734
735#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
737pub struct SpaceMemberRecord {
738 pub space_id: InlineId,
740 pub user_id: InlineId,
742 pub role: Option<SpaceMemberRole>,
744 pub date: i64,
746 pub can_access_public_chats: bool,
748}
749
750#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
752#[serde(rename_all = "snake_case")]
753pub enum NotificationMode {
754 All,
756 None,
758 Mentions,
760 ImportantOnly,
762 OnlyMentions,
764}
765
766#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
768pub struct UserSettingsRecord {
769 pub notification_mode: Option<NotificationMode>,
771 pub silent: Option<bool>,
773 pub zen_mode_requires_mention: Option<bool>,
775 pub zen_mode_uses_default_rules: Option<bool>,
777 pub zen_mode_custom_rules: Option<String>,
779 pub disable_dm_notifications: Option<bool>,
781}
782
783#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
785#[serde(tag = "type", rename_all = "snake_case")]
786#[non_exhaustive]
787pub enum MessageContent {
788 Text {
790 text: String,
792 },
793 Media {
795 kind: MediaKind,
797 file_id: String,
799 url: Option<String>,
801 mime_type: Option<String>,
803 file_name: Option<String>,
805 caption: Option<String>,
807 size_bytes: Option<u64>,
809 width: Option<u32>,
811 height: Option<u32>,
813 duration_ms: Option<u64>,
815 },
816 Unsupported {
818 reason: String,
820 },
821}
822
823#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
825#[serde(rename_all = "snake_case")]
826#[non_exhaustive]
827pub enum MediaKind {
828 Photo,
830 Video,
832 Document,
834 Voice,
836}
837
838impl fmt::Debug for MessageContent {
839 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
840 match self {
841 Self::Text { text } => f
842 .debug_struct("Text")
843 .field("text_len", &text.len())
844 .finish(),
845 Self::Media {
846 kind,
847 file_id,
848 url,
849 mime_type,
850 file_name,
851 caption,
852 size_bytes,
853 width,
854 height,
855 duration_ms,
856 } => f
857 .debug_struct("Media")
858 .field("kind", kind)
859 .field("file_id", file_id)
860 .field("has_url", &url.is_some())
861 .field("mime_type", mime_type)
862 .field("file_name", file_name)
863 .field("caption_len", &caption.as_ref().map(String::len))
864 .field("size_bytes", size_bytes)
865 .field("width", width)
866 .field("height", height)
867 .field("duration_ms", duration_ms)
868 .finish(),
869 Self::Unsupported { reason } => f
870 .debug_struct("Unsupported")
871 .field("reason", reason)
872 .finish(),
873 }
874 }
875}
876
877#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
879pub struct MessageRecord {
880 pub chat_id: InlineId,
882 pub message_id: InlineId,
884 pub sender_id: InlineId,
886 pub timestamp: i64,
888 pub is_outgoing: bool,
890 pub content: MessageContent,
892 pub reply_to_message_id: Option<InlineId>,
894 pub transaction: Option<TransactionIdentity>,
896}
897
898#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
900pub struct HistoryPage {
901 pub messages: Vec<MessageRecord>,
903 #[serde(default)]
905 pub users: Vec<UserRecord>,
906 pub has_more: bool,
908 pub next_cursor: Option<String>,
910}
911
912#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
914pub struct ChatParticipantRecord {
915 pub user_id: InlineId,
917 pub date: Option<i64>,
919}
920
921#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
923pub struct ChatParticipantsPage {
924 pub participants: Vec<ChatParticipantRecord>,
926 #[serde(default)]
928 pub users: Vec<UserRecord>,
929}
930
931#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
933pub struct CreatedChat {
934 pub chat_id: InlineId,
936 pub title: Option<String>,
938 pub parent_chat_id: Option<InlineId>,
940 pub parent_message_id: Option<InlineId>,
942}
943
944#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
946pub struct MessageMutation {
947 pub transaction: TransactionIdentity,
949 pub message_id: Option<InlineId>,
951 #[serde(default, skip_serializing_if = "Option::is_none")]
953 pub state: Option<TransactionState>,
954 #[serde(default, skip_serializing_if = "Option::is_none")]
956 pub failure: Option<ClientFailure>,
957}
958
959#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
961pub struct UploadHandle {
962 pub file_id: String,
964 pub mime_type: Option<String>,
966 pub size_bytes: Option<u64>,
968}
969
970#[cfg(test)]
971mod tests {
972 use super::*;
973 use crate::{TransactionId, TransactionIdentity};
974
975 #[test]
976 fn auth_token_debug_is_redacted() {
977 let token = AuthToken::try_new("secret-token").unwrap();
978
979 assert_eq!(format!("{token:?}"), "AuthToken(\"[redacted]\")");
980 assert_eq!(token.expose_secret(), "secret-token");
981 }
982
983 #[test]
984 fn session_namespace_debug_is_redacted() {
985 let request = ConnectRequest::new(AuthCredential::AccessToken {
986 token: AuthToken::try_new("token").unwrap(),
987 })
988 .with_account_namespace("secret-namespace");
989
990 let rendered = format!("{request:?}");
991 assert!(rendered.contains("[redacted]"));
992 assert!(!rendered.contains("secret-namespace"));
993 }
994
995 #[test]
996 fn send_text_debug_redacts_body() {
997 let req = SendTextRequest::new(
998 PeerRef::User {
999 user_id: InlineId::new(42),
1000 },
1001 "hello private world",
1002 );
1003 let rendered = format!("{req:?}");
1004
1005 assert!(rendered.contains("text_len"));
1006 assert!(!rendered.contains("hello private world"));
1007 }
1008
1009 #[test]
1010 fn history_page_redacts_message_text_in_debug() {
1011 let message = MessageRecord {
1012 chat_id: InlineId::new(1),
1013 message_id: InlineId::new(2),
1014 sender_id: InlineId::new(3),
1015 timestamp: 1_783_452_698,
1016 is_outgoing: false,
1017 content: MessageContent::Text {
1018 text: "private message".to_owned(),
1019 },
1020 reply_to_message_id: None,
1021 transaction: Some(TransactionIdentity::new(
1022 TransactionId::try_new("txn-1").unwrap(),
1023 None,
1024 RandomId::new(1),
1025 )),
1026 };
1027 let rendered = format!("{message:?}");
1028
1029 assert!(rendered.contains("text_len"));
1030 assert!(!rendered.contains("private message"));
1031 }
1032}