Is there an idiomatic way to validate the contents (keys & values) of a map?
Opened this issue · 2 comments
I appreciate garde. It has made validating structures not just easier, but a natural part of my development. However, I've run into a situation where I need to validate a HashMap (actually a DashMap) that is a member of a struct. If I try to validate it directly, it fails because there is no Display implementation for DashMap. I suspect I may be able to write a custom validator function...maybe? Or..I could wrap the DashMap in a newtype and implement Validate on that type manually...maybe? But, is there an existing best way to validate the keys and values of a map? I would love to be able to use the validation rules built into garde on the keys and values in the map and if I write custom validation code, I lose all of that.
Thanks in advance.
Validating keys is not possible right now without custom validators, and the only way you can validate the values in a HashMap
is via #[garde(dive)]
, which is definitely easier now with #[garde(transparent)]
, but still not great if all you have is a String
value or something along those lines.
One possible improvement here would be to add a modifier similar to inner
, but have it apply the validation rules to the key of a keyed collection. Rough idea:
struct Foo {
#[garde(
key(length(min = 3)), // applied to `K`
inner(length(min = 10)), // applied to `V`
)]
bar: HashMap<K, V>,
}
The error message would probably look like:
Foo {
bar: HashMap {
"a": "test"
}
}
value.bar: invalid key "a", length is lower than 3
value.bar.a: length is lower than 10
As for validating a DashMap
, right now you need to use a newtype to be able to implement the various validation traits which enable Garde's functionality. I'm not totally sure if there's a way out here other than adding a bunch of feature-gated impls to either library.
One thing I've seen done by serde is to implement a way to "adapt" one type to be validated as if it was another. Once again, here's a very rough idea:
mod dashmap_adapter {
// the `adapt` modifier will cause the proc macro to bypass the usual trait-based validation
// logic, and instead rely on functions present in this module:
// re-export all the garde rules
// because of the glob import, anything defined in this module will take precedence
pub use garde::rules::*;
// for example, here's how you'd adapt the `inner` modifier to work on `DashMap`:
pub mod inner {
pub fn apply<K, V, F>(map: &DashMap<K, V>, f: F)
where
K: PathComponentKind,
F: FnMut(&V, &K),
{
for (key, value) in map.iter() {
f(value, key)
}
}
}
}
struct Foo {
#[garde(
inner(length(min = 10)),
adapt(dashmap_adapter), // use the `dashmap_adapter` module
)]
bar: DashMap<K, V>,
}
Now instead of calling garde::rules::inner::apply
on the bar
field, it will call dashmap_adapter::inner::apply
. The module is reusable across your entire application, doesn't require using newtypes, and you still get most of the benefit of garde
.
Thank you for your in-depth response. I'll dig into it and hopefully have something I can share back to garde to help others when I'm done.