I have a struct for connection info of PostgreSQL:
struct PgConf {
host: String,
port: i16,
user: String,
passwd: String,
dbname: String
}
for now I have to init it from a HashMap<String, String>:
impl PgConf {
fn init(&mut self, args: &ArgList) -> () {
if let Some(val) = args.get("host") {
self.host = val.clone();
}
if let Some(val) = args.get("port") {
self.port = val.parse().unwrap_or(5432);
}
if let Some(val) = args.get("user") {
self.user = val.clone();
}
if let Some(val) = args.get("passwd") {
self.passwd = val.clone();
}
if let Some(val) = args.get("db") {
self.dbname = val.clone();
}
}
}
I think the above code is a bit bloated. Is there a better way to achieve this?
If you want a nicely cursed alternative, you could round-trip through a type-erased serde implementation e.g. serde_json, then it's just a matter of:
fn convert(h: HashMap<String, String>) -> Option<PgConf> {
serde_json::to_value(h).ok()
.and_then(|v| PgConf::deserialize(v).ok())
}
though that requires some support, annotating the struct to start with:
#[derive(Deserialize)]
struct PgConf {
host: String,
#[serde(
default = "default_port",
deserialize_with = "deserialize_number_from_string"
)]
port: u16,
user: String,
passwd: String,
dbname: String,
}
and then implementing the relevant hook to get the processing you need on the port:
fn default_port() -> u16 {
5432
}
/// copied from serde_aux as the playground doesn't have it
pub fn deserialize_number_from_string<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromStr + serde::Deserialize<'de>,
<T as FromStr>::Err: Display,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrInt<T> {
String(String),
Number(T),
}
match StringOrInt::<T>::deserialize(deserializer)? {
StringOrInt::String(s) => s.parse::<T>().map_err(serde::de::Error::custom),
StringOrInt::Number(i) => Ok(i),
}
}
There are cases where this can make sense, mostly situations where you're already using serde for that struct so all the "support" part is already there, and you just need to leverage it. It would not recommend it otherwise as it should be... less than ideally efficient. But it's one of those things you're glad to think of when they do apply.
If you don't want to use serde, as @Masklinn suggested (for example, you don't want to introduce a dependency), and the behavior change in the code provided by @Simson is unacceptable, a simple macro can simplify the work a bit:
struct PgConf {
host: String,
port: i16,
user: String,
passwd: String,
db: String, // Changed from `dbname` to simplify.
}
impl PgConf {
fn init(&mut self, args: &HashMap<String, String>) {
macro_rules! assign {
( $var:ident = $default:expr ) => {
if let Some(val) = args.get(stringify!($var)) {
self.$var = val.parse().unwrap_or_else(|_| $default);
}
};
( $var:ident ) => { assign!($var = unreachable!("no default provided")) };
}
assign!(host);
assign!(port = 5432);
assign!(user);
assign!(passwd);
assign!(db);
}
}
This macro is less type-safe since you can forget to add a default value to non-String entires and it will silently pass and panic at runtime. This can be improved, at the expense of making the macro longer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With