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