1use serde::{Deserialize, Serialize};
20
21use crate::text::WarningKind;
22
23#[derive(Debug, Clone, Copy, PartialEq, Serialize, schemars::JsonSchema)]
27#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
28#[non_exhaustive]
29#[serde(deny_unknown_fields)]
30pub enum Delay {
31 Seconds(f64),
34 Plugged,
36 ZeroOrPlugged,
39}
40
41impl<'de> Deserialize<'de> for Delay {
42 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43 deserializer.deserialize_map(DelayVisitor)
44 }
45}
46
47struct DelayVisitor;
51
52impl<'de> serde::de::Visitor<'de> for DelayVisitor {
53 type Value = Delay;
54
55 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
56 formatter.write_str("a delay: an object with a \"kind\", and a \"value\" for \"seconds\"")
57 }
58
59 fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Delay, A::Error> {
60 use serde::de::Error;
61 #[derive(Deserialize)]
62 #[serde(rename_all = "snake_case")]
63 enum Key {
64 Kind,
65 Value,
66 }
67 #[derive(Deserialize)]
68 #[serde(rename_all = "snake_case")]
69 enum Kind {
70 Seconds,
71 Plugged,
72 ZeroOrPlugged,
73 }
74 let (mut kind, mut value): (Option<Kind>, Option<Option<f64>>) = (None, None);
77 while let Some(key) = map.next_key::<Key>()? {
78 match key {
79 Key::Kind if kind.is_some() => return Err(A::Error::duplicate_field("kind")),
80 Key::Kind => kind = Some(map.next_value()?),
81 Key::Value if value.is_some() => return Err(A::Error::duplicate_field("value")),
82 Key::Value => value = Some(map.next_value()?),
83 }
84 }
85 match (kind, value) {
86 (None, _) => Err(A::Error::missing_field("kind")),
87 (Some(Kind::Seconds), Some(Some(seconds))) => Ok(Delay::Seconds(seconds)),
88 (Some(Kind::Seconds), Some(None)) => Err(A::Error::invalid_type(
89 serde::de::Unexpected::Unit,
90 &"the seconds, a number",
91 )),
92 (Some(Kind::Seconds), None) => Err(A::Error::missing_field("value")),
93 (Some(Kind::Plugged | Kind::ZeroOrPlugged), Some(_)) => Err(A::Error::custom(
94 "a \"value\" is given, which only a \"seconds\" delay has",
95 )),
96 (Some(Kind::Plugged), None) => Ok(Delay::Plugged),
97 (Some(Kind::ZeroOrPlugged), None) => Ok(Delay::ZeroOrPlugged),
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct DelayList {
105 pub delays: Vec<Delay>,
107 pub warnings: Vec<DelayWarning>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub struct DelayWarning {
116 pub kind: WarningKind,
118 pub message: String,
120}
121
122impl DelayList {
123 pub fn parse(raw: &str) -> Self {
126 let mut delays = Vec::new();
127 let mut warnings = Vec::new();
128 let trimmed = raw.trim();
129 if trimmed.is_empty() {
130 return Self { delays, warnings };
131 }
132 for piece in trimmed.split(['-', ',']) {
133 let piece = piece.trim();
134 match piece {
135 "" => warnings.push(DelayWarning {
136 kind: WarningKind::Dropped,
137 message: format!("empty delay in {raw:?} ignored"),
138 }),
139 "P" | "p" => delays.push(Delay::Plugged),
140 _ => match piece.parse::<f64>() {
141 Ok(marker) if marker == 100.0 || marker == 1000.0 => {
142 delays.push(Delay::Plugged)
143 }
144 Ok(0.0) => {
145 warnings.push(DelayWarning {
146 kind: WarningKind::Unusual,
147 message: format!(
148 "delay 0 in {raw:?} is ambiguous: the RASP spec means an ejection \
149 charge with no delay, but files mostly mean plugged"
150 ),
151 });
152 delays.push(Delay::ZeroOrPlugged);
153 }
154 Ok(seconds) if seconds.is_finite() && seconds > 0.0 => {
155 delays.push(Delay::Seconds(seconds));
156 }
157 _ => warnings.push(DelayWarning {
158 kind: WarningKind::Dropped,
159 message: format!("unreadable delay {piece:?} in {raw:?} ignored"),
160 }),
161 },
162 }
163 }
164 Self { delays, warnings }
165 }
166
167 pub fn has_plugged(&self) -> bool {
169 self.delays.contains(&Delay::Plugged)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn plain_lists_and_markers() {
179 let list = DelayList::parse("6-10-14");
180 assert_eq!(
181 list.delays,
182 [
183 Delay::Seconds(6.0),
184 Delay::Seconds(10.0),
185 Delay::Seconds(14.0)
186 ]
187 );
188 assert!(list.warnings.is_empty());
189 assert_eq!(DelayList::parse("P").delays, [Delay::Plugged]);
190 assert_eq!(DelayList::parse("1000.").delays, [Delay::Plugged]);
191 assert_eq!(DelayList::parse("100.0").delays, [Delay::Plugged]);
192 assert_eq!(DelayList::parse(" p ").delays, [Delay::Plugged]);
193 assert_eq!(
194 DelayList::parse("10,14,1000").delays,
195 [Delay::Seconds(10.0), Delay::Seconds(14.0), Delay::Plugged]
196 );
197 assert!(DelayList::parse("").delays.is_empty());
198 assert!(!DelayList::parse("6").has_plugged());
199 }
200
201 #[test]
202 fn serde_form_is_tagged() {
203 let delays = [Delay::Seconds(6.0), Delay::Plugged, Delay::ZeroOrPlugged];
204 let json = serde_json::to_string(&delays).unwrap();
205 assert_eq!(
206 json,
207 r#"[{"kind":"seconds","value":6.0},{"kind":"plugged"},{"kind":"zero_or_plugged"}]"#
208 );
209 assert_eq!(serde_json::from_str::<Vec<Delay>>(&json).unwrap(), delays);
210 assert_eq!(
212 serde_json::from_str::<Delay>(r#"{"value":4.5,"kind":"seconds"}"#).unwrap(),
213 Delay::Seconds(4.5)
214 );
215 }
216
217 #[test]
220 fn serde_form_is_read_as_the_schema_says() {
221 let refused = |json: &str| serde_json::from_str::<Delay>(json).unwrap_err().to_string();
222 for json in [
223 r#"{"kind":"plugged","value":null}"#,
224 r#"{"kind":"zero_or_plugged","value":null}"#,
225 r#"{"value":3.0,"kind":"plugged"}"#,
226 ] {
227 assert!(
228 refused(json).contains("which only a \"seconds\" delay has"),
229 "{json}: {}",
230 refused(json)
231 );
232 }
233 assert!(refused(r#"{"kind":"seconds","value":null}"#).contains("the seconds, a number"));
234 assert!(refused(r#"{"kind":"seconds"}"#).contains("missing field `value`"));
235 assert!(refused(r#"{"value":3.0}"#).contains("missing field `kind`"));
236 assert!(
237 refused(r#"{"kind":"plugged","kind":"plugged"}"#).contains("duplicate field `kind`")
238 );
239 assert!(
240 refused(r#"{"kind":"seconds","value":1.0,"value":2.0}"#)
241 .contains("duplicate field `value`")
242 );
243 assert!(refused(r#"{"kind":"plugged","x":0}"#).contains("unknown variant `x`"));
244 assert!(refused(r#"{"kind":"later"}"#).contains("unknown variant `later`"));
245 assert!(refused(r#"["plugged"]"#).contains("invalid type: sequence"));
246 assert!(refused(r#"["seconds", 6.0]"#).contains("invalid type: sequence"));
247 }
248
249 #[test]
250 fn malformed_pieces_are_dropped_with_warnings() {
251 let list = DelayList::parse("4-7-10,");
252 assert_eq!(list.delays.len(), 3);
253 assert_eq!(list.warnings.len(), 1);
254 let list = DelayList::parse("1-3--4");
255 assert_eq!(list.delays.len(), 3);
256 assert_eq!(list.warnings.len(), 1);
257 let list = DelayList::parse("-");
258 assert!(list.delays.is_empty());
259 assert_eq!(list.warnings.len(), 2);
260 let list = DelayList::parse("S-M-L");
261 assert!(list.delays.is_empty());
262 assert_eq!(list.warnings.len(), 3);
263 let list = DelayList::parse("0");
264 assert_eq!(list.delays, [Delay::ZeroOrPlugged]);
265 assert!(!list.has_plugged());
266 assert_eq!(list.warnings.len(), 1);
267 assert_eq!(list.warnings[0].kind, WarningKind::Unusual);
268 assert_eq!(DelayList::parse("0.0-6").delays[0], Delay::ZeroOrPlugged);
269 assert_eq!(DelayList::parse("x").warnings[0].kind, WarningKind::Dropped);
270 }
271}