Skip to content

Commit a4ea101

Browse files
committed
config: add duplicate name validation
If Pins A and B contain the same name for a pin, we now generate an error rather than waiting for a problem down the line to arise. Signed-off-by: Paul Osborne <[email protected]>
1 parent f737fdc commit a4ea101

File tree

1 file changed

+57
-7
lines changed

1 file changed

+57
-7
lines changed

src/config.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use glob::glob;
44
use rustc_serialize::{Decodable, Decoder};
5-
use std::collections::BTreeSet;
5+
use std::collections::{HashMap, BTreeSet};
66
use std::fmt;
77
use std::fs::{self, File};
88
use std::io;
@@ -43,6 +43,7 @@ pub enum Error {
4343
ParserErrors(Vec<toml::ParserError>),
4444
DecodingError(toml::DecodeError),
4545
NoConfigFound,
46+
DuplicateNames(String),
4647
}
4748

4849
impl fmt::Display for Error {
@@ -57,6 +58,7 @@ impl fmt::Display for Error {
5758
}
5859
Error::DecodingError(ref e) => e.fmt(f),
5960
Error::NoConfigFound => write!(f, "No Config Found"),
61+
Error::DuplicateNames(ref e) => e.fmt(f),
6062
}
6163
}
6264
}
@@ -118,6 +120,28 @@ impl Decodable for PinConfig {
118120
}
119121

120122
impl GpioConfig {
123+
/// Validate invariants on the config that cannot easily be done earlier
124+
///
125+
/// Currently, this just checks that there are no duplicated names between
126+
/// different pins in the config
127+
fn validate(&self) -> Result<(), Error> {
128+
let mut all_names: HashMap<&str, &PinConfig> = HashMap::new();
129+
for pin in &self.pins {
130+
for name in &pin.names {
131+
if let Some(other_pin) = all_names.get(&name[..]) {
132+
return Err(Error::DuplicateNames(format!("Pins {} and {} share duplicate \
133+
name '{}'",
134+
pin.num,
135+
other_pin.num,
136+
name)));
137+
}
138+
all_names.insert(&name[..], pin);
139+
}
140+
}
141+
142+
Ok(())
143+
}
144+
121145
/// Load a GPIO Config from the system
122146
///
123147
/// This function will load the GPIO configuration from standard system
@@ -158,7 +182,7 @@ impl GpioConfig {
158182
} else {
159183
let mut cfg = config_instances.remove(0);
160184
for higher_priority_cfg in config_instances {
161-
cfg.update(higher_priority_cfg);
185+
try!(cfg.update(higher_priority_cfg));
162186
}
163187
Ok(cfg)
164188
}
@@ -168,8 +192,11 @@ impl GpioConfig {
168192
pub fn from_str(config: &str) -> Result<GpioConfig, Error> {
169193
let mut parser = toml::Parser::new(config);
170194
let root = try!(parser.parse().ok_or(parser.errors));
171-
match Decodable::decode(&mut toml::Decoder::new(toml::Value::Table(root))) {
172-
Ok(cfg) => Ok(cfg),
195+
match GpioConfig::decode(&mut toml::Decoder::new(toml::Value::Table(root))) {
196+
Ok(cfg) => {
197+
try!(cfg.validate().or_else(|e| Err(Error::from(e))));
198+
Ok(cfg)
199+
},
173200
Err(e) => Err(Error::from(e)),
174201
}
175202
}
@@ -179,7 +206,9 @@ impl GpioConfig {
179206
let mut contents = String::new();
180207
let mut f = try!(File::open(path));
181208
try!(f.read_to_string(&mut contents));
182-
GpioConfig::from_str(&contents[..])
209+
let config = try!(GpioConfig::from_str(&contents[..]));
210+
try!(config.validate());
211+
Ok(config)
183212
}
184213

185214
/// Get the pin with the provided name if present in this configuration
@@ -203,7 +232,7 @@ impl GpioConfig {
203232
/// Merge other into self (takes ownership of other)
204233
///
205234
/// If in conflict, the other GPIO config takes priority.
206-
pub fn update(&mut self, other: GpioConfig) {
235+
pub fn update(&mut self, other: GpioConfig) -> Result<(), Error> {
207236
if let Some(symlink_root) = other.symlink_root {
208237
self.symlink_root = Some(symlink_root);
209238
}
@@ -225,6 +254,9 @@ impl GpioConfig {
225254
self.pins.push(other_pin);
226255
}
227256
}
257+
258+
// validate the resulting structure
259+
self.validate()
228260
}
229261
}
230262

@@ -262,6 +294,16 @@ symlink_root = "/tmp/gpio"
262294
const MISSING_PINNUM_CFG: &'static str = r#"
263295
[[pins]]
264296
export = true
297+
"#;
298+
299+
const DUPLICATED_NAMES_CFG: &'static str = r#"
300+
[[pins]]
301+
num = 25
302+
names = ["foo", "bar"]
303+
304+
[[pins]]
305+
num = 26
306+
names = ["baz", "foo"] # foo is repeated!
265307
"#;
266308

267309
const PARTIALLY_OVERLAPS_BASIC_CFG: &'static str = r#"
@@ -363,13 +405,21 @@ names = ["wildcard"]
363405
}
364406
}
365407

408+
#[test]
409+
fn test_error_on_duplicated_names() {
410+
match GpioConfig::from_str(DUPLICATED_NAMES_CFG) {
411+
Err(Error::DuplicateNames(_)) => (),
412+
r => panic!("Expected DuplicateNames Error, got {:?}", r),
413+
}
414+
}
415+
366416
#[test]
367417
fn test_merge_configs() {
368418
let mut config = GpioConfig::from_str(BASIC_CFG).unwrap();
369419
let cfg2 = GpioConfig::from_str(PARTIALLY_OVERLAPS_BASIC_CFG).unwrap();
370420

371421
// perform the merge
372-
config.update(cfg2);
422+
config.update(cfg2).unwrap();
373423

374424
assert_eq!(config.get_symlink_root(), "/foo/bar/baz");
375425

0 commit comments

Comments
 (0)