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