Skip to content

Commit e8cd385

Browse files
Fix config file parsing (#382)
* build: ➕ Update dependencies * feat: ✨ Add hex_u16 parser * test: 🧪 Add test fn * build: ⬆️ Make hex optional * fix: 🎨 Fix clippy warnings * feat: ✨ Add serialization method * test: 🧪 Add test fn * fix: ✨ Avoid panicking by underflow * docs: 📝 Remove confusing docstring * docs: 📝 Remove confusing docstring * style: 🎨 Improve test format * style: 🎨 Improve test format
1 parent cdddf53 commit e8cd385

File tree

3 files changed

+101
-49
lines changed

3 files changed

+101
-49
lines changed

Cargo.lock

Lines changed: 6 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

espflash/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ directories-next = { version = "2.0.0", optional = true }
3737
env_logger = { version = "0.10.0", optional = true }
3838
esp-idf-part = "0.2.0"
3939
flate2 = "1.0.25"
40+
hex = { version = "0.4.3", features = ["serde"], optional = true }
4041
indicatif = { version = "0.17.2", optional = true }
4142
lazy_static = { version = "1.4.0", optional = true }
4243
log = "0.4.17"
@@ -45,7 +46,6 @@ parse_int = { version = "0.6.0", optional = true }
4546
regex = { version = "1.7.1", optional = true }
4647
rppal = { version = "0.14.1", optional = true }
4748
serde = { version = "1.0.152", features = ["derive"] }
48-
serde-hex = { version = "0.1.0", optional = true }
4949
serialport = "4.2.0"
5050
sha2 = "0.10.6"
5151
slip-codec = "0.3.3"
@@ -66,11 +66,11 @@ cli = [
6666
"dep:dialoguer",
6767
"dep:directories-next",
6868
"dep:env_logger",
69+
"dep:hex",
6970
"dep:indicatif",
7071
"dep:lazy_static",
7172
"dep:parse_int",
7273
"dep:regex",
73-
"dep:serde-hex",
7474
"dep:update-informer",
7575
]
7676
raspberry = ["dep:rppal"]

espflash/src/cli/config.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use std::{
1515
use directories_next::ProjectDirs;
1616
use miette::{IntoDiagnostic, Result, WrapErr};
1717
use serde::{Deserialize, Serialize};
18-
use serde_hex::{Compact, SerHex};
1918
use serialport::UsbPortInfo;
2019

2120
/// A configured, known serial connection
@@ -35,13 +34,38 @@ pub struct Connection {
3534
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
3635
pub struct UsbDevice {
3736
/// USB Vendor ID
38-
#[serde(with = "SerHex::<Compact>")]
37+
#[serde(serialize_with = "parse_u16_hex", deserialize_with = "parse_hex_u16")]
3938
pub vid: u16,
4039
/// USB Product ID
41-
#[serde(with = "SerHex::<Compact>")]
40+
#[serde(serialize_with = "parse_u16_hex", deserialize_with = "parse_hex_u16")]
4241
pub pid: u16,
4342
}
4443

44+
fn parse_hex_u16<'de, D>(deserializer: D) -> Result<u16, D::Error>
45+
where
46+
D: serde::Deserializer<'de>,
47+
{
48+
let s = String::deserialize(deserializer)?;
49+
let bytes = hex::decode(if s.len() % 2 == 1 {
50+
format!("0{}", s)
51+
} else {
52+
s
53+
})
54+
.map_err(serde::de::Error::custom)?;
55+
let padding = vec![0; 2_usize.saturating_sub(bytes.len())];
56+
let vec = [&padding[..], &bytes[..]].concat();
57+
let decimal = u16::from_be_bytes(vec.try_into().unwrap());
58+
Ok(decimal)
59+
}
60+
61+
fn parse_u16_hex<S>(decimal: &u16, serializer: S) -> Result<S::Ok, S::Error>
62+
where
63+
S: serde::Serializer,
64+
{
65+
let hex_string = format!("{:04x}", decimal);
66+
serializer.serialize_str(&hex_string)
67+
}
68+
4569
impl UsbDevice {
4670
/// Check if the given USB port matches this device
4771
pub fn matches(&self, port: &UsbPortInfo) -> bool {
@@ -94,3 +118,69 @@ impl Config {
94118
.wrap_err_with(|| format!("Failed to write config to {}", self.save_path.display()))
95119
}
96120
}
121+
122+
#[cfg(test)]
123+
mod tests {
124+
use super::*;
125+
use serde::Deserialize;
126+
127+
#[derive(Debug, Deserialize, Serialize)]
128+
struct TestData {
129+
#[serde(serialize_with = "parse_u16_hex", deserialize_with = "parse_hex_u16")]
130+
value: u16,
131+
}
132+
133+
#[test]
134+
fn test_parse_hex_u16() {
135+
// Test no padding
136+
let result: Result<TestData, _> = toml::from_str(r#"value = "aaaa""#);
137+
assert_eq!(result.unwrap().value, 0xaaaa);
138+
139+
let result: Result<TestData, _> = toml::from_str(r#"value = "1234""#);
140+
assert_eq!(result.unwrap().value, 0x1234);
141+
142+
// Test padding
143+
let result: Result<TestData, _> = toml::from_str(r#"value = "a""#);
144+
assert_eq!(result.unwrap().value, 0x0a);
145+
146+
let result: Result<TestData, _> = toml::from_str(r#"value = "10""#);
147+
assert_eq!(result.unwrap().value, 0x10);
148+
149+
let result: Result<TestData, _> = toml::from_str(r#"value = "100""#);
150+
assert_eq!(result.unwrap().value, 0x0100);
151+
152+
// Test uppercase
153+
let result: Result<TestData, _> = toml::from_str(r#"value = "A1B2""#);
154+
assert_eq!(result.unwrap().value, 0xA1B2);
155+
156+
// Test invalid
157+
let result: Result<TestData, _> = toml::from_str(r#"value = "gg""#);
158+
assert!(result.is_err());
159+
160+
let result: Result<TestData, _> = toml::from_str(r#"value = "10gg""#);
161+
assert!(result.is_err());
162+
}
163+
164+
#[test]
165+
fn test_parse_u16_hex() {
166+
// Valid hexadecimal input with 1 digit
167+
let result: Result<TestData, _> = toml::from_str(r#"value = "1""#);
168+
assert_eq!(result.unwrap().value, 0x1);
169+
170+
// Valid hexadecimal input with 2 digits
171+
let result: Result<TestData, _> = toml::from_str(r#"value = "ff""#);
172+
assert_eq!(result.unwrap().value, 0xff);
173+
174+
// Valid hexadecimal input with 3 digits
175+
let result: Result<TestData, _> = toml::from_str(r#"value = "b1a""#);
176+
assert_eq!(result.unwrap().value, 0xb1a);
177+
178+
// Valid hexadecimal input with 4 digits
179+
let result: Result<TestData, _> = toml::from_str(r#"value = "abc1""#);
180+
assert_eq!(result.unwrap().value, 0xabc1);
181+
182+
// Invalid input (non-hexadecimal character)
183+
let result: Result<TestData, _> = toml::from_str(r#"value = "xyz""#);
184+
assert!(result.is_err());
185+
}
186+
}

0 commit comments

Comments
 (0)