rumdl_lib/types/
br_spaces.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct BrSpaces(usize);
13
14impl BrSpaces {
15 pub const MIN: usize = 2;
17
18 pub fn new(value: usize) -> Result<Self, BrSpacesError> {
23 if value >= Self::MIN {
24 Ok(Self(value))
25 } else {
26 Err(BrSpacesError(value))
27 }
28 }
29
30 pub fn get(self) -> usize {
32 self.0
33 }
34
35 pub const fn from_const(value: usize) -> Self {
40 assert!(value >= Self::MIN, "BrSpaces must be at least 2 (CommonMark standard)");
41 Self(value)
42 }
43}
44
45impl Default for BrSpaces {
46 fn default() -> Self {
47 Self(2) }
49}
50
51#[derive(Debug, Clone, Copy)]
53pub struct BrSpacesError(usize);
54
55impl fmt::Display for BrSpacesError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 write!(
58 f,
59 "Line break spaces must be at least 2, got {}. \
60 Markdown requires at least 2 trailing spaces to create a line break \
61 (CommonMark specification). Values of 0 or 1 do not create line breaks.",
62 self.0
63 )
64 }
65}
66
67impl std::error::Error for BrSpacesError {}
68
69impl<'de> Deserialize<'de> for BrSpaces {
70 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
71 where
72 D: Deserializer<'de>,
73 {
74 let value = usize::deserialize(deserializer)?;
75 BrSpaces::new(value).map_err(serde::de::Error::custom)
76 }
77}
78
79impl Serialize for BrSpaces {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: Serializer,
83 {
84 self.0.serialize(serializer)
85 }
86}
87
88impl From<BrSpaces> for usize {
89 fn from(val: BrSpaces) -> Self {
90 val.0
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_valid_values() {
100 for value in [2, 3, 4, 10, 100] {
101 let br_spaces = BrSpaces::new(value).unwrap();
102 assert_eq!(br_spaces.get(), value);
103 assert_eq!(usize::from(br_spaces), value);
104 }
105 }
106
107 #[test]
108 fn test_invalid_values() {
109 for value in [0, 1] {
110 assert!(BrSpaces::new(value).is_err());
111 }
112 }
113
114 #[test]
115 fn test_default() {
116 assert_eq!(BrSpaces::default().get(), 2);
117 }
118
119 #[test]
120 fn test_from_const() {
121 const DEFAULT: BrSpaces = BrSpaces::from_const(2);
122 assert_eq!(DEFAULT.get(), 2);
123 }
124
125 #[test]
126 fn test_min_constant() {
127 assert_eq!(BrSpaces::MIN, 2);
128 }
129
130 #[test]
131 fn test_roundtrip() {
132 #[derive(serde::Serialize, serde::Deserialize)]
133 struct TestConfig {
134 spaces: BrSpaces,
135 }
136
137 let config = TestConfig {
138 spaces: BrSpaces::new(3).unwrap(),
139 };
140 let serialized = toml::to_string(&config).unwrap();
141 let deserialized: TestConfig = toml::from_str(&serialized).unwrap();
142 assert_eq!(deserialized.spaces.get(), 3);
143 }
144
145 #[test]
146 fn test_validation_error() {
147 #[derive(Debug, serde::Deserialize)]
148 struct TestConfig {
149 spaces: BrSpaces,
150 }
151
152 let toml_str = "spaces = 1";
154 let result: Result<TestConfig, _> = toml::from_str(toml_str);
155 assert!(result.is_err());
156 let err = result.unwrap_err().to_string();
157 assert!(err.contains("must be at least 2") || err.contains("got 1"));
158
159 let toml_str = "spaces = 0";
161 let result: Result<TestConfig, _> = toml::from_str(toml_str);
162 assert!(result.is_err());
163
164 let valid_toml = "spaces = 2";
166 let config: TestConfig = toml::from_str(valid_toml).unwrap();
167 assert_eq!(config.spaces.get(), 2);
168 }
169}