Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there an idiomatic way to validate the contents (keys & values) of a map? #82

Open
ciresnave opened this issue Dec 8, 2023 · 2 comments

Comments

@ciresnave
Copy link

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.

@jprochazk
Copy link
Owner

jprochazk commented Dec 10, 2023

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.

@ciresnave
Copy link
Author

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.

This was referenced Jan 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants