rumdl_lib/types/
line_length.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::fmt;
3
4/// A line length value that can be 0 (meaning no limit) or a positive value (≥1)
5///
6/// Many configuration values for line length need to support both:
7/// - 0: Special value meaning "no line length limit"
8/// - ≥1: Actual line length limit
9///
10/// This type enforces those constraints at deserialization time.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct LineLength(Option<usize>);
13
14impl LineLength {
15    /// Create a new LineLength, where 0 means no limit and values ≥1 are actual limits.
16    pub fn new(value: usize) -> Self {
17        if value == 0 { Self(None) } else { Self(Some(value)) }
18    }
19
20    /// Get the underlying value (0 for no limit, otherwise the actual limit).
21    pub fn get(self) -> usize {
22        self.0.unwrap_or(0)
23    }
24
25    /// Check if this represents "no limit" (value was 0).
26    pub fn is_unlimited(self) -> bool {
27        self.0.is_none()
28    }
29
30    /// Get the effective limit for comparisons.
31    /// Returns usize::MAX for unlimited, otherwise the actual limit.
32    pub fn effective_limit(self) -> usize {
33        self.0.unwrap_or(usize::MAX)
34    }
35
36    /// Convert from a default value (for use in config defaults).
37    ///
38    /// # Panics
39    /// Never panics - accepts any value including 0.
40    pub const fn from_const(value: usize) -> Self {
41        if value == 0 { Self(None) } else { Self(Some(value)) }
42    }
43}
44
45/// We don't need a separate error type since LineLength accepts all values.
46/// The validation is implicit in the conversion.
47impl<'de> Deserialize<'de> for LineLength {
48    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
49    where
50        D: Deserializer<'de>,
51    {
52        let value = usize::deserialize(deserializer)?;
53        Ok(LineLength::new(value))
54    }
55}
56
57impl Serialize for LineLength {
58    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59    where
60        S: Serializer,
61    {
62        self.get().serialize(serializer)
63    }
64}
65
66impl From<LineLength> for usize {
67    fn from(val: LineLength) -> Self {
68        val.get()
69    }
70}
71
72impl fmt::Display for LineLength {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        if self.is_unlimited() {
75            write!(f, "unlimited")
76        } else {
77            write!(f, "{}", self.get())
78        }
79    }
80}
81
82impl Default for LineLength {
83    fn default() -> Self {
84        Self::from_const(80) // Default line length is 80
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_valid_values() {
94        // Test 0 (unlimited)
95        let unlimited = LineLength::new(0);
96        assert_eq!(unlimited.get(), 0);
97        assert!(unlimited.is_unlimited());
98        assert_eq!(unlimited.effective_limit(), usize::MAX);
99        assert_eq!(usize::from(unlimited), 0);
100
101        // Test positive values
102        for value in [1, 2, 80, 100, 120, 1000] {
103            let limited = LineLength::new(value);
104            assert_eq!(limited.get(), value);
105            assert!(!limited.is_unlimited());
106            assert_eq!(limited.effective_limit(), value);
107            assert_eq!(usize::from(limited), value);
108        }
109    }
110
111    #[test]
112    fn test_from_const() {
113        const UNLIMITED: LineLength = LineLength::from_const(0);
114        assert_eq!(UNLIMITED.get(), 0);
115        assert!(UNLIMITED.is_unlimited());
116
117        const LIMITED: LineLength = LineLength::from_const(80);
118        assert_eq!(LIMITED.get(), 80);
119        assert!(!LIMITED.is_unlimited());
120    }
121
122    #[test]
123    fn test_display() {
124        let unlimited = LineLength::new(0);
125        assert_eq!(format!("{unlimited}"), "unlimited");
126
127        let limited = LineLength::new(80);
128        assert_eq!(format!("{limited}"), "80");
129    }
130
131    #[test]
132    fn test_roundtrip() {
133        #[derive(serde::Serialize, serde::Deserialize)]
134        struct TestConfig {
135            value: LineLength,
136        }
137
138        // Test unlimited (0)
139        let config = TestConfig {
140            value: LineLength::new(0),
141        };
142        let serialized = toml::to_string(&config).unwrap();
143        assert_eq!(serialized.trim(), "value = 0");
144        let deserialized: TestConfig = toml::from_str(&serialized).unwrap();
145        assert_eq!(deserialized.value.get(), 0);
146        assert!(deserialized.value.is_unlimited());
147
148        // Test limited value
149        let config = TestConfig {
150            value: LineLength::new(100),
151        };
152        let serialized = toml::to_string(&config).unwrap();
153        assert_eq!(serialized.trim(), "value = 100");
154        let deserialized: TestConfig = toml::from_str(&serialized).unwrap();
155        assert_eq!(deserialized.value.get(), 100);
156        assert!(!deserialized.value.is_unlimited());
157    }
158
159    #[test]
160    fn test_deserialization() {
161        #[derive(Debug, serde::Deserialize)]
162        struct TestConfig {
163            value: LineLength,
164        }
165
166        // Test 0 (unlimited)
167        let toml_str = "value = 0";
168        let config: TestConfig = toml::from_str(toml_str).unwrap();
169        assert_eq!(config.value.get(), 0);
170        assert!(config.value.is_unlimited());
171
172        // Test positive values
173        let toml_str = "value = 120";
174        let config: TestConfig = toml::from_str(toml_str).unwrap();
175        assert_eq!(config.value.get(), 120);
176        assert!(!config.value.is_unlimited());
177    }
178}