1use std::borrow::Cow;
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, Copy)]
8pub struct ParsedJidParts<'a> {
9 pub user: &'a str,
10 pub server: &'a str,
11 pub agent: u8,
12 pub device: u16,
13 pub integrator: u16,
14}
15
16#[inline]
22pub fn parse_jid_fast(s: &str) -> Option<ParsedJidParts<'_>> {
23 if s.is_empty() {
24 return None;
25 }
26
27 let bytes = s.as_bytes();
28
29 let mut at_pos: Option<usize> = None;
31 let mut colon_pos: Option<usize> = None;
32 let mut last_dot_pos: Option<usize> = None;
33
34 for (i, &b) in bytes.iter().enumerate() {
35 match b {
36 b'@' => {
37 if at_pos.is_none() {
38 at_pos = Some(i);
39 }
40 }
41 b':' => {
42 if at_pos.is_none() {
44 colon_pos = Some(i);
45 }
46 }
47 b'.' => {
48 if at_pos.is_none() && colon_pos.is_none() {
50 last_dot_pos = Some(i);
51 }
52 }
53 _ => {}
54 }
55 }
56
57 let at = match at_pos {
59 Some(pos) => pos,
60 None => {
61 return None;
63 }
64 };
65
66 let user_part = &s[..at];
67 let server = &s[at + 1..];
68
69 if user_part.is_empty() {
71 return None;
72 }
73
74 if server == HIDDEN_USER_SERVER {
76 let (user, device) = match colon_pos {
77 Some(pos) if pos < at => {
78 let device_slice = &s[pos + 1..at];
79 (&s[..pos], device_slice.parse::<u16>().unwrap_or(0))
80 }
81 _ => (user_part, 0),
82 };
83 return Some(ParsedJidParts {
84 user,
85 server,
86 agent: 0,
87 device,
88 integrator: 0,
89 });
90 }
91
92 if server == DEFAULT_USER_SERVER {
94 if let Some(pos) = colon_pos {
96 let user_end = pos;
97 let device_start = pos + 1;
98 let device_slice = &s[device_start..at];
99 let device = device_slice.parse::<u16>().unwrap_or(0);
100 return Some(ParsedJidParts {
101 user: &s[..user_end],
102 server,
103 agent: 0,
104 device,
105 integrator: 0,
106 });
107 }
108 if let Some(dot_pos) = last_dot_pos {
110 let suffix = &s[dot_pos + 1..at];
112 if let Ok(device_val) = suffix.parse::<u16>() {
113 return Some(ParsedJidParts {
114 user: &s[..dot_pos],
115 server,
116 agent: 0,
117 device: device_val,
118 integrator: 0,
119 });
120 }
121 }
122 return Some(ParsedJidParts {
124 user: user_part,
125 server,
126 agent: 0,
127 device: 0,
128 integrator: 0,
129 });
130 }
131
132 let (user_before_colon, device) = match colon_pos {
134 Some(pos) => {
135 let user_end = pos;
137 let device_start = pos + 1;
138 let device_slice = &s[device_start..at];
139 (&s[..user_end], device_slice.parse::<u16>().unwrap_or(0))
140 }
141 None => (user_part, 0),
142 };
143
144 let user_to_check = user_before_colon;
146 let (final_user, agent) = {
147 if let Some(dot_pos) = user_to_check.rfind('.') {
148 let suffix = &user_to_check[dot_pos + 1..];
149 if let Ok(agent_val) = suffix.parse::<u16>() {
150 if agent_val <= u8::MAX as u16 {
151 (&user_to_check[..dot_pos], agent_val as u8)
152 } else {
153 (user_to_check, 0)
154 }
155 } else {
156 (user_to_check, 0)
157 }
158 } else {
159 (user_to_check, 0)
160 }
161 };
162
163 Some(ParsedJidParts {
164 user: final_user,
165 server,
166 agent,
167 device,
168 integrator: 0,
169 })
170}
171
172pub const DEFAULT_USER_SERVER: &str = "s.whatsapp.net";
173pub const SERVER_JID: &str = "s.whatsapp.net";
174pub const GROUP_SERVER: &str = "g.us";
175pub const LEGACY_USER_SERVER: &str = "c.us";
176pub const BROADCAST_SERVER: &str = "broadcast";
177pub const HIDDEN_USER_SERVER: &str = "lid";
178pub const NEWSLETTER_SERVER: &str = "newsletter";
179pub const HOSTED_SERVER: &str = "hosted";
180pub const HOSTED_LID_SERVER: &str = "hosted.lid";
181pub const MESSENGER_SERVER: &str = "msgr";
182pub const INTEROP_SERVER: &str = "interop";
183pub const BOT_SERVER: &str = "bot";
184pub const STATUS_BROADCAST_USER: &str = "status";
185
186pub type MessageId = String;
187pub type MessageServerId = i32;
188#[derive(Debug)]
189pub enum JidError {
190 InvalidFormat(String),
192 Parse(std::num::ParseIntError),
194}
195
196impl fmt::Display for JidError {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 match self {
199 JidError::InvalidFormat(s) => write!(f, "Invalid JID format: {s}"),
200 JidError::Parse(e) => write!(f, "Failed to parse component: {e}"),
201 }
202 }
203}
204
205impl std::error::Error for JidError {
206 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
207 match self {
208 JidError::Parse(e) => Some(e),
209 _ => None,
210 }
211 }
212}
213
214impl From<std::num::ParseIntError> for JidError {
216 fn from(err: std::num::ParseIntError) -> Self {
217 JidError::Parse(err)
218 }
219}
220
221pub trait JidExt {
222 fn user(&self) -> &str;
223 fn server(&self) -> &str;
224 fn device(&self) -> u16;
225 fn integrator(&self) -> u16;
226
227 fn is_ad(&self) -> bool {
228 self.device() > 0
229 && (self.server() == DEFAULT_USER_SERVER
230 || self.server() == HIDDEN_USER_SERVER
231 || self.server() == HOSTED_SERVER)
232 }
233
234 fn is_interop(&self) -> bool {
235 self.server() == INTEROP_SERVER && self.integrator() > 0
236 }
237
238 fn is_messenger(&self) -> bool {
239 self.server() == MESSENGER_SERVER && self.device() > 0
240 }
241
242 fn is_group(&self) -> bool {
243 self.server() == GROUP_SERVER
244 }
245
246 fn is_broadcast_list(&self) -> bool {
247 self.server() == BROADCAST_SERVER && self.user() != STATUS_BROADCAST_USER
248 }
249
250 fn is_status_broadcast(&self) -> bool {
251 self.server() == BROADCAST_SERVER && self.user() == STATUS_BROADCAST_USER
252 }
253
254 fn is_bot(&self) -> bool {
255 (self.server() == DEFAULT_USER_SERVER
256 && self.device() == 0
257 && (self.user().starts_with("1313555") || self.user().starts_with("131655500")))
258 || self.server() == BOT_SERVER
259 }
260
261 fn is_hosted(&self) -> bool {
265 self.device() == 99 || self.server() == HOSTED_SERVER || self.server() == HOSTED_LID_SERVER
266 }
267
268 fn is_empty(&self) -> bool {
269 self.server().is_empty()
270 }
271
272 fn is_same_user_as(&self, other: &impl JidExt) -> bool {
273 self.user() == other.user()
274 }
275}
276
277#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
278#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
279pub struct Jid {
280 pub user: String,
281 pub server: String,
282 pub agent: u8,
283 pub device: u16,
284 pub integrator: u16,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Hash)]
288pub struct JidRef<'a> {
289 pub user: Cow<'a, str>,
290 pub server: Cow<'a, str>,
291 pub agent: u8,
292 pub device: u16,
293 pub integrator: u16,
294}
295
296impl JidExt for Jid {
297 fn user(&self) -> &str {
298 &self.user
299 }
300 fn server(&self) -> &str {
301 &self.server
302 }
303 fn device(&self) -> u16 {
304 self.device
305 }
306 fn integrator(&self) -> u16 {
307 self.integrator
308 }
309}
310
311impl Jid {
312 pub fn new(user: &str, server: &str) -> Self {
313 Self {
314 user: user.to_string(),
315 server: server.to_string(),
316 ..Default::default()
317 }
318 }
319
320 pub fn pn(user: impl Into<String>) -> Self {
322 Self {
323 user: user.into(),
324 server: DEFAULT_USER_SERVER.to_string(),
325 ..Default::default()
326 }
327 }
328
329 pub fn lid(user: impl Into<String>) -> Self {
331 Self {
332 user: user.into(),
333 server: HIDDEN_USER_SERVER.to_string(),
334 ..Default::default()
335 }
336 }
337
338 pub fn group(id: impl Into<String>) -> Self {
340 Self {
341 user: id.into(),
342 server: GROUP_SERVER.to_string(),
343 ..Default::default()
344 }
345 }
346
347 pub fn pn_device(user: impl Into<String>, device: u16) -> Self {
349 Self {
350 user: user.into(),
351 server: DEFAULT_USER_SERVER.to_string(),
352 device,
353 ..Default::default()
354 }
355 }
356
357 pub fn lid_device(user: impl Into<String>, device: u16) -> Self {
359 Self {
360 user: user.into(),
361 server: HIDDEN_USER_SERVER.to_string(),
362 device,
363 ..Default::default()
364 }
365 }
366
367 #[inline]
369 pub fn is_pn(&self) -> bool {
370 self.server == DEFAULT_USER_SERVER
371 }
372
373 #[inline]
375 pub fn is_lid(&self) -> bool {
376 self.server == HIDDEN_USER_SERVER
377 }
378
379 #[inline]
381 pub fn user_base(&self) -> &str {
382 if let Some((base, _)) = self.user.split_once(':') {
383 base
384 } else {
385 &self.user
386 }
387 }
388
389 pub fn with_device(&self, device_id: u16) -> Self {
391 Self {
392 user: self.user.clone(),
393 server: self.server.clone(),
394 agent: self.agent,
395 device: device_id,
396 integrator: self.integrator,
397 }
398 }
399
400 pub fn actual_agent(&self) -> u8 {
401 match self.server.as_str() {
402 DEFAULT_USER_SERVER => 0,
403 HIDDEN_USER_SERVER => self.agent,
408 _ => self.agent,
409 }
410 }
411
412 pub fn to_non_ad(&self) -> Self {
413 Self {
414 user: self.user.clone(),
415 server: self.server.clone(),
416 integrator: self.integrator,
417 ..Default::default()
418 }
419 }
420
421 #[inline]
424 pub fn matches_user_or_lid(&self, user: &Jid, lid: Option<&Jid>) -> bool {
425 self.is_same_user_as(user) || lid.is_some_and(|l| self.is_same_user_as(l))
426 }
427
428 pub fn to_ad_string(&self) -> String {
429 if self.user.is_empty() {
430 self.server.clone()
431 } else {
432 format!(
433 "{}.{}:{}@{}",
434 self.user, self.agent, self.device, self.server
435 )
436 }
437 }
438}
439
440impl<'a> JidExt for JidRef<'a> {
441 fn user(&self) -> &str {
442 &self.user
443 }
444 fn server(&self) -> &str {
445 &self.server
446 }
447 fn device(&self) -> u16 {
448 self.device
449 }
450 fn integrator(&self) -> u16 {
451 self.integrator
452 }
453}
454
455impl<'a> JidRef<'a> {
456 pub fn new(user: Cow<'a, str>, server: Cow<'a, str>) -> Self {
457 Self {
458 user,
459 server,
460 agent: 0,
461 device: 0,
462 integrator: 0,
463 }
464 }
465
466 pub fn to_owned(&self) -> Jid {
467 Jid {
468 user: self.user.to_string(),
469 server: self.server.to_string(),
470 agent: self.agent,
471 device: self.device,
472 integrator: self.integrator,
473 }
474 }
475}
476
477impl FromStr for Jid {
478 type Err = JidError;
479 fn from_str(s: &str) -> Result<Self, Self::Err> {
480 if let Some(parts) = parse_jid_fast(s) {
482 return Ok(Jid {
483 user: parts.user.to_string(),
484 server: parts.server.to_string(),
485 agent: parts.agent,
486 device: parts.device,
487 integrator: parts.integrator,
488 });
489 }
490
491 let (user_part, server) = match s.split_once('@') {
494 Some((u, s)) => (u, s),
495 None => ("", s),
496 };
497
498 if user_part.is_empty() {
499 let known_servers = [
500 DEFAULT_USER_SERVER,
501 GROUP_SERVER,
502 LEGACY_USER_SERVER,
503 BROADCAST_SERVER,
504 HIDDEN_USER_SERVER,
505 NEWSLETTER_SERVER,
506 HOSTED_SERVER,
507 MESSENGER_SERVER,
508 INTEROP_SERVER,
509 BOT_SERVER,
510 STATUS_BROADCAST_USER,
511 ];
512 if !known_servers.contains(&server) {
513 return Err(JidError::InvalidFormat(format!(
514 "Invalid JID format: unknown server '{}'",
515 server
516 )));
517 }
518 }
519
520 if server == HIDDEN_USER_SERVER {
523 let (user, device) = if let Some((u, d_str)) = user_part.rsplit_once(':') {
524 (u, d_str.parse()?)
525 } else {
526 (user_part, 0)
527 };
528 return Ok(Jid {
529 user: user.to_string(),
530 server: server.to_string(),
531 device,
532 agent: 0,
533 integrator: 0,
534 });
535 }
536
537 let mut user = user_part;
539 let mut device = 0;
540 let mut agent = 0;
541
542 if let Some((u, d_str)) = user_part.rsplit_once(':') {
543 user = u;
544 device = d_str.parse()?;
545 }
546
547 if server != DEFAULT_USER_SERVER
548 && server != HIDDEN_USER_SERVER
549 && let Some((u, last_part)) = user.rsplit_once('.')
550 && let Ok(num_val) = last_part.parse::<u16>()
551 {
552 user = u;
553 agent = num_val as u8;
554 }
555
556 if let Some((u, last_part)) = user_part.rsplit_once('.')
557 && let Ok(num_val) = last_part.parse::<u16>()
558 {
559 if server == DEFAULT_USER_SERVER {
560 user = u;
561 device = num_val;
562 } else {
563 user = u;
564 if num_val > u8::MAX as u16 {
565 return Err(JidError::InvalidFormat(format!(
566 "Agent component out of range: {num_val}"
567 )));
568 }
569 agent = num_val as u8;
570 }
571 }
572
573 Ok(Jid {
574 user: user.to_string(),
575 server: server.to_string(),
576 agent,
577 device,
578 integrator: 0,
579 })
580 }
581}
582
583impl fmt::Display for Jid {
584 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585 if self.user.is_empty() {
586 write!(f, "{}", self.server)
588 } else {
589 write!(f, "{}", self.user)?;
590
591 if self.agent > 0 {
596 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
601 && server_str != HIDDEN_USER_SERVER
602 && server_str != HOSTED_SERVER
603 {
604 write!(f, ".{}", self.agent)?;
605 }
606 }
607
608 if self.device > 0 {
609 write!(f, ":{}", self.device)?;
610 }
611
612 write!(f, "@{}", self.server)
613 }
614 }
615}
616
617impl<'a> fmt::Display for JidRef<'a> {
618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619 if self.user.is_empty() {
620 write!(f, "{}", self.server)
622 } else {
623 write!(f, "{}", self.user)?;
624
625 if self.agent > 0 {
630 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
635 && server_str != HIDDEN_USER_SERVER
636 && server_str != HOSTED_SERVER
637 {
638 write!(f, ".{}", self.agent)?;
639 }
640 }
641
642 if self.device > 0 {
643 write!(f, ":{}", self.device)?;
644 }
645
646 write!(f, "@{}", self.server)
647 }
648 }
649}
650
651impl From<Jid> for String {
652 fn from(jid: Jid) -> Self {
653 jid.to_string()
654 }
655}
656
657impl<'a> From<JidRef<'a>> for String {
658 fn from(jid: JidRef<'a>) -> Self {
659 jid.to_string()
660 }
661}
662
663impl TryFrom<String> for Jid {
664 type Error = JidError;
665 fn try_from(value: String) -> Result<Self, Self::Error> {
666 Jid::from_str(&value)
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673 use std::str::FromStr;
674
675 fn assert_jid_roundtrip(
677 input: &str,
678 expected_user: &str,
679 expected_server: &str,
680 expected_device: u16,
681 expected_agent: u8,
682 ) {
683 assert_jid_parse_and_display(
684 input,
685 expected_user,
686 expected_server,
687 expected_device,
688 expected_agent,
689 input,
690 );
691 }
692
693 fn assert_jid_parse_and_display(
695 input: &str,
696 expected_user: &str,
697 expected_server: &str,
698 expected_device: u16,
699 expected_agent: u8,
700 expected_output: &str,
701 ) {
702 let jid = Jid::from_str(input).unwrap_or_else(|_| panic!("Failed to parse JID: {}", input));
704
705 assert_eq!(
706 jid.user, expected_user,
707 "User part did not match for {}",
708 input
709 );
710 assert_eq!(
711 jid.server, expected_server,
712 "Server part did not match for {}",
713 input
714 );
715 assert_eq!(
716 jid.device, expected_device,
717 "Device part did not match for {}",
718 input
719 );
720 assert_eq!(
721 jid.agent, expected_agent,
722 "Agent part did not match for {}",
723 input
724 );
725
726 let formatted = jid.to_string();
728 assert_eq!(
729 formatted, expected_output,
730 "Formatted string did not match expected output for {}",
731 input
732 );
733 }
734
735 #[test]
736 fn test_jid_parsing_and_display_roundtrip() {
737 assert_jid_roundtrip(
739 "1234567890@s.whatsapp.net",
740 "1234567890",
741 "s.whatsapp.net",
742 0,
743 0,
744 );
745 assert_jid_roundtrip(
746 "1234567890:15@s.whatsapp.net",
747 "1234567890",
748 "s.whatsapp.net",
749 15,
750 0,
751 );
752 assert_jid_roundtrip("123-456@g.us", "123-456", "g.us", 0, 0);
753
754 assert_jid_roundtrip("s.whatsapp.net", "", "s.whatsapp.net", 0, 0);
757
758 assert_jid_roundtrip("12345.6789@lid", "12345.6789", "lid", 0, 0);
760 assert_jid_roundtrip("12345.6789:25@lid", "12345.6789", "lid", 25, 0);
761 }
762
763 #[test]
764 fn test_special_from_str_parsing() {
765 let jid = Jid::from_str("1234567890.2:15@hosted").expect("test hosted JID should be valid");
767 assert_eq!(jid.user, "1234567890");
768 assert_eq!(jid.server, "hosted");
769 assert_eq!(jid.device, 15);
770 assert_eq!(jid.agent, 2);
771 }
772
773 #[test]
774 fn test_manual_jid_formatting_edge_cases() {
775 let jid1 = Jid {
782 user: "1234567890".to_string(),
783 server: "s.whatsapp.net".to_string(),
784 device: 15,
785 agent: 2, integrator: 0,
787 };
788 assert_eq!(jid1.to_string(), "1234567890:15@s.whatsapp.net");
791
792 let jid2 = Jid {
795 user: "12345.6789".to_string(),
796 server: "lid".to_string(),
797 device: 25,
798 agent: 1, integrator: 0,
800 };
801 assert_eq!(jid2.to_string(), "12345.6789:25@lid");
804
805 let jid3 = Jid {
808 user: "1234567890".to_string(),
809 server: "hosted".to_string(),
810 device: 15,
811 agent: 2,
812 integrator: 0,
813 };
814 assert_eq!(jid3.to_string(), "1234567890:15@hosted");
817
818 let jid4 = Jid {
820 user: "user".to_string(),
821 server: "custom.net".to_string(),
822 device: 10,
823 agent: 5,
824 integrator: 0,
825 };
826 assert_eq!(jid4.to_string(), "user.5:10@custom.net");
828 }
829
830 #[test]
831 fn test_invalid_jids_should_fail_to_parse() {
832 assert!(Jid::from_str("thisisnotajid").is_err());
833 assert!(Jid::from_str("").is_err());
834 assert!(Jid::from_str("@s.whatsapp.net").is_ok());
836 assert!(Jid::from_str("@unknown.server").is_err());
838 assert!(Jid::from_str("2").is_err());
841 }
842
843 #[test]
871 fn test_is_hosted_device_detection() {
872 let cloud_api_device: Jid = "5511999887766:99@s.whatsapp.net"
877 .parse()
878 .expect("test JID should be valid");
879 assert!(
880 cloud_api_device.is_hosted(),
881 "Device ID 99 on s.whatsapp.net should be detected as hosted (Cloud API)"
882 );
883
884 let cloud_api_lid: Jid = "100000012345678:99@lid"
886 .parse()
887 .expect("test JID should be valid");
888 assert!(
889 cloud_api_lid.is_hosted(),
890 "Device ID 99 on lid server should be detected as hosted"
891 );
892
893 let hosted_server: Jid = "5511999887766:99@hosted"
895 .parse()
896 .expect("test JID should be valid");
897 assert!(
898 hosted_server.is_hosted(),
899 "JID with @hosted server should be detected as hosted"
900 );
901
902 let hosted_lid_server: Jid = "100000012345678:99@hosted.lid"
904 .parse()
905 .expect("test JID should be valid");
906 assert!(
907 hosted_lid_server.is_hosted(),
908 "JID with @hosted.lid server should be detected as hosted"
909 );
910
911 let hosted_server_other_device: Jid = "5511999887766:0@hosted"
914 .parse()
915 .expect("test JID should be valid");
916 assert!(
917 hosted_server_other_device.is_hosted(),
918 "JID with @hosted server should be hosted regardless of device ID"
919 );
920
921 let regular_phone: Jid = "5511999887766:0@s.whatsapp.net"
925 .parse()
926 .expect("test JID should be valid");
927 assert!(
928 !regular_phone.is_hosted(),
929 "Regular phone device (ID 0) should NOT be hosted"
930 );
931
932 let companion_device: Jid = "5511999887766:33@s.whatsapp.net"
934 .parse()
935 .expect("test JID should be valid");
936 assert!(
937 !companion_device.is_hosted(),
938 "Companion device (ID 33) should NOT be hosted"
939 );
940
941 let regular_lid: Jid = "100000012345678:0@lid"
943 .parse()
944 .expect("test JID should be valid");
945 assert!(
946 !regular_lid.is_hosted(),
947 "Regular LID device should NOT be hosted"
948 );
949
950 let lid_companion: Jid = "100000012345678:33@lid"
952 .parse()
953 .expect("test JID should be valid");
954 assert!(
955 !lid_companion.is_hosted(),
956 "LID companion device (ID 33) should NOT be hosted"
957 );
958
959 let group_jid: Jid = "120363012345678@g.us"
961 .parse()
962 .expect("test JID should be valid");
963 assert!(
964 !group_jid.is_hosted(),
965 "Group JID should NOT be detected as hosted"
966 );
967
968 let user_jid: Jid = "5511999887766@s.whatsapp.net"
970 .parse()
971 .expect("test JID should be valid");
972 assert!(
973 !user_jid.is_hosted(),
974 "User JID without device should NOT be hosted"
975 );
976
977 let bot_jid: Jid = "13136555001:0@s.whatsapp.net"
979 .parse()
980 .expect("test JID should be valid");
981 assert!(
982 !bot_jid.is_hosted(),
983 "Bot JID should NOT be detected as hosted (different mechanism)"
984 );
985 }
986
987 #[test]
998 fn test_hosted_device_filtering_for_groups() {
999 let devices: Vec<Jid> = vec![
1001 "5511999887766:0@s.whatsapp.net"
1003 .parse()
1004 .expect("test JID should be valid"), "5511999887766:33@s.whatsapp.net"
1006 .parse()
1007 .expect("test JID should be valid"), "5521988776655:0@s.whatsapp.net"
1009 .parse()
1010 .expect("test JID should be valid"), "100000012345678:0@lid"
1012 .parse()
1013 .expect("test JID should be valid"), "100000012345678:33@lid"
1015 .parse()
1016 .expect("test JID should be valid"), "5531977665544:99@s.whatsapp.net"
1019 .parse()
1020 .expect("test JID should be valid"), "100000087654321:99@lid"
1022 .parse()
1023 .expect("test JID should be valid"), "5541966554433:99@hosted"
1025 .parse()
1026 .expect("test JID should be valid"), ];
1028
1029 let filtered: Vec<&Jid> = devices.iter().filter(|jid| !jid.is_hosted()).collect();
1031
1032 assert_eq!(
1034 filtered.len(),
1035 5,
1036 "Should have 5 non-hosted devices after filtering"
1037 );
1038
1039 for jid in &filtered {
1041 assert!(
1042 !jid.is_hosted(),
1043 "Filtered list should not contain hosted devices: {}",
1044 jid
1045 );
1046 }
1047
1048 let hosted_count = devices.iter().filter(|jid| jid.is_hosted()).count();
1050 assert_eq!(hosted_count, 3, "Should have filtered out 3 hosted devices");
1051 }
1052
1053 #[test]
1054 fn test_jid_pn_factory() {
1055 let jid = Jid::pn("1234567890");
1056 assert_eq!(jid.user, "1234567890");
1057 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1058 assert_eq!(jid.device, 0);
1059 assert!(jid.is_pn());
1060 }
1061
1062 #[test]
1063 fn test_jid_lid_factory() {
1064 let jid = Jid::lid("100000012345678");
1065 assert_eq!(jid.user, "100000012345678");
1066 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1067 assert_eq!(jid.device, 0);
1068 assert!(jid.is_lid());
1069 }
1070
1071 #[test]
1072 fn test_jid_group_factory() {
1073 let jid = Jid::group("123456789-1234567890");
1074 assert_eq!(jid.user, "123456789-1234567890");
1075 assert_eq!(jid.server, GROUP_SERVER);
1076 assert!(jid.is_group());
1077 }
1078
1079 #[test]
1080 fn test_jid_pn_device_factory() {
1081 let jid = Jid::pn_device("1234567890", 5);
1082 assert_eq!(jid.user, "1234567890");
1083 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1084 assert_eq!(jid.device, 5);
1085 assert!(jid.is_pn());
1086 assert!(jid.is_ad());
1087 }
1088
1089 #[test]
1090 fn test_jid_lid_device_factory() {
1091 let jid = Jid::lid_device("100000012345678", 33);
1092 assert_eq!(jid.user, "100000012345678");
1093 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1094 assert_eq!(jid.device, 33);
1095 assert!(jid.is_lid());
1096 assert!(jid.is_ad());
1097 }
1098
1099 #[test]
1100 fn test_jid_factories_with_string_types() {
1101 let jid1 = Jid::pn("123");
1103 assert_eq!(jid1.user, "123");
1104
1105 let jid2 = Jid::lid(String::from("456"));
1107 assert_eq!(jid2.user, "456");
1108
1109 let user = "789".to_string();
1111 let jid3 = Jid::group(user);
1112 assert_eq!(jid3.user, "789");
1113 }
1114}