1use super::generics::{FromAttributes, ParseFrom};
2use crate::util::text::bytes_to_string;
3use compact_str::CompactString;
4use serde_json::Value;
5use std::ops::Deref;
6use std::sync::Arc;
7
8pub type SmallString = CompactString;
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
45#[serde(transparent)]
46pub struct Url(String);
47
48impl Url {
49 #[inline]
61 pub fn new(s: impl Into<String>) -> Self {
62 Self(s.into())
63 }
64
65 #[inline]
76 pub fn as_str(&self) -> &str {
77 &self.0
78 }
79
80 #[inline]
92 pub fn into_inner(self) -> String {
93 self.0
94 }
95}
96
97impl Deref for Url {
98 type Target = str;
99
100 #[inline]
101 fn deref(&self) -> &str {
102 &self.0
103 }
104}
105
106impl From<String> for Url {
107 #[inline]
108 fn from(s: String) -> Self {
109 Self(s)
110 }
111}
112
113impl From<&str> for Url {
114 #[inline]
115 fn from(s: &str) -> Self {
116 Self(s.to_string())
117 }
118}
119
120impl AsRef<str> for Url {
121 #[inline]
122 fn as_ref(&self) -> &str {
123 &self.0
124 }
125}
126
127impl std::fmt::Display for Url {
128 #[inline]
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 self.0.fmt(f)
131 }
132}
133
134impl PartialEq<str> for Url {
135 fn eq(&self, other: &str) -> bool {
136 self.0 == other
137 }
138}
139
140impl PartialEq<&str> for Url {
141 fn eq(&self, other: &&str) -> bool {
142 self.0 == *other
143 }
144}
145
146impl PartialEq<String> for Url {
147 fn eq(&self, other: &String) -> bool {
148 &self.0 == other
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Hash)]
170pub struct MimeType(Arc<str>);
171
172impl serde::Serialize for MimeType {
174 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175 where
176 S: serde::Serializer,
177 {
178 serializer.serialize_str(&self.0)
179 }
180}
181
182impl<'de> serde::Deserialize<'de> for MimeType {
183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'de>,
186 {
187 let s = <String as serde::Deserialize>::deserialize(deserializer)?;
188 Ok(Self::new(s))
189 }
190}
191
192impl MimeType {
193 #[inline]
204 pub fn new(s: impl AsRef<str>) -> Self {
205 Self(Arc::from(s.as_ref()))
206 }
207
208 #[inline]
219 pub fn as_str(&self) -> &str {
220 &self.0
221 }
222
223 pub const TEXT_HTML: &'static str = "text/html";
234
235 pub const TEXT_PLAIN: &'static str = "text/plain";
237
238 pub const APPLICATION_XML: &'static str = "application/xml";
240
241 pub const APPLICATION_JSON: &'static str = "application/json";
243}
244
245impl Default for MimeType {
246 #[inline]
247 fn default() -> Self {
248 Self(Arc::from(""))
249 }
250}
251
252impl Deref for MimeType {
253 type Target = str;
254
255 #[inline]
256 fn deref(&self) -> &str {
257 &self.0
258 }
259}
260
261impl From<String> for MimeType {
262 #[inline]
263 fn from(s: String) -> Self {
264 Self(Arc::from(s.as_str()))
265 }
266}
267
268impl From<&str> for MimeType {
269 #[inline]
270 fn from(s: &str) -> Self {
271 Self(Arc::from(s))
272 }
273}
274
275impl AsRef<str> for MimeType {
276 #[inline]
277 fn as_ref(&self) -> &str {
278 &self.0
279 }
280}
281
282impl std::fmt::Display for MimeType {
283 #[inline]
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 self.0.fmt(f)
286 }
287}
288
289impl PartialEq<str> for MimeType {
290 fn eq(&self, other: &str) -> bool {
291 &*self.0 == other
292 }
293}
294
295impl PartialEq<&str> for MimeType {
296 fn eq(&self, other: &&str) -> bool {
297 &*self.0 == *other
298 }
299}
300
301impl PartialEq<String> for MimeType {
302 fn eq(&self, other: &String) -> bool {
303 &*self.0 == other
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
321#[serde(transparent)]
322pub struct Email(String);
323
324impl Email {
325 #[inline]
336 pub fn new(s: impl Into<String>) -> Self {
337 Self(s.into())
338 }
339
340 #[inline]
351 pub fn as_str(&self) -> &str {
352 &self.0
353 }
354
355 #[inline]
367 pub fn into_inner(self) -> String {
368 self.0
369 }
370}
371
372impl Deref for Email {
373 type Target = str;
374
375 #[inline]
376 fn deref(&self) -> &str {
377 &self.0
378 }
379}
380
381impl From<String> for Email {
382 #[inline]
383 fn from(s: String) -> Self {
384 Self(s)
385 }
386}
387
388impl From<&str> for Email {
389 #[inline]
390 fn from(s: &str) -> Self {
391 Self(s.to_string())
392 }
393}
394
395impl AsRef<str> for Email {
396 #[inline]
397 fn as_ref(&self) -> &str {
398 &self.0
399 }
400}
401
402impl std::fmt::Display for Email {
403 #[inline]
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 self.0.fmt(f)
406 }
407}
408
409impl PartialEq<str> for Email {
410 fn eq(&self, other: &str) -> bool {
411 self.0 == other
412 }
413}
414
415impl PartialEq<&str> for Email {
416 fn eq(&self, other: &&str) -> bool {
417 self.0 == *other
418 }
419}
420
421impl PartialEq<String> for Email {
422 fn eq(&self, other: &String) -> bool {
423 &self.0 == other
424 }
425}
426
427#[derive(Debug, Clone, Default)]
429pub struct Link {
430 pub href: Url,
432 pub rel: Option<SmallString>,
435 pub link_type: Option<MimeType>,
437 pub title: Option<String>,
439 pub length: Option<u64>,
441 pub hreflang: Option<SmallString>,
443}
444
445impl Link {
446 #[inline]
448 pub fn new(href: impl Into<Url>, rel: impl AsRef<str>) -> Self {
449 Self {
450 href: href.into(),
451 rel: Some(rel.as_ref().into()),
452 link_type: None,
453 title: None,
454 length: None,
455 hreflang: None,
456 }
457 }
458
459 #[inline]
461 pub fn alternate(href: impl Into<Url>) -> Self {
462 Self::new(href, "alternate")
463 }
464
465 #[inline]
467 pub fn self_link(href: impl Into<Url>, mime_type: impl Into<MimeType>) -> Self {
468 Self {
469 href: href.into(),
470 rel: Some("self".into()),
471 link_type: Some(mime_type.into()),
472 title: None,
473 length: None,
474 hreflang: None,
475 }
476 }
477
478 #[inline]
480 pub fn enclosure(href: impl Into<Url>, mime_type: Option<MimeType>) -> Self {
481 Self {
482 href: href.into(),
483 rel: Some("enclosure".into()),
484 link_type: mime_type,
485 title: None,
486 length: None,
487 hreflang: None,
488 }
489 }
490
491 #[inline]
493 pub fn related(href: impl Into<Url>) -> Self {
494 Self::new(href, "related")
495 }
496
497 #[inline]
499 #[must_use]
500 pub fn with_type(mut self, mime_type: impl Into<MimeType>) -> Self {
501 self.link_type = Some(mime_type.into());
502 self
503 }
504}
505
506#[derive(Debug, Clone, Default)]
508pub struct Person {
509 pub name: Option<SmallString>,
511 pub email: Option<Email>,
513 pub uri: Option<String>,
515}
516
517impl Person {
518 #[inline]
531 pub fn from_name(name: impl AsRef<str>) -> Self {
532 Self {
533 name: Some(name.as_ref().into()),
534 email: None,
535 uri: None,
536 }
537 }
538}
539
540#[derive(Debug, Clone)]
542pub struct Tag {
543 pub term: SmallString,
545 pub scheme: Option<SmallString>,
547 pub label: Option<SmallString>,
549}
550
551impl Tag {
552 #[inline]
554 pub fn new(term: impl AsRef<str>) -> Self {
555 Self {
556 term: term.as_ref().into(),
557 scheme: None,
558 label: None,
559 }
560 }
561}
562
563#[derive(Debug, Clone)]
565pub struct Image {
566 pub url: Url,
568 pub title: Option<String>,
570 pub link: Option<String>,
572 pub width: Option<u32>,
574 pub height: Option<u32>,
576 pub description: Option<String>,
578}
579
580#[derive(Debug, Clone)]
582pub struct Enclosure {
583 pub url: Url,
585 pub length: Option<u64>,
587 pub enclosure_type: Option<MimeType>,
589}
590
591#[derive(Debug, Clone)]
593pub struct Content {
594 pub value: String,
596 pub content_type: Option<MimeType>,
598 pub language: Option<SmallString>,
600 pub base: Option<String>,
602}
603
604impl Content {
605 #[inline]
607 pub fn html(value: impl Into<String>) -> Self {
608 Self {
609 value: value.into(),
610 content_type: Some(MimeType::new(MimeType::TEXT_HTML)),
611 language: None,
612 base: None,
613 }
614 }
615
616 #[inline]
618 pub fn plain(value: impl Into<String>) -> Self {
619 Self {
620 value: value.into(),
621 content_type: Some(MimeType::new(MimeType::TEXT_PLAIN)),
622 language: None,
623 base: None,
624 }
625 }
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630pub enum TextType {
631 Text,
633 Html,
635 Xhtml,
637}
638
639#[derive(Debug, Clone)]
641pub struct TextConstruct {
642 pub value: String,
644 pub content_type: TextType,
646 pub language: Option<SmallString>,
648 pub base: Option<String>,
650}
651
652impl TextConstruct {
653 #[inline]
655 pub fn text(value: impl Into<String>) -> Self {
656 Self {
657 value: value.into(),
658 content_type: TextType::Text,
659 language: None,
660 base: None,
661 }
662 }
663
664 #[inline]
666 pub fn html(value: impl Into<String>) -> Self {
667 Self {
668 value: value.into(),
669 content_type: TextType::Html,
670 language: None,
671 base: None,
672 }
673 }
674
675 #[inline]
677 #[must_use]
678 pub fn with_language(mut self, language: impl AsRef<str>) -> Self {
679 self.language = Some(language.as_ref().into());
680 self
681 }
682}
683
684#[derive(Debug, Clone)]
686pub struct Generator {
687 pub value: String,
689 pub uri: Option<String>,
691 pub version: Option<SmallString>,
693}
694
695#[derive(Debug, Clone)]
697pub struct Source {
698 pub title: Option<String>,
700 pub link: Option<String>,
702 pub id: Option<String>,
704}
705
706#[derive(Debug, Clone)]
708pub struct MediaThumbnail {
709 pub url: Url,
716 pub width: Option<u32>,
718 pub height: Option<u32>,
720}
721
722#[derive(Debug, Clone)]
724pub struct MediaContent {
725 pub url: Url,
732 pub content_type: Option<MimeType>,
734 pub filesize: Option<u64>,
736 pub width: Option<u32>,
738 pub height: Option<u32>,
740 pub duration: Option<u64>,
742}
743
744impl FromAttributes for Link {
745 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
746 where
747 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
748 {
749 let mut href = None;
750 let mut rel = None;
751 let mut link_type = None;
752 let mut title = None;
753 let mut hreflang = None;
754 let mut length = None;
755
756 for attr in attrs {
757 if attr.value.len() > max_attr_length {
758 continue;
759 }
760 match attr.key.as_ref() {
761 b"href" => href = Some(bytes_to_string(&attr.value)),
762 b"rel" => rel = Some(bytes_to_string(&attr.value)),
763 b"type" => link_type = Some(bytes_to_string(&attr.value)),
764 b"title" => title = Some(bytes_to_string(&attr.value)),
765 b"hreflang" => hreflang = Some(bytes_to_string(&attr.value)),
766 b"length" => length = bytes_to_string(&attr.value).parse().ok(),
767 _ => {}
768 }
769 }
770
771 href.map(|href| Self {
772 href: Url::new(href),
773 rel: rel
774 .map(std::convert::Into::into)
775 .or_else(|| Some("alternate".into())),
776 link_type: link_type.map(MimeType::new),
777 title,
778 length,
779 hreflang: hreflang.map(std::convert::Into::into),
780 })
781 }
782}
783
784impl FromAttributes for Tag {
785 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
786 where
787 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
788 {
789 let mut term = None;
790 let mut scheme = None;
791 let mut label = None;
792
793 for attr in attrs {
794 if attr.value.len() > max_attr_length {
795 continue;
796 }
797
798 match attr.key.as_ref() {
799 b"term" => term = Some(bytes_to_string(&attr.value)),
800 b"scheme" | b"domain" => scheme = Some(bytes_to_string(&attr.value)),
801 b"label" => label = Some(bytes_to_string(&attr.value)),
802 _ => {}
803 }
804 }
805
806 term.map(|term| Self {
807 term: term.into(),
808 scheme: scheme.map(std::convert::Into::into),
809 label: label.map(std::convert::Into::into),
810 })
811 }
812}
813
814impl FromAttributes for Enclosure {
815 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
816 where
817 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
818 {
819 let mut url = None;
820 let mut length = None;
821 let mut enclosure_type = None;
822
823 for attr in attrs {
824 if attr.value.len() > max_attr_length {
825 continue;
826 }
827
828 match attr.key.as_ref() {
829 b"url" => url = Some(bytes_to_string(&attr.value)),
830 b"length" => length = bytes_to_string(&attr.value).parse().ok(),
831 b"type" => enclosure_type = Some(bytes_to_string(&attr.value)),
832 _ => {}
833 }
834 }
835
836 url.map(|url| Self {
837 url: Url::new(url),
838 length,
839 enclosure_type: enclosure_type.map(MimeType::new),
840 })
841 }
842}
843
844impl FromAttributes for MediaThumbnail {
845 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
846 where
847 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
848 {
849 let mut url = None;
850 let mut width = None;
851 let mut height = None;
852
853 for attr in attrs {
854 if attr.value.len() > max_attr_length {
855 continue;
856 }
857
858 match attr.key.as_ref() {
859 b"url" => url = Some(bytes_to_string(&attr.value)),
860 b"width" => width = bytes_to_string(&attr.value).parse().ok(),
861 b"height" => height = bytes_to_string(&attr.value).parse().ok(),
862 _ => {}
863 }
864 }
865
866 url.map(|url| Self {
867 url: Url::new(url),
868 width,
869 height,
870 })
871 }
872}
873
874impl FromAttributes for MediaContent {
875 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
876 where
877 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
878 {
879 let mut url = None;
880 let mut content_type = None;
881 let mut filesize = None;
882 let mut width = None;
883 let mut height = None;
884 let mut duration = None;
885
886 for attr in attrs {
887 if attr.value.len() > max_attr_length {
888 continue;
889 }
890
891 match attr.key.as_ref() {
892 b"url" => url = Some(bytes_to_string(&attr.value)),
893 b"type" => content_type = Some(bytes_to_string(&attr.value)),
894 b"fileSize" => filesize = bytes_to_string(&attr.value).parse().ok(),
895 b"width" => width = bytes_to_string(&attr.value).parse().ok(),
896 b"height" => height = bytes_to_string(&attr.value).parse().ok(),
897 b"duration" => duration = bytes_to_string(&attr.value).parse().ok(),
898 _ => {}
899 }
900 }
901
902 url.map(|url| Self {
903 url: Url::new(url),
904 content_type: content_type.map(MimeType::new),
905 filesize,
906 width,
907 height,
908 duration,
909 })
910 }
911}
912
913impl ParseFrom<&Value> for Person {
916 fn parse_from(json: &Value) -> Option<Self> {
920 json.as_object().map(|obj| Self {
921 name: obj
922 .get("name")
923 .and_then(Value::as_str)
924 .map(std::convert::Into::into),
925 email: None, uri: obj.get("url").and_then(Value::as_str).map(String::from),
927 })
928 }
929}
930
931impl ParseFrom<&Value> for Enclosure {
932 fn parse_from(json: &Value) -> Option<Self> {
936 let obj = json.as_object()?;
937 let url = obj.get("url").and_then(Value::as_str)?;
938 Some(Self {
939 url: Url::new(url),
940 length: obj.get("size_in_bytes").and_then(Value::as_u64),
941 enclosure_type: obj
942 .get("mime_type")
943 .and_then(Value::as_str)
944 .map(MimeType::new),
945 })
946 }
947}
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952 use serde_json::json;
953
954 #[test]
955 fn test_link_default() {
956 let link = Link::default();
957 assert!(link.href.is_empty());
958 assert!(link.rel.is_none());
959 }
960
961 #[test]
962 fn test_link_builders() {
963 let link = Link::alternate("https://example.com");
964 assert_eq!(link.href, "https://example.com");
965 assert_eq!(link.rel.as_deref(), Some("alternate"));
966
967 let link = Link::self_link("https://example.com/feed", "application/feed+json");
968 assert_eq!(link.rel.as_deref(), Some("self"));
969 assert_eq!(link.link_type.as_deref(), Some("application/feed+json"));
970
971 let link = Link::enclosure("https://example.com/audio.mp3", Some("audio/mpeg".into()));
972 assert_eq!(link.rel.as_deref(), Some("enclosure"));
973 assert_eq!(link.link_type.as_deref(), Some("audio/mpeg"));
974
975 let link = Link::related("https://other.com");
976 assert_eq!(link.rel.as_deref(), Some("related"));
977 }
978
979 #[test]
980 fn test_tag_builder() {
981 let tag = Tag::new("rust");
982 assert_eq!(tag.term, "rust");
983 assert!(tag.scheme.is_none());
984 }
985
986 #[test]
987 fn test_text_construct_builders() {
988 let text = TextConstruct::text("Hello");
989 assert_eq!(text.value, "Hello");
990 assert_eq!(text.content_type, TextType::Text);
991
992 let html = TextConstruct::html("<p>Hello</p>");
993 assert_eq!(html.content_type, TextType::Html);
994
995 let with_lang = TextConstruct::text("Hello").with_language("en");
996 assert_eq!(with_lang.language.as_deref(), Some("en"));
997 }
998
999 #[test]
1000 fn test_content_builders() {
1001 let html = Content::html("<p>Content</p>");
1002 assert_eq!(html.content_type.as_deref(), Some("text/html"));
1003
1004 let plain = Content::plain("Content");
1005 assert_eq!(plain.content_type.as_deref(), Some("text/plain"));
1006 }
1007
1008 #[test]
1009 fn test_person_default() {
1010 let person = Person::default();
1011 assert!(person.name.is_none());
1012 assert!(person.email.is_none());
1013 assert!(person.uri.is_none());
1014 }
1015
1016 #[test]
1017 fn test_person_parse_from_json() {
1018 let json = json!({"name": "John Doe", "url": "https://example.com"});
1019 let person = Person::parse_from(&json).unwrap();
1020 assert_eq!(person.name.as_deref(), Some("John Doe"));
1021 assert_eq!(person.uri.as_deref(), Some("https://example.com"));
1022 assert!(person.email.is_none());
1023 }
1024
1025 #[test]
1026 fn test_person_parse_from_empty_json() {
1027 let json = json!({});
1028 let person = Person::parse_from(&json).unwrap();
1029 assert!(person.name.is_none());
1030 }
1031
1032 #[test]
1033 fn test_enclosure_parse_from_json() {
1034 let json = json!({
1035 "url": "https://example.com/file.mp3",
1036 "mime_type": "audio/mpeg",
1037 "size_in_bytes": 12345
1038 });
1039 let enclosure = Enclosure::parse_from(&json).unwrap();
1040 assert_eq!(enclosure.url, "https://example.com/file.mp3");
1041 assert_eq!(enclosure.enclosure_type.as_deref(), Some("audio/mpeg"));
1042 assert_eq!(enclosure.length, Some(12345));
1043 }
1044
1045 #[test]
1046 fn test_enclosure_parse_from_json_missing_url() {
1047 let json = json!({"mime_type": "audio/mpeg"});
1048 assert!(Enclosure::parse_from(&json).is_none());
1049 }
1050
1051 #[test]
1052 fn test_text_type_equality() {
1053 assert_eq!(TextType::Text, TextType::Text);
1054 assert_ne!(TextType::Text, TextType::Html);
1055 }
1056
1057 #[test]
1060 fn test_url_new() {
1061 let url = Url::new("https://example.com");
1062 assert_eq!(url.as_str(), "https://example.com");
1063 }
1064
1065 #[test]
1066 fn test_url_from_string() {
1067 let url: Url = String::from("https://example.com").into();
1068 assert_eq!(url.as_str(), "https://example.com");
1069 }
1070
1071 #[test]
1072 fn test_url_from_str() {
1073 let url: Url = "https://example.com".into();
1074 assert_eq!(url.as_str(), "https://example.com");
1075 }
1076
1077 #[test]
1078 fn test_url_deref() {
1079 let url = Url::new("https://example.com");
1080 assert_eq!(url.len(), 19);
1082 assert!(url.starts_with("https://"));
1083 }
1084
1085 #[test]
1086 fn test_url_into_inner() {
1087 let url = Url::new("https://example.com");
1088 let inner = url.into_inner();
1089 assert_eq!(inner, "https://example.com");
1090 }
1091
1092 #[test]
1093 fn test_url_default() {
1094 let url = Url::default();
1095 assert_eq!(url.as_str(), "");
1096 }
1097
1098 #[test]
1099 fn test_url_clone() {
1100 let url1 = Url::new("https://example.com");
1101 let url2 = url1.clone();
1102 assert_eq!(url1, url2);
1103 }
1104
1105 #[test]
1106 fn test_mime_type_new() {
1107 let mime = MimeType::new("text/html");
1108 assert_eq!(mime.as_str(), "text/html");
1109 }
1110
1111 #[test]
1112 fn test_mime_type_from_string() {
1113 let mime: MimeType = String::from("application/json").into();
1114 assert_eq!(mime.as_str(), "application/json");
1115 }
1116
1117 #[test]
1118 fn test_mime_type_from_str() {
1119 let mime: MimeType = "text/plain".into();
1120 assert_eq!(mime.as_str(), "text/plain");
1121 }
1122
1123 #[test]
1124 fn test_mime_type_deref() {
1125 let mime = MimeType::new("text/html");
1126 assert_eq!(mime.len(), 9);
1127 assert!(mime.starts_with("text/"));
1128 }
1129
1130 #[test]
1131 fn test_mime_type_default() {
1132 let mime = MimeType::default();
1133 assert_eq!(mime.as_str(), "");
1134 }
1135
1136 #[test]
1137 fn test_mime_type_clone() {
1138 let mime1 = MimeType::new("application/xml");
1139 let mime2 = mime1.clone();
1140 assert_eq!(mime1, mime2);
1141 }
1143
1144 #[test]
1145 fn test_mime_type_constants() {
1146 assert_eq!(MimeType::TEXT_HTML, "text/html");
1147 assert_eq!(MimeType::TEXT_PLAIN, "text/plain");
1148 assert_eq!(MimeType::APPLICATION_XML, "application/xml");
1149 assert_eq!(MimeType::APPLICATION_JSON, "application/json");
1150 }
1151
1152 #[test]
1153 fn test_email_new() {
1154 let email = Email::new("user@example.com");
1155 assert_eq!(email.as_str(), "user@example.com");
1156 }
1157
1158 #[test]
1159 fn test_email_from_string() {
1160 let email: Email = String::from("user@example.com").into();
1161 assert_eq!(email.as_str(), "user@example.com");
1162 }
1163
1164 #[test]
1165 fn test_email_from_str() {
1166 let email: Email = "user@example.com".into();
1167 assert_eq!(email.as_str(), "user@example.com");
1168 }
1169
1170 #[test]
1171 fn test_email_deref() {
1172 let email = Email::new("user@example.com");
1173 assert_eq!(email.len(), 16);
1174 assert!(email.contains('@'));
1175 }
1176
1177 #[test]
1178 fn test_email_into_inner() {
1179 let email = Email::new("user@example.com");
1180 let inner = email.into_inner();
1181 assert_eq!(inner, "user@example.com");
1182 }
1183
1184 #[test]
1185 fn test_email_default() {
1186 let email = Email::default();
1187 assert_eq!(email.as_str(), "");
1188 }
1189
1190 #[test]
1191 fn test_email_clone() {
1192 let email1 = Email::new("user@example.com");
1193 let email2 = email1.clone();
1194 assert_eq!(email1, email2);
1195 }
1196}