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 pub fn hub(href: impl Into<Url>) -> Self {
521 Self::new(href, "hub")
522 }
523
524 #[inline]
526 #[must_use]
527 pub fn with_type(mut self, mime_type: impl Into<MimeType>) -> Self {
528 self.link_type = Some(mime_type.into());
529 self
530 }
531}
532
533#[derive(Debug, Clone, Default)]
535pub struct Person {
536 pub name: Option<SmallString>,
538 pub email: Option<Email>,
540 pub uri: Option<String>,
542 pub avatar: Option<String>,
544}
545
546impl Person {
547 #[inline]
560 pub fn from_name(name: impl AsRef<str>) -> Self {
561 Self {
562 name: Some(name.as_ref().into()),
563 email: None,
564 uri: None,
565 avatar: None,
566 }
567 }
568
569 #[must_use]
573 pub fn flat_string(&self) -> Option<SmallString> {
574 match (&self.name, &self.email) {
575 (Some(name), Some(email)) => Some(format!("{name} ({email})").into()),
576 (Some(name), None) => Some(name.clone()),
577 (None, Some(email)) => Some(email.as_str().into()),
578 (None, None) => None,
579 }
580 }
581}
582
583#[derive(Debug, Clone)]
585pub struct Tag {
586 pub term: SmallString,
588 pub scheme: Option<SmallString>,
590 pub label: Option<SmallString>,
592}
593
594impl Tag {
595 #[inline]
597 pub fn new(term: impl AsRef<str>) -> Self {
598 Self {
599 term: term.as_ref().into(),
600 scheme: None,
601 label: None,
602 }
603 }
604}
605
606#[derive(Debug, Default, Clone, PartialEq, Eq)]
608pub struct Cloud {
609 pub domain: Option<String>,
611 pub port: Option<String>,
613 pub path: Option<String>,
615 pub register_procedure: Option<String>,
617 pub protocol: Option<String>,
619}
620
621#[derive(Debug, Default, Clone, PartialEq, Eq)]
623pub struct TextInput {
624 pub title: Option<String>,
626 pub description: Option<String>,
628 pub name: Option<String>,
630 pub link: Option<String>,
632}
633
634#[derive(Debug, Clone)]
636pub struct Image {
637 pub url: Url,
639 pub title: Option<String>,
641 pub link: Option<String>,
643 pub width: Option<u32>,
645 pub height: Option<u32>,
647 pub description: Option<String>,
649}
650
651#[derive(Debug, Clone)]
653pub struct Enclosure {
654 pub url: Url,
656 pub length: Option<String>,
658 pub enclosure_type: Option<MimeType>,
660 pub title: Option<String>,
662 pub duration: Option<String>,
664}
665
666#[derive(Debug, Clone)]
668pub struct Content {
669 pub value: String,
671 pub content_type: Option<MimeType>,
673 pub language: Option<SmallString>,
675 pub base: Option<String>,
677 pub src: Option<String>,
679}
680
681impl Content {
682 #[inline]
684 pub fn html(value: impl Into<String>) -> Self {
685 Self {
686 value: value.into(),
687 content_type: Some(MimeType::new(MimeType::TEXT_HTML)),
688 language: None,
689 base: None,
690 src: None,
691 }
692 }
693
694 #[inline]
696 pub fn plain(value: impl Into<String>) -> Self {
697 Self {
698 value: value.into(),
699 content_type: Some(MimeType::new(MimeType::TEXT_PLAIN)),
700 language: None,
701 base: None,
702 src: None,
703 }
704 }
705}
706
707#[derive(Debug, Clone, Copy, PartialEq, Eq)]
709pub enum TextType {
710 Text,
712 Html,
714 Xhtml,
716}
717
718impl TextType {
719 #[must_use]
740 pub fn from_type_attr(raw: &str) -> Self {
741 match raw.trim().to_ascii_lowercase().as_str() {
742 "text" | "text/plain" => Self::Text,
743 "xhtml" | "application/xhtml+xml" => Self::Xhtml,
744 _ => Self::Html,
745 }
746 }
747}
748
749#[derive(Debug, Clone)]
751pub struct TextConstruct {
752 pub value: String,
754 pub content_type: TextType,
756 pub language: Option<SmallString>,
758 pub base: Option<String>,
760}
761
762impl TextConstruct {
763 #[inline]
765 pub fn text(value: impl Into<String>) -> Self {
766 Self {
767 value: value.into(),
768 content_type: TextType::Text,
769 language: None,
770 base: None,
771 }
772 }
773
774 #[inline]
776 pub fn html(value: impl Into<String>) -> Self {
777 Self {
778 value: value.into(),
779 content_type: TextType::Html,
780 language: None,
781 base: None,
782 }
783 }
784
785 #[inline]
787 #[must_use]
788 pub fn with_language(mut self, language: impl AsRef<str>) -> Self {
789 self.language = Some(language.as_ref().into());
790 self
791 }
792}
793
794#[derive(Debug, Clone)]
796pub struct Generator {
797 pub name: String,
799 pub href: Option<String>,
801 pub version: Option<SmallString>,
803}
804
805#[derive(Debug, Clone)]
807pub struct Source {
808 pub title: Option<String>,
810 pub href: Option<String>,
812 pub link: Option<String>,
814 pub author: Option<String>,
816 pub id: Option<String>,
818 pub links: Vec<Link>,
820 pub updated: Option<DateTime<Utc>>,
822 pub updated_str: Option<String>,
824 pub rights: Option<String>,
826 pub guidislink: Option<bool>,
832}
833
834#[derive(Debug, Clone)]
836pub struct MediaThumbnail {
837 pub url: Url,
844 pub width: Option<String>,
846 pub height: Option<String>,
848 pub time: Option<String>,
852}
853
854#[derive(Debug, Clone)]
856pub struct MediaContent {
857 pub url: Url,
864 pub content_type: Option<MimeType>,
866 pub medium: Option<String>,
868 pub filesize: Option<String>,
870 pub width: Option<String>,
872 pub height: Option<String>,
874 pub duration: Option<String>,
876 pub bitrate: Option<String>,
878 pub lang: Option<String>,
880 pub channels: Option<String>,
882 pub codec: Option<String>,
884 pub expression: Option<String>,
886 pub isdefault: Option<String>,
888 pub samplingrate: Option<String>,
890 pub framerate: Option<String>,
892}
893
894#[derive(Debug, Clone, Default, PartialEq, Eq)]
899pub struct MediaRating {
900 pub scheme: Option<String>,
902 pub content: String,
904}
905
906impl FromAttributes for Link {
907 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
908 where
909 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
910 {
911 let mut href = None;
912 let mut rel = None;
913 let mut link_type = None;
914 let mut title = None;
915 let mut hreflang = None;
916 let mut length = None;
917 let mut thr_count = None;
918 let mut thr_updated = None;
919
920 for attr in attrs {
921 if attr.value.len() > max_attr_length {
922 continue;
923 }
924 match attr.key.as_ref() {
925 b"href" => href = Some(bytes_to_string(&attr.value)),
926 b"rel" => rel = Some(bytes_to_string(&attr.value)),
927 b"type" => link_type = Some(bytes_to_string(&attr.value)),
928 b"title" => title = Some(bytes_to_string(&attr.value)),
929 b"hreflang" => hreflang = Some(bytes_to_string(&attr.value)),
930 b"length" => length = Some(bytes_to_string(&attr.value)),
931 b"thr:count" => {
932 thr_count = bytes_to_string(&attr.value).trim().parse::<u32>().ok();
933 }
934 b"thr:updated" => {
935 thr_updated = parse_date(bytes_to_string(&attr.value).trim());
936 }
937 _ => {}
938 }
939 }
940
941 let rel_str: Option<SmallString> = rel
942 .map(std::convert::Into::into)
943 .or_else(|| Some("alternate".into()));
944 let resolved_type = link_type
945 .map(MimeType::new)
946 .or_else(|| match rel_str.as_deref() {
947 Some("self") => Some(MimeType::new("application/atom+xml")),
948 _ => Some(MimeType::new("text/html")),
949 });
950
951 href.map(|href| Self {
952 href: Url::new(href),
953 rel: rel_str,
954 link_type: resolved_type,
955 title,
956 length,
957 hreflang: hreflang.map(std::convert::Into::into),
958 thr_count,
959 thr_updated,
960 })
961 }
962}
963
964impl FromAttributes for Tag {
965 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
966 where
967 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
968 {
969 let mut term = None;
970 let mut scheme = None;
971 let mut label = None;
972
973 for attr in attrs {
974 if attr.value.len() > max_attr_length {
975 continue;
976 }
977
978 match attr.key.as_ref() {
979 b"term" => term = Some(bytes_to_string(&attr.value)),
980 b"scheme" | b"domain" => scheme = Some(bytes_to_string(&attr.value)),
981 b"label" => label = Some(bytes_to_string(&attr.value)),
982 _ => {}
983 }
984 }
985
986 term.map(|term| Self {
987 term: term.into(),
988 scheme: scheme.map(std::convert::Into::into),
989 label: label.map(std::convert::Into::into),
990 })
991 }
992}
993
994impl FromAttributes for Enclosure {
995 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
996 where
997 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
998 {
999 let mut url = None;
1000 let mut length = None;
1001 let mut enclosure_type = None;
1002
1003 for attr in attrs {
1004 if attr.value.len() > max_attr_length {
1005 continue;
1006 }
1007
1008 match attr.key.as_ref() {
1009 b"url" => url = Some(bytes_to_string(&attr.value)),
1010 b"length" => length = Some(bytes_to_string(&attr.value)),
1011 b"type" => enclosure_type = Some(bytes_to_string(&attr.value)),
1012 _ => {}
1013 }
1014 }
1015
1016 url.map(|url| Self {
1017 url: Url::new(url),
1018 length,
1019 enclosure_type: enclosure_type.map(MimeType::new),
1020 title: None,
1021 duration: None,
1022 })
1023 }
1024}
1025
1026impl FromAttributes for MediaThumbnail {
1027 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
1028 where
1029 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
1030 {
1031 let mut url = None;
1032 let mut width = None;
1033 let mut height = None;
1034
1035 let mut time = None;
1036
1037 for attr in attrs {
1038 if attr.value.len() > max_attr_length {
1039 continue;
1040 }
1041
1042 match attr.key.as_ref() {
1043 b"url" => url = Some(bytes_to_string(&attr.value)),
1044 b"width" => width = Some(bytes_to_string(&attr.value)),
1045 b"height" => height = Some(bytes_to_string(&attr.value)),
1046 b"time" => time = Some(bytes_to_string(&attr.value)),
1047 _ => {}
1048 }
1049 }
1050
1051 url.map(|url| Self {
1052 url: Url::new(url),
1053 width,
1054 height,
1055 time,
1056 })
1057 }
1058}
1059
1060impl FromAttributes for MediaContent {
1061 fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
1062 where
1063 I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>,
1064 {
1065 let mut url = None;
1066 let mut content_type = None;
1067 let mut medium = None;
1068 let mut filesize = None;
1069 let mut width = None;
1070 let mut height = None;
1071 let mut duration = None;
1072 let mut bitrate = None;
1073 let mut lang = None;
1074 let mut channels = None;
1075 let mut codec = None;
1076 let mut expression = None;
1077 let mut isdefault = None;
1078 let mut samplingrate = None;
1079 let mut framerate = None;
1080
1081 for attr in attrs {
1082 if attr.value.len() > max_attr_length {
1083 continue;
1084 }
1085
1086 match attr.key.as_ref() {
1087 b"url" => url = Some(bytes_to_string(&attr.value)),
1088 b"type" => content_type = Some(bytes_to_string(&attr.value)),
1089 b"medium" => medium = Some(bytes_to_string(&attr.value)),
1090 b"fileSize" => filesize = Some(bytes_to_string(&attr.value)),
1091 b"width" => width = Some(bytes_to_string(&attr.value)),
1092 b"height" => height = Some(bytes_to_string(&attr.value)),
1093 b"duration" => duration = Some(bytes_to_string(&attr.value)),
1094 b"bitrate" => bitrate = Some(bytes_to_string(&attr.value)),
1095 b"lang" => lang = Some(bytes_to_string(&attr.value)),
1096 b"channels" => channels = Some(bytes_to_string(&attr.value)),
1097 b"codec" => codec = Some(bytes_to_string(&attr.value)),
1098 b"expression" => expression = Some(bytes_to_string(&attr.value)),
1099 b"isDefault" => isdefault = Some(bytes_to_string(&attr.value)),
1100 b"samplingrate" => samplingrate = Some(bytes_to_string(&attr.value)),
1101 b"framerate" => framerate = Some(bytes_to_string(&attr.value)),
1102 _ => {}
1103 }
1104 }
1105
1106 url.map(|url| Self {
1107 url: Url::new(url),
1108 content_type: content_type.map(MimeType::new),
1109 medium,
1110 filesize,
1111 width,
1112 height,
1113 duration,
1114 bitrate,
1115 lang,
1116 channels,
1117 codec,
1118 expression,
1119 isdefault,
1120 samplingrate,
1121 framerate,
1122 })
1123 }
1124}
1125
1126impl ParseFrom<&Value> for Person {
1129 fn parse_from(json: &Value) -> Option<Self> {
1133 json.as_object().map(|obj| Self {
1134 name: obj
1135 .get("name")
1136 .and_then(Value::as_str)
1137 .map(std::convert::Into::into),
1138 email: None, uri: obj.get("url").and_then(Value::as_str).map(String::from),
1140 avatar: obj.get("avatar").and_then(Value::as_str).map(String::from),
1141 })
1142 }
1143}
1144
1145impl ParseFrom<&Value> for Enclosure {
1146 fn parse_from(json: &Value) -> Option<Self> {
1150 let obj = json.as_object()?;
1151 let url = obj.get("url").and_then(Value::as_str)?;
1152 Some(Self {
1153 url: Url::new(url),
1154 length: obj
1155 .get("size_in_bytes")
1156 .and_then(Value::as_u64)
1157 .map(|v| v.to_string()),
1158 enclosure_type: obj
1159 .get("mime_type")
1160 .and_then(Value::as_str)
1161 .map(MimeType::new),
1162 title: obj.get("title").and_then(Value::as_str).map(String::from),
1163 duration: obj
1164 .get("duration_in_seconds")
1165 .and_then(Value::as_u64)
1166 .map(|v| v.to_string()),
1167 })
1168 }
1169}
1170
1171#[derive(Debug, Clone, Default)]
1173pub struct MediaCredit {
1174 pub role: Option<String>,
1176 pub scheme: Option<String>,
1178 pub content: String,
1180}
1181
1182#[derive(Debug, Clone, Default)]
1184pub struct MediaCopyright {
1185 pub url: Option<String>,
1187}
1188
1189#[cfg(test)]
1190mod tests {
1191 use super::*;
1192 use serde_json::json;
1193
1194 #[test]
1195 fn test_link_default() {
1196 let link = Link::default();
1197 assert!(link.href.is_empty());
1198 assert!(link.rel.is_none());
1199 }
1200
1201 #[test]
1202 fn test_link_builders() {
1203 let link = Link::alternate("https://example.com");
1204 assert_eq!(link.href, "https://example.com");
1205 assert_eq!(link.rel.as_deref(), Some("alternate"));
1206
1207 let link = Link::self_link("https://example.com/feed", "application/feed+json");
1208 assert_eq!(link.rel.as_deref(), Some("self"));
1209 assert_eq!(link.link_type.as_deref(), Some("application/feed+json"));
1210
1211 let link = Link::enclosure("https://example.com/audio.mp3", Some("audio/mpeg".into()));
1212 assert_eq!(link.rel.as_deref(), Some("enclosure"));
1213 assert_eq!(link.link_type.as_deref(), Some("audio/mpeg"));
1214
1215 let link = Link::related("https://other.com");
1216 assert_eq!(link.rel.as_deref(), Some("related"));
1217 }
1218
1219 #[test]
1220 fn test_tag_builder() {
1221 let tag = Tag::new("rust");
1222 assert_eq!(tag.term, "rust");
1223 assert!(tag.scheme.is_none());
1224 }
1225
1226 #[test]
1227 fn test_text_construct_builders() {
1228 let text = TextConstruct::text("Hello");
1229 assert_eq!(text.value, "Hello");
1230 assert_eq!(text.content_type, TextType::Text);
1231
1232 let html = TextConstruct::html("<p>Hello</p>");
1233 assert_eq!(html.content_type, TextType::Html);
1234
1235 let with_lang = TextConstruct::text("Hello").with_language("en");
1236 assert_eq!(with_lang.language.as_deref(), Some("en"));
1237 }
1238
1239 #[test]
1240 fn test_content_builders() {
1241 let html = Content::html("<p>Content</p>");
1242 assert_eq!(html.content_type.as_deref(), Some("text/html"));
1243
1244 let plain = Content::plain("Content");
1245 assert_eq!(plain.content_type.as_deref(), Some("text/plain"));
1246 }
1247
1248 #[test]
1249 fn test_person_default() {
1250 let person = Person::default();
1251 assert!(person.name.is_none());
1252 assert!(person.email.is_none());
1253 assert!(person.uri.is_none());
1254 }
1255
1256 #[test]
1257 fn test_person_parse_from_json() {
1258 let json = json!({"name": "John Doe", "url": "https://example.com"});
1259 let person = Person::parse_from(&json).unwrap();
1260 assert_eq!(person.name.as_deref(), Some("John Doe"));
1261 assert_eq!(person.uri.as_deref(), Some("https://example.com"));
1262 assert!(person.email.is_none());
1263 }
1264
1265 #[test]
1266 fn test_person_parse_from_empty_json() {
1267 let json = json!({});
1268 let person = Person::parse_from(&json).unwrap();
1269 assert!(person.name.is_none());
1270 }
1271
1272 #[test]
1273 fn test_enclosure_parse_from_json() {
1274 let json = json!({
1275 "url": "https://example.com/file.mp3",
1276 "mime_type": "audio/mpeg",
1277 "size_in_bytes": 12345
1278 });
1279 let enclosure = Enclosure::parse_from(&json).unwrap();
1280 assert_eq!(enclosure.url, "https://example.com/file.mp3");
1281 assert_eq!(enclosure.enclosure_type.as_deref(), Some("audio/mpeg"));
1282 assert_eq!(enclosure.length.as_deref(), Some("12345"));
1283 }
1284
1285 #[test]
1286 fn test_enclosure_parse_from_json_missing_url() {
1287 let json = json!({"mime_type": "audio/mpeg"});
1288 assert!(Enclosure::parse_from(&json).is_none());
1289 }
1290
1291 #[test]
1292 fn test_text_type_equality() {
1293 assert_eq!(TextType::Text, TextType::Text);
1294 assert_ne!(TextType::Text, TextType::Html);
1295 }
1296
1297 #[test]
1300 fn test_url_new() {
1301 let url = Url::new("https://example.com");
1302 assert_eq!(url.as_str(), "https://example.com");
1303 }
1304
1305 #[test]
1306 fn test_url_from_string() {
1307 let url: Url = String::from("https://example.com").into();
1308 assert_eq!(url.as_str(), "https://example.com");
1309 }
1310
1311 #[test]
1312 fn test_url_from_str() {
1313 let url: Url = "https://example.com".into();
1314 assert_eq!(url.as_str(), "https://example.com");
1315 }
1316
1317 #[test]
1318 fn test_url_deref() {
1319 let url = Url::new("https://example.com");
1320 assert_eq!(url.len(), 19);
1322 assert!(url.starts_with("https://"));
1323 }
1324
1325 #[test]
1326 fn test_url_into_inner() {
1327 let url = Url::new("https://example.com");
1328 let inner = url.into_inner();
1329 assert_eq!(inner, "https://example.com");
1330 }
1331
1332 #[test]
1333 fn test_url_default() {
1334 let url = Url::default();
1335 assert_eq!(url.as_str(), "");
1336 }
1337
1338 #[test]
1339 fn test_url_clone() {
1340 let url1 = Url::new("https://example.com");
1341 let url2 = url1.clone();
1342 assert_eq!(url1, url2);
1343 }
1344
1345 #[test]
1346 fn test_mime_type_new() {
1347 let mime = MimeType::new("text/html");
1348 assert_eq!(mime.as_str(), "text/html");
1349 }
1350
1351 #[test]
1352 fn test_mime_type_from_string() {
1353 let mime: MimeType = String::from("application/json").into();
1354 assert_eq!(mime.as_str(), "application/json");
1355 }
1356
1357 #[test]
1358 fn test_mime_type_from_str() {
1359 let mime: MimeType = "text/plain".into();
1360 assert_eq!(mime.as_str(), "text/plain");
1361 }
1362
1363 #[test]
1364 fn test_mime_type_deref() {
1365 let mime = MimeType::new("text/html");
1366 assert_eq!(mime.len(), 9);
1367 assert!(mime.starts_with("text/"));
1368 }
1369
1370 #[test]
1371 fn test_mime_type_default() {
1372 let mime = MimeType::default();
1373 assert_eq!(mime.as_str(), "");
1374 }
1375
1376 #[test]
1377 fn test_mime_type_clone() {
1378 let mime1 = MimeType::new("application/xml");
1379 let mime2 = mime1.clone();
1380 assert_eq!(mime1, mime2);
1381 }
1383
1384 #[test]
1385 fn test_mime_type_constants() {
1386 assert_eq!(MimeType::TEXT_HTML, "text/html");
1387 assert_eq!(MimeType::TEXT_PLAIN, "text/plain");
1388 assert_eq!(MimeType::APPLICATION_XML, "application/xml");
1389 assert_eq!(MimeType::APPLICATION_JSON, "application/json");
1390 }
1391
1392 #[test]
1393 fn test_email_new() {
1394 let email = Email::new("user@example.com");
1395 assert_eq!(email.as_str(), "user@example.com");
1396 }
1397
1398 #[test]
1399 fn test_email_from_string() {
1400 let email: Email = String::from("user@example.com").into();
1401 assert_eq!(email.as_str(), "user@example.com");
1402 }
1403
1404 #[test]
1405 fn test_email_from_str() {
1406 let email: Email = "user@example.com".into();
1407 assert_eq!(email.as_str(), "user@example.com");
1408 }
1409
1410 #[test]
1411 fn test_email_deref() {
1412 let email = Email::new("user@example.com");
1413 assert_eq!(email.len(), 16);
1414 assert!(email.contains('@'));
1415 }
1416
1417 #[test]
1418 fn test_email_into_inner() {
1419 let email = Email::new("user@example.com");
1420 let inner = email.into_inner();
1421 assert_eq!(inner, "user@example.com");
1422 }
1423
1424 #[test]
1425 fn test_email_default() {
1426 let email = Email::default();
1427 assert_eq!(email.as_str(), "");
1428 }
1429
1430 #[test]
1431 fn test_email_clone() {
1432 let email1 = Email::new("user@example.com");
1433 let email2 = email1.clone();
1434 assert_eq!(email1, email2);
1435 }
1436}