Implement config file reader
This commit is contained in:
parent
264acc6645
commit
7d02ff7f0a
5 changed files with 234 additions and 1 deletions
|
|
@ -8,3 +8,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
toml = "0.5"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
function_name = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -3,4 +3,7 @@
|
|||
# Start test if one of the files was modified
|
||||
cargo install cargo-watch
|
||||
|
||||
RUST_BACKTRACE=full cargo watch -x "test -- --nocapture"
|
||||
# Run the tests on change
|
||||
# Set RUST_BACKTRACE to 1 to see a short backtrace in case of an error
|
||||
# Set RUST_BACKTRACE to "full" to see a full backtrace in case of an error
|
||||
RUST_BACKTRACE=0 cargo watch -x "test -- --nocapture"
|
||||
|
|
|
|||
52
src/cfg_reader.rs
Normal file
52
src/cfg_reader.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use serde_derive::Deserialize;
|
||||
use std::fs::File;
|
||||
use std::{error::Error, io::Read};
|
||||
|
||||
extern crate toml;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
channels: Vec<S0Channel>,
|
||||
// debounce_millis : Option<u8>,
|
||||
// invert : Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct S0Channel {
|
||||
id: u8,
|
||||
gpio: u8,
|
||||
// debounce_millis: Option<u8>,
|
||||
}
|
||||
|
||||
// use std::any::type_name;
|
||||
// fn type_of<T>(_: T) -> &'static str {
|
||||
// type_name::<T>()
|
||||
// }
|
||||
|
||||
const MAX_CONFIG_FILE_SIZE: u64 = 1_000_000u64;
|
||||
|
||||
pub fn parse_file(config_file_name: &str) -> Result<Config, Box<dyn Error>> {
|
||||
let mut cfg_file = File::open(String::from(config_file_name))?;
|
||||
let cfg_file_meta = &cfg_file.metadata()?;
|
||||
let cfg_file_len = &cfg_file_meta.len();
|
||||
if cfg_file_len > &MAX_CONFIG_FILE_SIZE {
|
||||
return Err(From::from(format!(
|
||||
"Size of config file is {} exceeds the limit of {}",
|
||||
&cfg_file_len, MAX_CONFIG_FILE_SIZE
|
||||
)));
|
||||
}
|
||||
let mut cfg_file_content = String::new();
|
||||
cfg_file.read_to_string(&mut cfg_file_content).unwrap();
|
||||
parse_string(&cfg_file_content)
|
||||
}
|
||||
|
||||
fn parse_string(cfg_str: &String) -> Result<Config, Box<dyn Error>> {
|
||||
match toml::from_str(cfg_str) {
|
||||
Ok(cfg) => Ok(cfg),
|
||||
Err(err) => Err(From::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./cfg_reader_test.rs"]
|
||||
mod cfg_reader_test;
|
||||
172
src/cfg_reader_test.rs
Normal file
172
src/cfg_reader_test.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use super::*;
|
||||
use function_name::named;
|
||||
use std::{fs, io::Write};
|
||||
|
||||
fn create_cfg_file(fn_name: &str, content: &str) -> String {
|
||||
let cfg_file_name = format!("/tmp/{}.cfg", fn_name);
|
||||
let mut cfg_file = File::create(&cfg_file_name).unwrap();
|
||||
cfg_file.write(content.as_bytes()).unwrap();
|
||||
cfg_file_name
|
||||
}
|
||||
|
||||
fn remove_cfg_file(cfg_file_name: &String) {
|
||||
fs::remove_file(cfg_file_name).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_file_fails_if_file_does_not_exist() {
|
||||
assert!(parse_file("/this/path/does/not/exist").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[named]
|
||||
fn parse_file_fails_if_file_is_greater_than_1mb() {
|
||||
let cfg_file_name = create_cfg_file(
|
||||
function_name!(),
|
||||
"a".repeat(MAX_CONFIG_FILE_SIZE as usize + 1).as_str(),
|
||||
);
|
||||
assert!(parse_file(&cfg_file_name).is_err());
|
||||
remove_cfg_file(&cfg_file_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[named]
|
||||
fn parse_file_succeeds() {
|
||||
let cfg_file_name = create_cfg_file(
|
||||
function_name!(),
|
||||
r#"
|
||||
[[channels]]
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_file(&cfg_file_name).is_ok());
|
||||
remove_cfg_file(&cfg_file_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_on_empty_string() {
|
||||
let cfg_str = String::from(r#""#);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_without_section_channels() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_on_unknown_section() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[unknown]]
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_on_section_format_error() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[channels]
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
id =
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
id = 1
|
||||
gpio = 4
|
||||
2
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_on_missing_field_id() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_fails_on_missing_field_gpio() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
id = 1
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_ignores_unknown_attributes() {
|
||||
let cfg_str = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
unknown = 1
|
||||
id = 1
|
||||
gpio = 4
|
||||
"#,
|
||||
);
|
||||
assert!(parse_string(&cfg_str).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string_succeeds() {
|
||||
let cfg = String::from(
|
||||
r#"
|
||||
[[channels]]
|
||||
id = 1
|
||||
gpio = 4
|
||||
|
||||
[[channels]]
|
||||
id = 2
|
||||
gpio = 17
|
||||
"#,
|
||||
);
|
||||
let config = parse_string(&cfg);
|
||||
assert!(&config.is_ok());
|
||||
let config = config.unwrap();
|
||||
assert_eq!(config.channels.len(), 2);
|
||||
assert_eq!(config.channels[0].id, 1);
|
||||
assert_eq!(config.channels[0].gpio, 4);
|
||||
assert_eq!(config.channels[1].id, 2);
|
||||
assert_eq!(config.channels[1].gpio, 17);
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
mod cfg_reader;
|
||||
|
||||
const APP_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
const DEFAULT_CONFIG_FILE_NAME: &str = "/etc/s0_logger.cfg";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue