I'm new to Rust. Coming from a C/C++/Go background, one thing I'm having trouble is dealing with functions that may return different error types.
For example,
enum CustomError {
InvalidConfig
}
fn read_config<P: AsRef<Path>>(path: P) -> Result<Config, ??> {
let contents = fs::read_to_string(path)?; // may return std::io::Error
let cfg: Config = serde_json::from_str(&content)?; // may return serde_json::Error
cfg.validate()?; // may return CustomError
Ok(cfg)
}
How does one deal with multiple Error types? I could return Result<Config, Box<dyn std::error::Error>>, but I'm wondering if there's an idiomatic way to solve this.
One straighforward way is to define your own enum Error which provides From implementations for all kinds of errors you expect to encounter. Crates such as thiserror can help reduce the corresponding boilerplate.
The way I have approached this is to create wrappers for functions which return an incompatible Result type and repackages them into a compatible type. For example:
#[derive(PartialEq,Debug,...)] // make it easier to print in an eprintln!() macro, to check for in tests, etc.
enum CustomError {
InvalidConfig,
PathConversionError(std::io::Error),
JsonValidationError(...); // too lazy to look up member type here, but you get the idea I think.
JsonConversionError(...);
}
fn convert_path_to_string<P: AsRef<Path>>(path: P) -> Result<String, CustomError> {
match fs::read_to_string(path) {
Ok(s) => Ok(s),
Err(e) => Err(CustomError::PathConversionError(e)),
}
}
fn string_to_config(content: &str) -> Result<Config, CustomError> {
match serde_json::from_str(content) {
Ok(json) => match json.validate() {
Ok(cfg) => Ok(cfg),
Err(e) => Err(CustomError::JsonValidationError(e)),
},
Err(e) => Err(CustomError::JsonConversionError(e)),
}
}
pub fn read_config<P: AsRef<Path>>(path: P) -> Result<Config, CustomError> {
let contents = path_to_string(path)?;
let cfg: Config = string_to_json(contents)?;
Ok(cfg)
}