Skip to main content

feedparser_rs/types/
thread.rs

1use super::common::{MimeType, SmallString, Url};
2
3/// Atom Threading Extensions (RFC 4685) in-reply-to element
4///
5/// Represents a reference to another entry that this entry is a reply to.
6/// Namespace URI: `http://purl.org/syndication/thread/1.0`
7///
8/// # Examples
9///
10/// ```
11/// use feedparser_rs::InReplyTo;
12///
13/// let reply = InReplyTo {
14///     ref_: Some("tag:example.com,2024:post/1".into()),
15///     href: Some("https://example.com/post/1".into()),
16///     type_: Some("text/html".into()),
17///     source: Some("https://example.com/feed.xml".into()),
18/// };
19///
20/// assert_eq!(reply.ref_.as_deref(), Some("tag:example.com,2024:post/1"));
21/// ```
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct InReplyTo {
24    /// IRI of the entry being replied to (ref attribute)
25    ///
26    /// Required by RFC 4685, but we tolerate missing values (bozo pattern).
27    /// Empty-string values from malformed feeds are normalized to None.
28    /// Field named `ref_` to avoid Rust keyword clash.
29    pub ref_: Option<SmallString>,
30
31    /// URL where the referenced entry can be found (href attribute)
32    ///
33    /// # Security
34    ///
35    /// This URL comes from untrusted feed input and has NOT been validated.
36    /// Applications MUST validate URLs before fetching to prevent SSRF.
37    pub href: Option<Url>,
38
39    /// MIME type of the linked resource (type attribute)
40    ///
41    /// Field named `type_` to avoid Rust keyword clash.
42    pub type_: Option<MimeType>,
43
44    /// IRI of the feed containing the referenced entry (source attribute)
45    ///
46    /// Not to be confused with [`Entry::source`](super::entry::Entry::source)
47    /// which is the RSS/Atom source feed reference. This field is the
48    /// RFC 4685 `source` attribute: an IRI identifying the feed that
49    /// contains the entry being replied to.
50    ///
51    /// # Security
52    ///
53    /// This URL comes from untrusted feed input and has NOT been validated.
54    /// Applications MUST validate URLs before fetching to prevent SSRF.
55    pub source: Option<Url>,
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_in_reply_to_default() {
64        let reply = InReplyTo::default();
65        assert!(reply.ref_.is_none());
66        assert!(reply.href.is_none());
67        assert!(reply.type_.is_none());
68        assert!(reply.source.is_none());
69    }
70
71    #[test]
72    fn test_in_reply_to_partial_construction() {
73        let reply = InReplyTo {
74            ref_: Some("tag:example.com,2024:post/1".into()),
75            ..Default::default()
76        };
77        assert_eq!(reply.ref_.as_deref(), Some("tag:example.com,2024:post/1"));
78        assert!(reply.href.is_none());
79        assert!(reply.type_.is_none());
80        assert!(reply.source.is_none());
81    }
82
83    #[test]
84    fn test_in_reply_to_equality() {
85        let a = InReplyTo {
86            ref_: Some("tag:example.com,2024:1".into()),
87            href: Some("https://example.com/1".into()),
88            type_: Some("text/html".into()),
89            source: Some("https://example.com/feed.xml".into()),
90        };
91        let b = a.clone();
92        assert_eq!(a, b);
93    }
94
95    #[test]
96    #[allow(clippy::redundant_clone)]
97    fn test_in_reply_to_clone() {
98        let original = InReplyTo {
99            ref_: Some("tag:example.com,2024:post/1".into()),
100            href: Some("https://example.com/post/1".into()),
101            type_: Some("text/html".into()),
102            source: Some("https://example.com/feed.xml".into()),
103        };
104        let cloned = original.clone();
105        assert_eq!(cloned.ref_.as_deref(), Some("tag:example.com,2024:post/1"));
106        assert_eq!(cloned.href.as_deref(), Some("https://example.com/post/1"));
107        assert_eq!(cloned.type_.as_deref(), Some("text/html"));
108        assert_eq!(
109            cloned.source.as_deref(),
110            Some("https://example.com/feed.xml")
111        );
112    }
113}