json_predicate/predicate/first_order/
matches.rs

1use derive_builder::Builder;
2use regex::RegexBuilder;
3use serde_json::Value;
4use std::marker::PhantomData;
5
6use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
7use serde::{ser::SerializeStruct, Serialize};
8
9use crate::json_path::JSONPath;
10use crate::predicate::context::PredicateContext;
11use crate::predicate::error::PredicateError;
12use crate::predicate::PredicateImpl;
13use crate::regex::Regex;
14
15use super::FirstOrder;
16
17/// The "matches" predicate evaluates as true if the referenced element is
18/// defined and has a value whose completely string representation matches the
19/// regular expression provided by the predicate object's "value" member.
20#[derive(Debug, Clone, PartialEq, Eq, Builder)]
21#[builder(pattern = "owned", setter(into, strip_option))]
22pub struct Matches {
23    /// Must be a [JSON Pointer](https://tools.ietf.org/html/rfc6901)
24    /// If the "path" member is not specified within the predicate object, it's value is assumed to be an empty string.
25    pub path: Option<JSONPath>,
26    #[builder(default)]
27    pub ignore_case: bool,
28    /// Must be a Regex
29    pub value: Regex,
30}
31
32impl Serialize for Matches {
33    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34    where
35        S: serde::Serializer,
36    {
37        let mut state = serializer.serialize_struct("Matches", 3)?;
38        if self.ignore_case {
39            state.serialize_field("op", "matches-")?;
40        } else {
41            state.serialize_field("op", "matches")?;
42        }
43        state.serialize_field("path", &self.path)?;
44        state.serialize_field("value", &self.value)?;
45        state.end()
46    }
47}
48
49impl<'de> Deserialize<'de> for Matches {
50    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51    where
52        D: Deserializer<'de>,
53    {
54        #[allow(non_camel_case_types)]
55        enum Field {
56            op,
57            path,
58            value,
59            __ignore,
60        }
61        struct FieldVisitor;
62
63        impl<'de> Visitor<'de> for FieldVisitor {
64            type Value = Field;
65            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
66                formatter.write_str("field identifier")
67            }
68
69            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
70            where
71                E: de::Error,
72            {
73                match value {
74                    "op" => Ok(Field::op),
75                    "path" => Ok(Field::path),
76                    "value" => Ok(Field::value),
77                    _ => Ok(Field::__ignore),
78                }
79            }
80        }
81
82        impl<'de> Deserialize<'de> for Field {
83            #[inline]
84            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85            where
86                D: Deserializer<'de>,
87            {
88                Deserializer::deserialize_identifier(deserializer, FieldVisitor)
89            }
90        }
91
92        struct VisitorMatches<'de> {
93            marker: PhantomData<Matches>,
94            lifetime: PhantomData<&'de ()>,
95        }
96
97        impl<'de> Visitor<'de> for VisitorMatches<'de> {
98            type Value = Matches;
99            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
100                formatter.write_str("Matches")
101            }
102
103            #[inline]
104            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
105            where
106                A: MapAccess<'de>,
107            {
108                let mut path: Option<Option<JSONPath>> = None;
109                let mut value: Option<Regex> = None;
110                let mut op: Option<String> = None;
111
112                while let Some(key) = MapAccess::next_key::<Field>(&mut map)? {
113                    match key {
114                        Field::op => {
115                            if op.is_some() {
116                                return Err(serde::de::Error::duplicate_field("op"));
117                            }
118                            op = Some(MapAccess::next_value::<String>(&mut map)?);
119                        }
120                        Field::path => {
121                            if path.is_some() {
122                                return Err(serde::de::Error::duplicate_field("path"));
123                            }
124                            path = Some(MapAccess::next_value::<Option<JSONPath>>(&mut map)?);
125                        }
126                        Field::value => {
127                            if value.is_some() {
128                                return Err(serde::de::Error::duplicate_field("value"));
129                            }
130                            value = Some(MapAccess::next_value::<Regex>(&mut map)?);
131                        }
132                        Field::__ignore => {}
133                    }
134                }
135
136                let path = path.ok_or(serde::de::Error::missing_field("path"))?;
137                let value = value.ok_or(serde::de::Error::missing_field("value"))?;
138                let op = op.ok_or(serde::de::Error::missing_field("op"))?;
139
140                let ignore_case = match op.as_str() {
141                    "matches" => false,
142                    "matches-" => true,
143                    _ => {
144                        return Err(serde::de::Error::custom(
145                            "`op` should be either `matches` or `matches-`",
146                        ));
147                    }
148                };
149
150                Ok(Matches {
151                    path,
152                    ignore_case,
153                    value,
154                })
155            }
156        }
157
158        const FIELDS: &[&str] = &["path", "op", "value"];
159        Deserializer::deserialize_struct(
160            deserializer,
161            "Matches",
162            FIELDS,
163            VisitorMatches {
164                marker: PhantomData::<Matches>,
165                lifetime: PhantomData,
166            },
167        )
168    }
169}
170
171impl From<Matches> for FirstOrder {
172    fn from(value: Matches) -> Self {
173        FirstOrder::Matches(value)
174    }
175}
176
177impl PredicateImpl for Matches {
178    fn evaluate(&self, data: &Value, ctx: PredicateContext) -> Result<bool, PredicateError> {
179        let path = ctx.final_path(&self.path).unwrap_or(JSONPath::empty());
180        let ptr = path.take();
181
182        let value = &self.value;
183        let context_value = ptr.get(data)?;
184
185        match context_value {
186            Value::String(context) => {
187                if self.ignore_case {
188                    let value = RegexBuilder::new(value.as_ref().as_str())
189                        .case_insensitive(true)
190                        .build()
191                        .map_err(|_err| PredicateError::ty_issue())?;
192
193                    Ok(value.is_match(context))
194                } else {
195                    Ok(value.as_ref().is_match(context))
196                }
197            }
198            _ => Ok(false),
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use regex::Regex;
206    use serde::Deserialize;
207
208    use crate::{json_path::JSONPath, predicate::first_order::matches::Matches};
209
210    #[test]
211    fn snapshot_test() {
212        let matches_expect = serde_json::json!({
213             "op": "matches",
214             "path": "/a/b",
215             "value": ".*"
216        });
217
218        let matches = Matches {
219            path: Some(JSONPath::new("/a/b").unwrap()),
220            ignore_case: false,
221            value: Regex::new(".*").unwrap().into(),
222        };
223
224        assert_eq!(serde_json::to_value(matches).unwrap(), matches_expect);
225    }
226
227    #[test]
228    fn deser_test() {
229        let matches_expect = serde_json::json!({
230             "op": "matches",
231             "path": "/a/b",
232             "value": ".*"
233        });
234
235        let matches = Matches {
236            path: Some(JSONPath::new("/a/b").unwrap()),
237            ignore_case: false,
238            value: Regex::new(".*").unwrap().into(),
239        };
240
241        let deser = Matches::deserialize(matches_expect).unwrap();
242
243        assert_eq!(matches, deser);
244    }
245}