Implement config file reader

This commit is contained in:
Harald Kube 2021-02-11 22:09:29 +01:00
parent 264acc6645
commit 7d02ff7f0a
5 changed files with 234 additions and 1 deletions

View file

@ -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"

View file

@ -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
View 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
View 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);
}

View file

@ -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";