|
2 | 2 | use serde::{Deserialize, Serialize}; |
3 | 3 | pub use svg2star::lower::{ConversionConfig, ConversionOptions}; |
4 | 4 |
|
| 5 | +/// Deserializes a field that can be either a JSON string or an array of strings. |
| 6 | +/// Arrays are joined with newlines into a single string. |
| 7 | +/// `null` or a missing field deserializes to `None`. |
| 8 | +#[cfg(feature = "serde")] |
| 9 | +fn deserialize_string_or_strings<'de, D>(deserializer: D) -> Result<Option<String>, D::Error> |
| 10 | +where |
| 11 | + D: serde::Deserializer<'de>, |
| 12 | +{ |
| 13 | + use serde::de::{self, Visitor}; |
| 14 | + |
| 15 | + struct StringOrStringsVisitor; |
| 16 | + |
| 17 | + impl<'de> Visitor<'de> for StringOrStringsVisitor { |
| 18 | + type Value = Option<String>; |
| 19 | + |
| 20 | + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { |
| 21 | + formatter.write_str("null, a string, or an array of strings") |
| 22 | + } |
| 23 | + |
| 24 | + fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> { |
| 25 | + Ok(None) |
| 26 | + } |
| 27 | + |
| 28 | + fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> { |
| 29 | + Ok(None) |
| 30 | + } |
| 31 | + |
| 32 | + fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> { |
| 33 | + Ok(Some(v.to_owned())) |
| 34 | + } |
| 35 | + |
| 36 | + fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> { |
| 37 | + Ok(Some(v)) |
| 38 | + } |
| 39 | + |
| 40 | + fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> { |
| 41 | + let mut parts: Vec<String> = Vec::new(); |
| 42 | + while let Some(s) = seq.next_element::<String>()? { |
| 43 | + parts.push(s); |
| 44 | + } |
| 45 | + if parts.is_empty() { |
| 46 | + Ok(None) |
| 47 | + } else { |
| 48 | + Ok(Some(parts.join("\n"))) |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + deserializer.deserialize_any(StringOrStringsVisitor) |
| 54 | +} |
| 55 | + |
5 | 56 | #[cfg(all(test, feature = "serde"))] |
6 | 57 | #[allow(dead_code)] |
7 | 58 | mod v0; |
@@ -36,9 +87,25 @@ pub struct Settings { |
36 | 87 | #[derive(Debug, Default, Clone, PartialEq)] |
37 | 88 | pub struct MachineConfig { |
38 | 89 | pub supported_functionality: SupportedFunctionality, |
| 90 | + #[cfg_attr( |
| 91 | + feature = "serde", |
| 92 | + serde(default, deserialize_with = "deserialize_string_or_strings") |
| 93 | + )] |
39 | 94 | pub tool_on_sequence: Option<String>, |
| 95 | + #[cfg_attr( |
| 96 | + feature = "serde", |
| 97 | + serde(default, deserialize_with = "deserialize_string_or_strings") |
| 98 | + )] |
40 | 99 | pub tool_off_sequence: Option<String>, |
| 100 | + #[cfg_attr( |
| 101 | + feature = "serde", |
| 102 | + serde(default, deserialize_with = "deserialize_string_or_strings") |
| 103 | + )] |
41 | 104 | pub begin_sequence: Option<String>, |
| 105 | + #[cfg_attr( |
| 106 | + feature = "serde", |
| 107 | + serde(default, deserialize_with = "deserialize_string_or_strings") |
| 108 | + )] |
42 | 109 | pub end_sequence: Option<String>, |
43 | 110 | } |
44 | 111 |
|
@@ -292,4 +359,85 @@ mod tests { |
292 | 359 | "#; |
293 | 360 | serde_json::from_str::<v5::Settings>(json).unwrap(); |
294 | 361 | } |
| 362 | + |
| 363 | + fn machine_with_field(field: &str, value_json: &str) -> MachineConfig { |
| 364 | + let json = format!( |
| 365 | + r#"{{"supported_functionality": {{"circular_interpolation": false}}, "{field}": {value_json}}}"# |
| 366 | + ); |
| 367 | + serde_json::from_str(&json).unwrap() |
| 368 | + } |
| 369 | + |
| 370 | + fn machine_config(begin_sequence_json: &str) -> MachineConfig { |
| 371 | + machine_with_field("begin_sequence", begin_sequence_json) |
| 372 | + } |
| 373 | + |
| 374 | + #[test] |
| 375 | + fn begin_sequence_as_string_deserializes() { |
| 376 | + let config = machine_config(r#""G28 O""#); |
| 377 | + assert_eq!(config.begin_sequence, Some("G28 O".to_owned())); |
| 378 | + } |
| 379 | + |
| 380 | + #[test] |
| 381 | + fn begin_sequence_as_array_deserializes() { |
| 382 | + let config = machine_config(r#"["G28 O", "G0 X70 Y30 Z2 F4500"]"#); |
| 383 | + assert_eq!( |
| 384 | + config.begin_sequence, |
| 385 | + Some("G28 O\nG0 X70 Y30 Z2 F4500".to_owned()) |
| 386 | + ); |
| 387 | + } |
| 388 | + |
| 389 | + #[test] |
| 390 | + fn begin_sequence_as_null_deserializes() { |
| 391 | + let config = machine_config("null"); |
| 392 | + assert_eq!(config.begin_sequence, None); |
| 393 | + } |
| 394 | + |
| 395 | + #[test] |
| 396 | + fn begin_sequence_missing_deserializes() { |
| 397 | + let json = r#"{"supported_functionality": {"circular_interpolation": false}}"#; |
| 398 | + let config: MachineConfig = serde_json::from_str(json).unwrap(); |
| 399 | + assert_eq!(config.begin_sequence, None); |
| 400 | + } |
| 401 | + |
| 402 | + #[test] |
| 403 | + fn begin_sequence_empty_array_deserializes() { |
| 404 | + let config = machine_config("[]"); |
| 405 | + assert_eq!(config.begin_sequence, None); |
| 406 | + } |
| 407 | + |
| 408 | + #[test] |
| 409 | + fn tool_on_sequence_as_string_deserializes() { |
| 410 | + let config = machine_with_field("tool_on_sequence", r#""M3 S255""#); |
| 411 | + assert_eq!(config.tool_on_sequence, Some("M3 S255".to_owned())); |
| 412 | + } |
| 413 | + |
| 414 | + #[test] |
| 415 | + fn tool_on_sequence_as_array_deserializes() { |
| 416 | + let config = machine_with_field("tool_on_sequence", r#"["M3", "S255"]"#); |
| 417 | + assert_eq!(config.tool_on_sequence, Some("M3\nS255".to_owned())); |
| 418 | + } |
| 419 | + |
| 420 | + #[test] |
| 421 | + fn tool_off_sequence_as_string_deserializes() { |
| 422 | + let config = machine_with_field("tool_off_sequence", r#""M5""#); |
| 423 | + assert_eq!(config.tool_off_sequence, Some("M5".to_owned())); |
| 424 | + } |
| 425 | + |
| 426 | + #[test] |
| 427 | + fn tool_off_sequence_as_array_deserializes() { |
| 428 | + let config = machine_with_field("tool_off_sequence", r#"["M5", "G4 P0"]"#); |
| 429 | + assert_eq!(config.tool_off_sequence, Some("M5\nG4 P0".to_owned())); |
| 430 | + } |
| 431 | + |
| 432 | + #[test] |
| 433 | + fn end_sequence_as_string_deserializes() { |
| 434 | + let config = machine_with_field("end_sequence", r#""M2""#); |
| 435 | + assert_eq!(config.end_sequence, Some("M2".to_owned())); |
| 436 | + } |
| 437 | + |
| 438 | + #[test] |
| 439 | + fn end_sequence_as_array_deserializes() { |
| 440 | + let config = machine_with_field("end_sequence", r#"["G0 X0 Y0", "M2"]"#); |
| 441 | + assert_eq!(config.end_sequence, Some("G0 X0 Y0\nM2".to_owned())); |
| 442 | + } |
295 | 443 | } |
0 commit comments