I want to write a function that does the following
- construct a path from some components using PathBuf
- Read & Parse the yaml file from the path, into strongly typed Configuration Structure that is a mix of HashMaps, Vectors of String, and possibly other fields.
- Start with a default instance of this Configuration structure with default values for all fields.
- Read & Parse a list YAML config files, that might each define subset of the fields possible in the struct.
- Arriving at a final Config struct instance where the values defined in last file on the list takes precedence over the default and previous yaml files.
Writing this code ends up being quite verbose and non-elegant. I suspect it is my limited knowledge on Error handling, Result<T,E> return types an
Couple of problems I ran into.
- Defining a function that takes some config file path components and returns the Structure ran into multiple issues relating Generic Types and what Error to use in Result<T,Error>. The function handles PathBuf operaitons, file read operations, and serde_yaml::from_str(content.as_str()) which all can return differet types of Errors. But using std::error::Error causes compile time errors saying size is not known.
pub fn configread<T>(basepath: &str, conf: &str) -> Result<T,Error> {}
- For some cases, I want the program to error out, with a message. The closses I got to achieving that is something like the following that still generates more than just an error and exit.
let config : T = serde_yaml::from_str(content.as_str()).unwrap_or_else(|e| {
debug(format_args!("Error parsing {:?} file {}\n", e, cfgpath.to_string_lossy()));
panic!();
});
I couldnt get serde_yaml::from_reader(open(filepath)) to work so I am now reading the content into string and calling from_str() as above.
So my question, what is the most idiomatic and cleanest way to achieve the following pseudo code in Rust
pub fn configread<T>(basepath: &str, conf: &str) -> Result<T,Error>
where
T: de::DeserializeOwned,
{
let cfgpath = Path::new(basepath)
.parent().unwrap_or_else(|| {
debug(format_args!("Error accessing path parent of {}\n", basepath));
panic!();
});
debug(format_args!("cfgpath: {}\n", cfgpath.to_string_lossy()));
let cfgpath = cfgpath.parent().unwrap_or_else(|| {
debug(format_args!("Error accessing path parent of {}\n", cfgpath.to_string_lossy()));
panic!();
});
debug(format_args!("cfgpath: {}\n", cfgpath.to_string_lossy()));
if let Err(cfgpath) = Path::new(cfgpath).join("config").join(conf)
.canonicalize() {
return to so we can skip this file if the file does not exist.
};
debug(format_args!("cfgpath: {}\n", cfgpath.to_string_lossy()));
let content = std::fs::read_to_string(cfgpath.to_owned()).unwrap_or_else(|e| {
// If the file exist, reading and parsing should be successfull OR panic/exit
debug(format_args!("Error reading {:?} file {}\n", e, cfgpath.to_string_lossy()));
panic!();
});
let config : T = serde_yaml::from_str(content.as_str()).unwrap_or_else(|e| {
debug(format_args!("Error parsing {:?} file {}\n", e, cfgpath.to_string_lossy()));
panic!();
});
config
}
#[derive(Default, Debug, serde::Deserialize, PartialEq)]
pub struct Config {
only64bit: String,
myhash: HashMap<String, String>,
myvecstrings: Vec<String>,
myvecofints: Vec<i32>,
}
const CONFIG_DEFAULTS: &str = "
wsonly : true
var2: 1
var3: something
pi: 3.14159
xmas: true
french-hens: 3
calling-birds:
- huey
- dewey
- louie
- fred
xmas-fifth-day:
calling-birds: four
french-hens: 3
golden-rings: 5
partridges:
count: 1
location: "a pear tree"
turtle-doves: two
";
pub fn readmultipleconfigs() -> Config {
// If any of the config file exists but has a parse error, the program should exit with parse error informaiton
let defconf:Config = serde_yaml::from_str(CONFIG_DEFAULTS.as_str()).unwrap();
let conf:Config = { ..configread("/a/b/c/d", "file1.ini"), ..DEFAULT};
let conf:Config = { ..configread("/a/b/c/d", "file2.ini"), ..conf};
let conf:Config = { ..configread("/a/b/c/d", "file_nonexistent.ini"), ..conf}; // ignored
let conf:Config = { ..configread("/a/b/c/d", "file3.ini"), ..conf};
config
}
What is the best way to achieve this in Rust.
I am not expecting anyone to write this full piece. Even pointers on simple idiomatic ways of achieving pieces of this puzzle that I can put to gether would be helpful