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<String>,
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<String>,
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 name: String,
710 pub href: 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<String>,
739 pub height: Option<String>,
741}
742
743#[derive(Debug, Clone)]
745pub struct MediaContent {
746 pub url: Url,
753 pub content_type: Option<MimeType>,
755 pub medium: Option<String>,
757 pub filesize: Option<u64>,
759 pub width: Option<String>,
761 pub height: Option<String>,
763 pub duration: Option<String>,
765 pub bitrate: Option<String>,
767 pub lang: Option<String>,
769 pub channels: Option<String>,
771 pub codec: Option<String>,
773 pub expression: Option<String>,
775 pub isdefault: Option<String>,
777 pub samplingrate: Option<String>,
779}
780
781impl FromAttributes for Link {
782 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
783 where
784 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
785 {
786 let mut href = None;
787 let mut rel = None;
788 let mut link_type = None;
789 let mut title = None;
790 let mut hreflang = None;
791 let mut length = None;
792 let mut thr_count = None;
793 let mut thr_updated = None;
794
795 for attr in attrs {
796 if attr.value.len() > max_attr_length {
797 continue;
798 }
799 match attr.key.as_ref() {
800 b"href" => href = Some(bytes_to_string(&attr.value)),
801 b"rel" => rel = Some(bytes_to_string(&attr.value)),
802 b"type" => link_type = Some(bytes_to_string(&attr.value)),
803 b"title" => title = Some(bytes_to_string(&attr.value)),
804 b"hreflang" => hreflang = Some(bytes_to_string(&attr.value)),
805 b"length" => length = Some(bytes_to_string(&attr.value)),
806 b"thr:count" => {
807 thr_count = bytes_to_string(&attr.value).trim().parse::<u32>().ok();
808 }
809 b"thr:updated" => {
810 thr_updated = parse_date(bytes_to_string(&attr.value).trim());
811 }
812 _ => {}
813 }
814 }
815
816 let rel_str: Option<SmallString> = rel
817 .map(std::convert::Into::into)
818 .or_else(|| Some("alternate".into()));
819 let resolved_type = link_type
820 .map(MimeType::new)
821 .or_else(|| match rel_str.as_deref() {
822 Some("self") => Some(MimeType::new("application/atom+xml")),
823 _ => Some(MimeType::new("text/html")),
824 });
825
826 href.map(|href| Self {
827 href: Url::new(href),
828 rel: rel_str,
829 link_type: resolved_type,
830 title,
831 length,
832 hreflang: hreflang.map(std::convert::Into::into),
833 thr_count,
834 thr_updated,
835 })
836 }
837}
838
839impl FromAttributes for Tag {
840 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
841 where
842 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
843 {
844 let mut term = None;
845 let mut scheme = None;
846 let mut label = None;
847
848 for attr in attrs {
849 if attr.value.len() > max_attr_length {
850 continue;
851 }
852
853 match attr.key.as_ref() {
854 b"term" => term = Some(bytes_to_string(&attr.value)),
855 b"scheme" | b"domain" => scheme = Some(bytes_to_string(&attr.value)),
856 b"label" => label = Some(bytes_to_string(&attr.value)),
857 _ => {}
858 }
859 }
860
861 term.map(|term| Self {
862 term: term.into(),
863 scheme: scheme.map(std::convert::Into::into),
864 label: label.map(std::convert::Into::into),
865 })
866 }
867}
868
869impl FromAttributes for Enclosure {
870 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
871 where
872 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
873 {
874 let mut url = None;
875 let mut length = None;
876 let mut enclosure_type = None;
877
878 for attr in attrs {
879 if attr.value.len() > max_attr_length {
880 continue;
881 }
882
883 match attr.key.as_ref() {
884 b"url" => url = Some(bytes_to_string(&attr.value)),
885 b"length" => length = Some(bytes_to_string(&attr.value)),
886 b"type" => enclosure_type = Some(bytes_to_string(&attr.value)),
887 _ => {}
888 }
889 }
890
891 url.map(|url| Self {
892 url: Url::new(url),
893 length,
894 enclosure_type: enclosure_type.map(MimeType::new),
895 })
896 }
897}
898
899impl FromAttributes for MediaThumbnail {
900 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
901 where
902 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
903 {
904 let mut url = None;
905 let mut width = None;
906 let mut height = None;
907
908 for attr in attrs {
909 if attr.value.len() > max_attr_length {
910 continue;
911 }
912
913 match attr.key.as_ref() {
914 b"url" => url = Some(bytes_to_string(&attr.value)),
915 b"width" => width = Some(bytes_to_string(&attr.value)),
916 b"height" => height = Some(bytes_to_string(&attr.value)),
917 _ => {}
918 }
919 }
920
921 url.map(|url| Self {
922 url: Url::new(url),
923 width,
924 height,
925 })
926 }
927}
928
929impl FromAttributes for MediaContent {
930 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
931 where
932 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
933 {
934 let mut url = None;
935 let mut content_type = None;
936 let mut medium = None;
937 let mut filesize = None;
938 let mut width = None;
939 let mut height = None;
940 let mut duration = None;
941 let mut bitrate = None;
942 let mut lang = None;
943 let mut channels = None;
944 let mut codec = None;
945 let mut expression = None;
946 let mut isdefault = None;
947 let mut samplingrate = None;
948
949 for attr in attrs {
950 if attr.value.len() > max_attr_length {
951 continue;
952 }
953
954 match attr.key.as_ref() {
955 b"url" => url = Some(bytes_to_string(&attr.value)),
956 b"type" => content_type = Some(bytes_to_string(&attr.value)),
957 b"medium" => medium = Some(bytes_to_string(&attr.value)),
958 b"fileSize" => filesize = bytes_to_string(&attr.value).parse().ok(),
959 b"width" => width = Some(bytes_to_string(&attr.value)),
960 b"height" => height = Some(bytes_to_string(&attr.value)),
961 b"duration" => duration = Some(bytes_to_string(&attr.value)),
962 b"bitrate" => bitrate = Some(bytes_to_string(&attr.value)),
963 b"lang" => lang = Some(bytes_to_string(&attr.value)),
964 b"channels" => channels = Some(bytes_to_string(&attr.value)),
965 b"codec" => codec = Some(bytes_to_string(&attr.value)),
966 b"expression" => expression = Some(bytes_to_string(&attr.value)),
967 b"isDefault" => isdefault = Some(bytes_to_string(&attr.value)),
968 b"samplingrate" => samplingrate = Some(bytes_to_string(&attr.value)),
969 _ => {}
970 }
971 }
972
973 url.map(|url| Self {
974 url: Url::new(url),
975 content_type: content_type.map(MimeType::new),
976 medium,
977 filesize,
978 width,
979 height,
980 duration,
981 bitrate,
982 lang,
983 channels,
984 codec,
985 expression,
986 isdefault,
987 samplingrate,
988 })
989 }
990}
991
992impl ParseFrom<&Value> for Person {
995 fn parse_from(json: &Value) -> Option<Self> {
999 json.as_object().map(|obj| Self {
1000 name: obj
1001 .get("name")
1002 .and_then(Value::as_str)
1003 .map(std::convert::Into::into),
1004 email: None, uri: obj.get("url").and_then(Value::as_str).map(String::from),
1006 })
1007 }
1008}
1009
1010impl ParseFrom<&Value> for Enclosure {
1011 fn parse_from(json: &Value) -> Option<Self> {
1015 let obj = json.as_object()?;
1016 let url = obj.get("url").and_then(Value::as_str)?;
1017 Some(Self {
1018 url: Url::new(url),
1019 length: obj
1020 .get("size_in_bytes")
1021 .and_then(Value::as_u64)
1022 .map(|v| v.to_string()),
1023 enclosure_type: obj
1024 .get("mime_type")
1025 .and_then(Value::as_str)
1026 .map(MimeType::new),
1027 })
1028 }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033 use super::*;
1034 use serde_json::json;
1035
1036 #[test]
1037 fn test_link_default() {
1038 let link = Link::default();
1039 assert!(link.href.is_empty());
1040 assert!(link.rel.is_none());
1041 }
1042
1043 #[test]
1044 fn test_link_builders() {
1045 let link = Link::alternate("https://example.com");
1046 assert_eq!(link.href, "https://example.com");
1047 assert_eq!(link.rel.as_deref(), Some("alternate"));
1048
1049 let link = Link::self_link("https://example.com/feed", "application/feed+json");
1050 assert_eq!(link.rel.as_deref(), Some("self"));
1051 assert_eq!(link.link_type.as_deref(), Some("application/feed+json"));
1052
1053 let link = Link::enclosure("https://example.com/audio.mp3", Some("audio/mpeg".into()));
1054 assert_eq!(link.rel.as_deref(), Some("enclosure"));
1055 assert_eq!(link.link_type.as_deref(), Some("audio/mpeg"));
1056
1057 let link = Link::related("https://other.com");
1058 assert_eq!(link.rel.as_deref(), Some("related"));
1059 }
1060
1061 #[test]
1062 fn test_tag_builder() {
1063 let tag = Tag::new("rust");
1064 assert_eq!(tag.term, "rust");
1065 assert!(tag.scheme.is_none());
1066 }
1067
1068 #[test]
1069 fn test_text_construct_builders() {
1070 let text = TextConstruct::text("Hello");
1071 assert_eq!(text.value, "Hello");
1072 assert_eq!(text.content_type, TextType::Text);
1073
1074 let html = TextConstruct::html("<p>Hello</p>");
1075 assert_eq!(html.content_type, TextType::Html);
1076
1077 let with_lang = TextConstruct::text("Hello").with_language("en");
1078 assert_eq!(with_lang.language.as_deref(), Some("en"));
1079 }
1080
1081 #[test]
1082 fn test_content_builders() {
1083 let html = Content::html("<p>Content</p>");
1084 assert_eq!(html.content_type.as_deref(), Some("text/html"));
1085
1086 let plain = Content::plain("Content");
1087 assert_eq!(plain.content_type.as_deref(), Some("text/plain"));
1088 }
1089
1090 #[test]
1091 fn test_person_default() {
1092 let person = Person::default();
1093 assert!(person.name.is_none());
1094 assert!(person.email.is_none());
1095 assert!(person.uri.is_none());
1096 }
1097
1098 #[test]
1099 fn test_person_parse_from_json() {
1100 let json = json!({"name": "John Doe", "url": "https://example.com"});
1101 let person = Person::parse_from(&json).unwrap();
1102 assert_eq!(person.name.as_deref(), Some("John Doe"));
1103 assert_eq!(person.uri.as_deref(), Some("https://example.com"));
1104 assert!(person.email.is_none());
1105 }
1106
1107 #[test]
1108 fn test_person_parse_from_empty_json() {
1109 let json = json!({});
1110 let person = Person::parse_from(&json).unwrap();
1111 assert!(person.name.is_none());
1112 }
1113
1114 #[test]
1115 fn test_enclosure_parse_from_json() {
1116 let json = json!({
1117 "url": "https://example.com/file.mp3",
1118 "mime_type": "audio/mpeg",
1119 "size_in_bytes": 12345
1120 });
1121 let enclosure = Enclosure::parse_from(&json).unwrap();
1122 assert_eq!(enclosure.url, "https://example.com/file.mp3");
1123 assert_eq!(enclosure.enclosure_type.as_deref(), Some("audio/mpeg"));
1124 assert_eq!(enclosure.length.as_deref(), Some("12345"));
1125 }
1126
1127 #[test]
1128 fn test_enclosure_parse_from_json_missing_url() {
1129 let json = json!({"mime_type": "audio/mpeg"});
1130 assert!(Enclosure::parse_from(&json).is_none());
1131 }
1132
1133 #[test]
1134 fn test_text_type_equality() {
1135 assert_eq!(TextType::Text, TextType::Text);
1136 assert_ne!(TextType::Text, TextType::Html);
1137 }
1138
1139 #[test]
1142 fn test_url_new() {
1143 let url = Url::new("https://example.com");
1144 assert_eq!(url.as_str(), "https://example.com");
1145 }
1146
1147 #[test]
1148 fn test_url_from_string() {
1149 let url: Url = String::from("https://example.com").into();
1150 assert_eq!(url.as_str(), "https://example.com");
1151 }
1152
1153 #[test]
1154 fn test_url_from_str() {
1155 let url: Url = "https://example.com".into();
1156 assert_eq!(url.as_str(), "https://example.com");
1157 }
1158
1159 #[test]
1160 fn test_url_deref() {
1161 let url = Url::new("https://example.com");
1162 assert_eq!(url.len(), 19);
1164 assert!(url.starts_with("https://"));
1165 }
1166
1167 #[test]
1168 fn test_url_into_inner() {
1169 let url = Url::new("https://example.com");
1170 let inner = url.into_inner();
1171 assert_eq!(inner, "https://example.com");
1172 }
1173
1174 #[test]
1175 fn test_url_default() {
1176 let url = Url::default();
1177 assert_eq!(url.as_str(), "");
1178 }
1179
1180 #[test]
1181 fn test_url_clone() {
1182 let url1 = Url::new("https://example.com");
1183 let url2 = url1.clone();
1184 assert_eq!(url1, url2);
1185 }
1186
1187 #[test]
1188 fn test_mime_type_new() {
1189 let mime = MimeType::new("text/html");
1190 assert_eq!(mime.as_str(), "text/html");
1191 }
1192
1193 #[test]
1194 fn test_mime_type_from_string() {
1195 let mime: MimeType = String::from("application/json").into();
1196 assert_eq!(mime.as_str(), "application/json");
1197 }
1198
1199 #[test]
1200 fn test_mime_type_from_str() {
1201 let mime: MimeType = "text/plain".into();
1202 assert_eq!(mime.as_str(), "text/plain");
1203 }
1204
1205 #[test]
1206 fn test_mime_type_deref() {
1207 let mime = MimeType::new("text/html");
1208 assert_eq!(mime.len(), 9);
1209 assert!(mime.starts_with("text/"));
1210 }
1211
1212 #[test]
1213 fn test_mime_type_default() {
1214 let mime = MimeType::default();
1215 assert_eq!(mime.as_str(), "");
1216 }
1217
1218 #[test]
1219 fn test_mime_type_clone() {
1220 let mime1 = MimeType::new("application/xml");
1221 let mime2 = mime1.clone();
1222 assert_eq!(mime1, mime2);
1223 }
1225
1226 #[test]
1227 fn test_mime_type_constants() {
1228 assert_eq!(MimeType::TEXT_HTML, "text/html");
1229 assert_eq!(MimeType::TEXT_PLAIN, "text/plain");
1230 assert_eq!(MimeType::APPLICATION_XML, "application/xml");
1231 assert_eq!(MimeType::APPLICATION_JSON, "application/json");
1232 }
1233
1234 #[test]
1235 fn test_email_new() {
1236 let email = Email::new("user@example.com");
1237 assert_eq!(email.as_str(), "user@example.com");
1238 }
1239
1240 #[test]
1241 fn test_email_from_string() {
1242 let email: Email = String::from("user@example.com").into();
1243 assert_eq!(email.as_str(), "user@example.com");
1244 }
1245
1246 #[test]
1247 fn test_email_from_str() {
1248 let email: Email = "user@example.com".into();
1249 assert_eq!(email.as_str(), "user@example.com");
1250 }
1251
1252 #[test]
1253 fn test_email_deref() {
1254 let email = Email::new("user@example.com");
1255 assert_eq!(email.len(), 16);
1256 assert!(email.contains('@'));
1257 }
1258
1259 #[test]
1260 fn test_email_into_inner() {
1261 let email = Email::new("user@example.com");
1262 let inner = email.into_inner();
1263 assert_eq!(inner, "user@example.com");
1264 }
1265
1266 #[test]
1267 fn test_email_default() {
1268 let email = Email::default();
1269 assert_eq!(email.as_str(), "");
1270 }
1271
1272 #[test]
1273 fn test_email_clone() {
1274 let email1 = Email::new("user@example.com");
1275 let email2 = email1.clone();
1276 assert_eq!(email1, email2);
1277 }
1278}