In Go I could have deserialized to a map[string]interface{}, but I'm not sure how to proceed in rust.
The purpose of the configuration file is to enable + configure heterogeneous modules in a program.
I know I can deserialize in a giant struct like the following
struct Modules {
ports: Option<Vec<u16>>,
#[serde(rename = "domain/whois")]
domain_whois: Option<()>,
#[serde(rename = "http/drupal/CVE_2018_7600")]
http_drupal_cve_2018_7600: Option<()>,
// ...
}
//...
if let Some(ports) = modules.ports {
findings.push(modules.ports.run())
}
if let Some(()) = modules.domain_whois {
findings.push(modules.domain_whois.run())
}
if let Some(()) = modules.http_drupal_cve_2018_7600 {
findings.push(modules.http_drupal_cve_2018_7600.run())
}
but I wanted to know if there is a better way to proceed where I don't have to create this giant struct with optional but instead a collection that I can iterate.
// let findings = modules.map(|module| module.run())
Nevertheless, I actually think having a concrete struct is preferably to having a loosely typed collection like you are requesting.
Also, you can add a #[serde(default)] to the ports field, so that it always instantiates to a vector (empty if undefined), so that you don't have to deal with Option.
The problem with the concrete struct is that it creates a lot of boilerplate code which can be error prone
think of
// if ports module is enabled
if let Some(ports) = modules.ports {
findings.push(modules.ports.run())
}
for 1000+ modules and it's growing.
Thank you for the default thing I wasn't aware. The thing is that here we may want to disable the module by not including it in the config, so with. my very limited rust knowledge, I think Option is better in this specific case.
It is unlikely that you will be able to remove all of the duck typing boilerplate by using duck typing. Take for example Value::get; it returns Option! Another example is type checking with methods like is_array, is_object, and is_string.
A concrete type will help remove some of the boilerplate but only if you are certain that the given struct fields are required. Then there is no need to handle None, or unwrap.
It is also possible to use a hybrid approach where the required fields are specified in a concrete type, and everything else is accessed through duck typing like the Value enum. I use this in one of my projects, but with a much much smaller data set and simpler JSON schema.
cargo --message-format=json returns a list of JSON objects separated by \n characters. This code splits the list and filters it into two iterators that are of interest: One that contains warning messages, and another that contains compiler artifacts.