feedparser_rs/types/
generics.rs

1//! Generic type abstractions for DRY principle
2//!
3//! This module provides reusable generic types that eliminate code duplication
4//! across feed types and parsing logic.
5
6use std::fmt::Debug;
7
8/// A field with an optional detailed representation
9///
10/// Many feed fields follow the pattern of having a simple value (like a string)
11/// and an optional detailed struct with additional metadata. This generic wrapper
12/// captures that pattern.
13///
14/// Future use: Planned for Phase 4+ when adding advanced metadata support
15///
16/// # Type Parameters
17///
18/// * `V` - The simple value type (usually `String`)
19/// * `D` - The detailed representation type (e.g., `TextConstruct`, `Person`)
20///
21/// # Examples
22///
23/// ```
24/// use feedparser_rs::types::generics::DetailedField;
25///
26/// // Simple value only
27/// let title: DetailedField<String, ()> = DetailedField::from_value("My Title".to_string());
28/// assert_eq!(title.value(), "My Title");
29/// assert!(title.detail().is_none());
30///
31/// // With detail
32/// let title = DetailedField::with_detail("My Title".to_string(), "extra info");
33/// assert_eq!(title.detail(), Some(&"extra info"));
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[allow(dead_code)]
37pub struct DetailedField<V, D> {
38    value: V,
39    detail: Option<D>,
40}
41
42impl<V, D> DetailedField<V, D> {
43    /// Create a field with only a simple value
44    #[inline]
45    #[must_use]
46    pub const fn from_value(value: V) -> Self {
47        Self {
48            value,
49            detail: None,
50        }
51    }
52
53    /// Create a field with both value and detail
54    #[inline]
55    #[must_use]
56    pub const fn with_detail(value: V, detail: D) -> Self {
57        Self {
58            value,
59            detail: Some(detail),
60        }
61    }
62
63    /// Get reference to the simple value
64    #[inline]
65    #[must_use]
66    pub const fn value(&self) -> &V {
67        &self.value
68    }
69
70    /// Get mutable reference to the simple value
71    #[inline]
72    pub const fn value_mut(&mut self) -> &mut V {
73        &mut self.value
74    }
75
76    /// Get reference to the detail if present
77    #[inline]
78    #[must_use]
79    pub const fn detail(&self) -> Option<&D> {
80        self.detail.as_ref()
81    }
82
83    /// Get mutable reference to the detail if present
84    #[inline]
85    pub const fn detail_mut(&mut self) -> Option<&mut D> {
86        self.detail.as_mut()
87    }
88
89    /// Set the detail
90    #[inline]
91    pub fn set_detail(&mut self, detail: D) {
92        self.detail = Some(detail);
93    }
94
95    /// Take the detail, leaving None in its place
96    #[inline]
97    pub const fn take_detail(&mut self) -> Option<D> {
98        self.detail.take()
99    }
100
101    /// Convert into a tuple of (value, `Option<detail>`)
102    #[inline]
103    #[must_use]
104    pub fn into_parts(self) -> (V, Option<D>) {
105        (self.value, self.detail)
106    }
107}
108
109impl<V: Default, D> Default for DetailedField<V, D> {
110    fn default() -> Self {
111        Self {
112            value: V::default(),
113            detail: None,
114        }
115    }
116}
117
118impl<V, D> From<V> for DetailedField<V, D> {
119    fn from(value: V) -> Self {
120        Self::from_value(value)
121    }
122}
123
124impl<V, D> From<(V, D)> for DetailedField<V, D> {
125    fn from((value, detail): (V, D)) -> Self {
126        Self::with_detail(value, detail)
127    }
128}
129
130/// Extension trait for collections with size limits
131///
132/// Provides methods for safely adding items to collections while respecting
133/// configured limits, which is essential for `DoS` protection.
134///
135/// # Examples
136///
137/// ```
138/// use feedparser_rs::types::LimitedCollectionExt;
139///
140/// let mut vec = Vec::new();
141/// assert!(vec.try_push_limited("first", 2));
142/// assert!(vec.try_push_limited("second", 2));
143/// assert!(!vec.try_push_limited("third", 2)); // Exceeds limit
144/// assert_eq!(vec.len(), 2);
145/// ```
146pub trait LimitedCollectionExt<T> {
147    /// Try to push an item if the collection is below the limit
148    ///
149    /// Returns `true` if the item was added, `false` if limit was reached.
150    fn try_push_limited(&mut self, item: T, limit: usize) -> bool;
151
152    /// Check if the collection has reached its limit
153    fn is_at_limit(&self, limit: usize) -> bool;
154
155    /// Get remaining capacity before reaching the limit
156    fn remaining_capacity(&self, limit: usize) -> usize;
157}
158
159impl<T> LimitedCollectionExt<T> for Vec<T> {
160    #[inline]
161    fn try_push_limited(&mut self, item: T, limit: usize) -> bool {
162        if self.len() < limit {
163            self.push(item);
164            true
165        } else {
166            false
167        }
168    }
169
170    #[inline]
171    fn is_at_limit(&self, limit: usize) -> bool {
172        self.len() >= limit
173    }
174
175    #[inline]
176    #[allow(dead_code)]
177    fn remaining_capacity(&self, limit: usize) -> usize {
178        limit.saturating_sub(self.len())
179    }
180}
181
182/// Trait for types that can be built from XML attributes
183///
184/// Implement this trait for structs that are parsed from XML element attributes,
185/// providing a consistent interface for attribute extraction with limit validation.
186pub trait FromAttributes: Sized {
187    /// Parse from XML attributes with limit validation
188    ///
189    /// # Arguments
190    ///
191    /// * `attrs` - Iterator over XML attributes
192    /// * `max_attr_length` - Maximum allowed attribute value length
193    ///
194    /// # Returns
195    ///
196    /// * `Some(Self)` - Successfully parsed struct
197    /// * `None` - Required attributes missing or validation failed
198    fn from_attributes<'a, I>(attrs: I, max_attr_length: usize) -> Option<Self>
199    where
200        I: Iterator<Item = quick_xml::events::attributes::Attribute<'a>>;
201}
202
203/// Generic trait for parsing types from various sources using GAT
204///
205/// This trait provides a unified interface for constructing types from
206/// different data sources (JSON values, XML elements, etc.) using
207/// Generic Associated Types (GAT) for flexible lifetime handling.
208///
209/// # Examples
210///
211/// ```
212/// use feedparser_rs::types::generics::ParseFrom;
213/// use feedparser_rs::types::Person;
214/// use serde_json::json;
215///
216/// let json = json!({"name": "John Doe", "url": "https://example.com"});
217/// let person = Person::parse_from(&json);
218/// assert!(person.is_some());
219/// assert_eq!(person.unwrap().name.as_deref(), Some("John Doe"));
220/// ```
221pub trait ParseFrom<Source>: Sized {
222    /// Parse from the given source
223    ///
224    /// # Arguments
225    ///
226    /// * `source` - The source data to parse from
227    ///
228    /// # Returns
229    ///
230    /// * `Some(Self)` - Successfully parsed instance
231    /// * `None` - Failed to parse (missing required fields, wrong type, etc.)
232    fn parse_from(source: Source) -> Option<Self>;
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_detailed_field_from_value() {
241        let field: DetailedField<String, i32> = DetailedField::from_value("test".to_string());
242        assert_eq!(field.value(), "test");
243        assert!(field.detail().is_none());
244    }
245
246    #[test]
247    fn test_detailed_field_with_detail() {
248        let field = DetailedField::with_detail("test".to_string(), 42);
249        assert_eq!(field.value(), "test");
250        assert_eq!(field.detail(), Some(&42));
251    }
252
253    #[test]
254    fn test_detailed_field_from_tuple() {
255        let field: DetailedField<String, i32> = ("test".to_string(), 42).into();
256        assert_eq!(field.value(), "test");
257        assert_eq!(field.detail(), Some(&42));
258    }
259
260    #[test]
261    fn test_detailed_field_into_parts() {
262        let field = DetailedField::with_detail("test".to_string(), 42);
263        let (value, detail) = field.into_parts();
264        assert_eq!(value, "test");
265        assert_eq!(detail, Some(42));
266    }
267
268    #[test]
269    fn test_limited_collection_try_push() {
270        let mut vec: Vec<i32> = Vec::new();
271        assert!(vec.try_push_limited(1, 3));
272        assert!(vec.try_push_limited(2, 3));
273        assert!(vec.try_push_limited(3, 3));
274        assert!(!vec.try_push_limited(4, 3));
275        assert_eq!(vec.len(), 3);
276    }
277
278    #[test]
279    fn test_limited_collection_is_at_limit() {
280        let mut vec: Vec<i32> = Vec::new();
281        assert!(!vec.is_at_limit(2));
282        vec.push(1);
283        assert!(!vec.is_at_limit(2));
284        vec.push(2);
285        assert!(vec.is_at_limit(2));
286    }
287
288    #[test]
289    fn test_limited_collection_remaining_capacity() {
290        let mut vec: Vec<i32> = Vec::new();
291        assert_eq!(vec.remaining_capacity(5), 5);
292        vec.push(1);
293        vec.push(2);
294        assert_eq!(vec.remaining_capacity(5), 3);
295    }
296
297    #[test]
298    fn test_detailed_field_default() {
299        let field: DetailedField<String, i32> = DetailedField::default();
300        assert_eq!(field.value(), "");
301        assert!(field.detail().is_none());
302    }
303
304    #[test]
305    fn test_detailed_field_mutability() {
306        let mut field = DetailedField::from_value("original".to_string());
307        *field.value_mut() = "modified".to_string();
308        assert_eq!(field.value(), "modified");
309
310        field.set_detail(100);
311        assert_eq!(field.detail(), Some(&100));
312
313        let taken = field.take_detail();
314        assert_eq!(taken, Some(100));
315        assert!(field.detail().is_none());
316    }
317}