Skip to content

Commit 385f1db

Browse files
committed
Allow json arrays in settings for machine.*_sequence
1 parent 81893c0 commit 385f1db

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

g_code/src/config/mod.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,57 @@
22
use serde::{Deserialize, Serialize};
33
pub use svg2star::lower::{ConversionConfig, ConversionOptions};
44

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+
556
#[cfg(all(test, feature = "serde"))]
657
#[allow(dead_code)]
758
mod v0;
@@ -36,9 +87,25 @@ pub struct Settings {
3687
#[derive(Debug, Default, Clone, PartialEq)]
3788
pub struct MachineConfig {
3889
pub supported_functionality: SupportedFunctionality,
90+
#[cfg_attr(
91+
feature = "serde",
92+
serde(default, deserialize_with = "deserialize_string_or_strings")
93+
)]
3994
pub tool_on_sequence: Option<String>,
95+
#[cfg_attr(
96+
feature = "serde",
97+
serde(default, deserialize_with = "deserialize_string_or_strings")
98+
)]
4099
pub tool_off_sequence: Option<String>,
100+
#[cfg_attr(
101+
feature = "serde",
102+
serde(default, deserialize_with = "deserialize_string_or_strings")
103+
)]
41104
pub begin_sequence: Option<String>,
105+
#[cfg_attr(
106+
feature = "serde",
107+
serde(default, deserialize_with = "deserialize_string_or_strings")
108+
)]
42109
pub end_sequence: Option<String>,
43110
}
44111

@@ -292,4 +359,85 @@ mod tests {
292359
"#;
293360
serde_json::from_str::<v5::Settings>(json).unwrap();
294361
}
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+
}
295443
}

0 commit comments

Comments
 (0)