globset 0.4.18

Cross platform single glob and glob set matching. Glob set matching is the process of matching one or more glob patterns against a single candidate path simultaneously, and returning all of the globs that matched.
Documentation
use serde::{
    de::{Error, SeqAccess, Visitor},
    {Deserialize, Deserializer, Serialize, Serializer},
};

use crate::{Glob, GlobSet, GlobSetBuilder};

impl Serialize for Glob {
    fn serialize<S: Serializer>(
        &self,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(self.glob())
    }
}

struct GlobVisitor;

impl<'de> Visitor<'de> for GlobVisitor {
    type Value = Glob;

    fn expecting(
        &self,
        formatter: &mut std::fmt::Formatter,
    ) -> std::fmt::Result {
        formatter.write_str("a glob pattern")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Glob::new(v).map_err(serde::de::Error::custom)
    }
}

impl<'de> Deserialize<'de> for Glob {
    fn deserialize<D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Self, D::Error> {
        deserializer.deserialize_str(GlobVisitor)
    }
}

struct GlobSetVisitor;

impl<'de> Visitor<'de> for GlobSetVisitor {
    type Value = GlobSet;

    fn expecting(
        &self,
        formatter: &mut std::fmt::Formatter,
    ) -> std::fmt::Result {
        formatter.write_str("an array of glob patterns")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: SeqAccess<'de>,
    {
        let mut builder = GlobSetBuilder::new();
        while let Some(glob) = seq.next_element()? {
            builder.add(glob);
        }
        builder.build().map_err(serde::de::Error::custom)
    }
}

impl<'de> Deserialize<'de> for GlobSet {
    fn deserialize<D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Self, D::Error> {
        deserializer.deserialize_seq(GlobSetVisitor)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::{Glob, GlobSet};

    #[test]
    fn glob_deserialize_borrowed() {
        let string = r#"{"markdown": "*.md"}"#;

        let map: HashMap<String, Glob> =
            serde_json::from_str(&string).unwrap();
        assert_eq!(map["markdown"], Glob::new("*.md").unwrap());
    }

    #[test]
    fn glob_deserialize_owned() {
        let string = r#"{"markdown": "*.md"}"#;

        let v: serde_json::Value = serde_json::from_str(&string).unwrap();
        let map: HashMap<String, Glob> = serde_json::from_value(v).unwrap();
        assert_eq!(map["markdown"], Glob::new("*.md").unwrap());
    }

    #[test]
    fn glob_deserialize_error() {
        let string = r#"{"error": "["}"#;

        let map = serde_json::from_str::<HashMap<String, Glob>>(&string);

        assert!(map.is_err());
    }

    #[test]
    fn glob_json_works() {
        let test_glob = Glob::new("src/**/*.rs").unwrap();

        let ser = serde_json::to_string(&test_glob).unwrap();
        assert_eq!(ser, "\"src/**/*.rs\"");

        let de: Glob = serde_json::from_str(&ser).unwrap();
        assert_eq!(test_glob, de);
    }

    #[test]
    fn glob_set_deserialize() {
        let j = r#" ["src/**/*.rs", "README.md"] "#;
        let set: GlobSet = serde_json::from_str(j).unwrap();
        assert!(set.is_match("src/lib.rs"));
        assert!(!set.is_match("Cargo.lock"));
    }
}