scim_proto/
filter.rs

1#![allow(warnings)]
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct AttrPath {
9    // Uri: Option<String>,
10    a: String,
11    s: Option<String>,
12}
13
14impl ToString for AttrPath {
15    fn to_string(&self) -> String {
16        match self {
17            Self {
18                a: attrname,
19                s: Some(subattr),
20            } => format!("{attrname}.{subattr}"),
21            Self {
22                a: attrname,
23                s: None,
24            } => attrname.to_owned(),
25        }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub enum ScimFilter {
31    Or(Box<ScimFilter>, Box<ScimFilter>),
32    And(Box<ScimFilter>, Box<ScimFilter>),
33    Not(Box<ScimFilter>),
34
35    Present(AttrPath),
36    Equal(AttrPath, Value),
37    NotEqual(AttrPath, Value),
38    Contains(AttrPath, Value),
39    StartsWith(AttrPath, Value),
40    EndsWith(AttrPath, Value),
41    Greater(AttrPath, Value),
42    Less(AttrPath, Value),
43    GreaterOrEqual(AttrPath, Value),
44    LessOrEqual(AttrPath, Value),
45
46    Complex(String, Box<ScimComplexFilter>),
47}
48
49impl ToString for ScimFilter {
50    fn to_string(&self) -> String {
51        match self {
52            Self::And(this, that) => format!("({} and {})", this.to_string(), that.to_string()),
53            Self::Contains(attrpath, value) => format!("({} co {value})", attrpath.to_string()),
54            Self::EndsWith(attrpath, value) => format!("({} ew {value})", attrpath.to_string()),
55            Self::Equal(attrpath, value) => format!("({} eq {value})", attrpath.to_string()),
56            Self::Greater(attrpath, value) => format!("({} gt {value})", attrpath.to_string()),
57            Self::GreaterOrEqual(attrpath, value) => {
58                format!("({} ge {value})", attrpath.to_string())
59            }
60            Self::Less(attrpath, value) => format!("({} lt {value})", attrpath.to_string()),
61            Self::LessOrEqual(attrpath, value) => format!("({} le {value})", attrpath.to_string()),
62            Self::Not(expr) => format!("(not ({}))", expr.to_string()),
63            Self::NotEqual(attrpath, value) => format!("({} ne {value})", attrpath.to_string()),
64            Self::Or(this, that) => format!("({} or {})", this.to_string(), that.to_string()),
65            Self::Present(attrpath) => format!("({} pr)", attrpath.to_string()),
66            Self::StartsWith(attrpath, value) => format!("({} sw {value})", attrpath.to_string()),
67            Self::Complex(attrname, expr) => format!("{attrname}[{}]", expr.to_string()),
68        }
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub enum ScimComplexFilter {
74    Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
75    And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
76    Not(Box<ScimComplexFilter>),
77
78    Present(String),
79    Equal(String, Value),
80    NotEqual(String, Value),
81    Contains(String, Value),
82    StartsWith(String, Value),
83    EndsWith(String, Value),
84    Greater(String, Value),
85    Less(String, Value),
86    GreaterOrEqual(String, Value),
87    LessOrEqual(String, Value),
88}
89
90impl ToString for ScimComplexFilter {
91    fn to_string(&self) -> String {
92        match self {
93            Self::And(this, that) => format!("({} and {})", this.to_string(), that.to_string()),
94            Self::Contains(attrname, value) => format!("({attrname} co {value})"),
95            Self::EndsWith(attrname, value) => format!("({attrname} ew {value})"),
96            Self::Equal(attrname, value) => format!("({attrname} eq {value})"),
97            Self::Greater(attrname, value) => format!("({attrname} gt {value})"),
98            Self::GreaterOrEqual(attrname, value) => format!("({attrname} ge {value})"),
99            Self::Less(attrname, value) => format!("({attrname} lt {value})"),
100            Self::LessOrEqual(attrname, value) => format!("({attrname} le {value})"),
101            Self::Not(expr) => format!("(not ({}))", expr.to_string()),
102            Self::NotEqual(attrname, value) => format!("({attrname} ne {value})"),
103            Self::Or(this, that) => format!("({} or {})", this.to_string(), that.to_string()),
104            Self::Present(attrname) => format!("({attrname} pr)"),
105            Self::StartsWith(attrname, value) => format!("({attrname} sw {value})"),
106        }
107    }
108}
109
110// separator()* "(" e:term() ")" separator()* { e }
111
112peg::parser! {
113    grammar scimfilter() for str {
114
115        pub rule parse() -> ScimFilter = precedence!{
116            a:(@) separator()+ "or" separator()+ b:@ {
117                ScimFilter::Or(
118                    Box::new(a),
119                    Box::new(b)
120                )
121            }
122            --
123            a:(@) separator()+ "and" separator()+ b:@ {
124                ScimFilter::And(
125                    Box::new(a),
126                    Box::new(b)
127                )
128            }
129            --
130            "not" separator()+ "(" e:parse() ")" {
131                ScimFilter::Not(Box::new(e))
132            }
133            --
134            a:attrname()"[" e:parse_complex() "]" {
135                ScimFilter::Complex(
136                    a,
137                    Box::new(e)
138                )
139            }
140            --
141            a:attrexp() { a }
142            "(" e:parse() ")" { e }
143        }
144
145        pub rule parse_complex() -> ScimComplexFilter = precedence!{
146            a:(@) separator()+ "or" separator()+ b:@ {
147                ScimComplexFilter::Or(
148                    Box::new(a),
149                    Box::new(b)
150                )
151            }
152            --
153            a:(@) separator()+ "and" separator()+ b:@ {
154                ScimComplexFilter::And(
155                    Box::new(a),
156                    Box::new(b)
157                )
158            }
159            --
160            "not" separator()+ "(" e:parse_complex() ")" {
161                ScimComplexFilter::Not(Box::new(e))
162            }
163            --
164            a:complex_attrexp() { a }
165            "(" e:parse_complex() ")" { e }
166        }
167
168        pub(crate) rule attrexp() -> ScimFilter =
169            pres()
170            / eq()
171            / ne()
172            / co()
173            / sw()
174            / ew()
175            / gt()
176            / lt()
177            / ge()
178            / le()
179
180        pub(crate) rule pres() -> ScimFilter =
181            a:attrpath() separator()+ "pr" { ScimFilter::Present(a) }
182
183        pub(crate) rule eq() -> ScimFilter =
184            a:attrpath() separator()+ "eq" separator()+ v:value() { ScimFilter::Equal(a, v) }
185
186        pub(crate) rule ne() -> ScimFilter =
187            a:attrpath() separator()+ "ne" separator()+ v:value() { ScimFilter::NotEqual(a, v) }
188
189        pub(crate) rule co() -> ScimFilter =
190            a:attrpath() separator()+ "co" separator()+ v:value() { ScimFilter::Contains(a, v) }
191
192        pub(crate) rule sw() -> ScimFilter =
193            a:attrpath() separator()+ "sw" separator()+ v:value() { ScimFilter::StartsWith(a, v) }
194
195        pub(crate) rule ew() -> ScimFilter =
196            a:attrpath() separator()+ "ew" separator()+ v:value() { ScimFilter::EndsWith(a, v) }
197
198        pub(crate) rule gt() -> ScimFilter =
199            a:attrpath() separator()+ "gt" separator()+ v:value() { ScimFilter::Greater(a, v) }
200
201        pub(crate) rule lt() -> ScimFilter =
202            a:attrpath() separator()+ "lt" separator()+ v:value() { ScimFilter::Less(a, v) }
203
204        pub(crate) rule ge() -> ScimFilter =
205            a:attrpath() separator()+ "ge" separator()+ v:value() { ScimFilter::GreaterOrEqual(a, v) }
206
207        pub(crate) rule le() -> ScimFilter =
208            a:attrpath() separator()+ "le" separator()+ v:value() { ScimFilter::LessOrEqual(a, v) }
209
210        pub(crate) rule complex_attrexp() -> ScimComplexFilter =
211            c_pres()
212            / c_eq()
213            / c_ne()
214            / c_co()
215            / c_sw()
216            / c_ew()
217            / c_gt()
218            / c_lt()
219            / c_ge()
220            / c_le()
221
222        pub(crate) rule c_pres() -> ScimComplexFilter =
223            a:attrname() separator()+ "pr" { ScimComplexFilter::Present(a) }
224
225        pub(crate) rule c_eq() -> ScimComplexFilter =
226            a:attrname() separator()+ "eq" separator()+ v:value() { ScimComplexFilter::Equal(a, v) }
227
228        pub(crate) rule c_ne() -> ScimComplexFilter =
229            a:attrname() separator()+ "ne" separator()+ v:value() { ScimComplexFilter::NotEqual(a, v) }
230
231        pub(crate) rule c_co() -> ScimComplexFilter =
232            a:attrname() separator()+ "co" separator()+ v:value() { ScimComplexFilter::Contains(a, v) }
233
234        pub(crate) rule c_sw() -> ScimComplexFilter =
235            a:attrname() separator()+ "sw" separator()+ v:value() { ScimComplexFilter::StartsWith(a, v) }
236
237        pub(crate) rule c_ew() -> ScimComplexFilter =
238            a:attrname() separator()+ "ew" separator()+ v:value() { ScimComplexFilter::EndsWith(a, v) }
239
240        pub(crate) rule c_gt() -> ScimComplexFilter =
241            a:attrname() separator()+ "gt" separator()+ v:value() { ScimComplexFilter::Greater(a, v) }
242
243        pub(crate) rule c_lt() -> ScimComplexFilter =
244            a:attrname() separator()+ "lt" separator()+ v:value() { ScimComplexFilter::Less(a, v) }
245
246        pub(crate) rule c_ge() -> ScimComplexFilter =
247            a:attrname() separator()+ "ge" separator()+ v:value() { ScimComplexFilter::GreaterOrEqual(a, v) }
248
249        pub(crate) rule c_le() -> ScimComplexFilter =
250            a:attrname() separator()+ "le" separator()+ v:value() { ScimComplexFilter::LessOrEqual(a, v) }
251
252        rule separator() =
253            ['\n' | ' ' | '\t' ]
254
255        rule operator() =
256            ['\n' | ' ' | '\t' | '(' | ')' | '[' | ']' ]
257
258        rule value() -> Value =
259            barevalue()
260
261        rule barevalue() -> Value =
262            s:$((!operator()[_])*) {? serde_json::from_str(s).map_err(|_| "invalid json value" ) }
263
264        pub(crate) rule attrpath() -> AttrPath =
265            a:attrname() s:subattr()? { AttrPath { a, s } }
266
267        rule subattr() -> String =
268            "." s:attrname() { s.to_string() }
269
270        pub(crate) rule attrname() -> String =
271            s:$([ 'a'..='z' | 'A'..='Z']['a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' ]*) { s.to_string() }
272    }
273}
274
275impl FromStr for AttrPath {
276    type Err = peg::error::ParseError<peg::str::LineCol>;
277    fn from_str(input: &str) -> Result<Self, Self::Err> {
278        scimfilter::attrpath(input)
279    }
280}
281
282impl FromStr for ScimFilter {
283    type Err = peg::error::ParseError<peg::str::LineCol>;
284    fn from_str(input: &str) -> Result<Self, Self::Err> {
285        scimfilter::parse(input)
286    }
287}
288
289impl FromStr for ScimComplexFilter {
290    type Err = peg::error::ParseError<peg::str::LineCol>;
291    fn from_str(input: &str) -> Result<Self, Self::Err> {
292        scimfilter::parse_complex(input)
293    }
294}
295
296#[cfg(test)]
297mod test {
298    use super::*;
299    use crate::filter::AttrPath;
300    use crate::filter::ScimFilter;
301    use serde_json::Value;
302
303    #[test]
304    fn test_scimfilter_attrname() {
305        assert_eq!(scimfilter::attrname("abcd-_"), Ok("abcd-_".to_string()));
306        assert_eq!(scimfilter::attrname("aB-_CD"), Ok("aB-_CD".to_string()));
307        assert_eq!(scimfilter::attrname("a1-_23"), Ok("a1-_23".to_string()));
308        assert!(scimfilter::attrname("-bcd").is_err());
309        assert!(scimfilter::attrname("_bcd").is_err());
310        assert!(scimfilter::attrname("0bcd").is_err());
311    }
312
313    #[test]
314    fn test_scimfilter_attrpath() {
315        assert_eq!(
316            scimfilter::attrpath("abcd"),
317            Ok(AttrPath {
318                a: "abcd".to_string(),
319                s: None
320            })
321        );
322
323        assert_eq!(
324            scimfilter::attrpath("abcd.abcd"),
325            Ok(AttrPath {
326                a: "abcd".to_string(),
327                s: Some("abcd".to_string())
328            })
329        );
330
331        assert!(scimfilter::attrname("abcd.0").is_err());
332        assert!(scimfilter::attrname("abcd._").is_err());
333        assert!(scimfilter::attrname("abcd,0").is_err());
334        assert!(scimfilter::attrname(".abcd").is_err());
335    }
336
337    #[test]
338    fn test_scimfilter_pres() {
339        assert!(
340            scimfilter::parse("abcd pr")
341                == Ok(ScimFilter::Present(AttrPath {
342                    a: "abcd".to_string(),
343                    s: None
344                }))
345        );
346    }
347
348    #[test]
349    fn test_scimfilter_eq() {
350        assert!(
351            scimfilter::parse("abcd eq \"dcba\"")
352                == Ok(ScimFilter::Equal(
353                    AttrPath {
354                        a: "abcd".to_string(),
355                        s: None
356                    },
357                    Value::String("dcba".to_string())
358                ))
359        );
360    }
361
362    #[test]
363    fn test_scimfilter_ne() {
364        assert!(
365            scimfilter::parse("abcd ne \"dcba\"")
366                == Ok(ScimFilter::NotEqual(
367                    AttrPath {
368                        a: "abcd".to_string(),
369                        s: None
370                    },
371                    Value::String("dcba".to_string())
372                ))
373        );
374    }
375
376    #[test]
377    fn test_scimfilter_co() {
378        assert!(
379            scimfilter::parse("abcd co \"dcba\"")
380                == Ok(ScimFilter::Contains(
381                    AttrPath {
382                        a: "abcd".to_string(),
383                        s: None
384                    },
385                    Value::String("dcba".to_string())
386                ))
387        );
388    }
389
390    #[test]
391    fn test_scimfilter_sw() {
392        assert!(
393            scimfilter::parse("abcd sw \"dcba\"")
394                == Ok(ScimFilter::StartsWith(
395                    AttrPath {
396                        a: "abcd".to_string(),
397                        s: None
398                    },
399                    Value::String("dcba".to_string())
400                ))
401        );
402    }
403
404    #[test]
405    fn test_scimfilter_ew() {
406        assert!(
407            scimfilter::parse("abcd ew \"dcba\"")
408                == Ok(ScimFilter::EndsWith(
409                    AttrPath {
410                        a: "abcd".to_string(),
411                        s: None
412                    },
413                    Value::String("dcba".to_string())
414                ))
415        );
416    }
417
418    #[test]
419    fn test_scimfilter_gt() {
420        assert!(
421            scimfilter::parse("abcd gt \"dcba\"")
422                == Ok(ScimFilter::Greater(
423                    AttrPath {
424                        a: "abcd".to_string(),
425                        s: None
426                    },
427                    Value::String("dcba".to_string())
428                ))
429        );
430    }
431
432    #[test]
433    fn test_scimfilter_lt() {
434        assert!(
435            scimfilter::parse("abcd lt \"dcba\"")
436                == Ok(ScimFilter::Less(
437                    AttrPath {
438                        a: "abcd".to_string(),
439                        s: None
440                    },
441                    Value::String("dcba".to_string())
442                ))
443        );
444    }
445
446    #[test]
447    fn test_scimfilter_ge() {
448        assert!(
449            scimfilter::parse("abcd ge \"dcba\"")
450                == Ok(ScimFilter::GreaterOrEqual(
451                    AttrPath {
452                        a: "abcd".to_string(),
453                        s: None
454                    },
455                    Value::String("dcba".to_string())
456                ))
457        );
458    }
459
460    #[test]
461    fn test_scimfilter_le() {
462        assert!(
463            scimfilter::parse("abcd le \"dcba\"")
464                == Ok(ScimFilter::LessOrEqual(
465                    AttrPath {
466                        a: "abcd".to_string(),
467                        s: None
468                    },
469                    Value::String("dcba".to_string())
470                ))
471        );
472    }
473
474    #[test]
475    fn test_scimfilter_group() {
476        let f = scimfilter::parse("(abcd eq \"dcba\")");
477        eprintln!("{:?}", f);
478        assert!(
479            f == Ok(ScimFilter::Equal(
480                AttrPath {
481                    a: "abcd".to_string(),
482                    s: None
483                },
484                Value::String("dcba".to_string())
485            ))
486        );
487    }
488
489    #[test]
490    fn test_scimfilter_not() {
491        let f = scimfilter::parse("not (abcd eq \"dcba\")");
492        eprintln!("{:?}", f);
493
494        assert!(
495            f == Ok(ScimFilter::Not(Box::new(ScimFilter::Equal(
496                AttrPath {
497                    a: "abcd".to_string(),
498                    s: None
499                },
500                Value::String("dcba".to_string())
501            ))))
502        );
503    }
504
505    #[test]
506    fn test_scimfilter_and() {
507        let f = scimfilter::parse("abcd eq \"dcba\" and bcda ne \"1234\"");
508        eprintln!("{:?}", f);
509
510        assert!(
511            f == Ok(ScimFilter::And(
512                Box::new(ScimFilter::Equal(
513                    AttrPath {
514                        a: "abcd".to_string(),
515                        s: None
516                    },
517                    Value::String("dcba".to_string())
518                )),
519                Box::new(ScimFilter::NotEqual(
520                    AttrPath {
521                        a: "bcda".to_string(),
522                        s: None
523                    },
524                    Value::String("1234".to_string())
525                ))
526            ))
527        );
528    }
529
530    #[test]
531    fn test_scimfilter_or() {
532        let f = scimfilter::parse("abcd eq \"dcba\" or bcda ne \"1234\"");
533        eprintln!("{:?}", f);
534
535        assert!(
536            f == Ok(ScimFilter::Or(
537                Box::new(ScimFilter::Equal(
538                    AttrPath {
539                        a: "abcd".to_string(),
540                        s: None
541                    },
542                    Value::String("dcba".to_string())
543                )),
544                Box::new(ScimFilter::NotEqual(
545                    AttrPath {
546                        a: "bcda".to_string(),
547                        s: None
548                    },
549                    Value::String("1234".to_string())
550                ))
551            ))
552        );
553    }
554
555    #[test]
556    fn test_scimfilter_complex() {
557        let f = scimfilter::parse("emails[type eq \"work\"]");
558        eprintln!("-- {:?}", f);
559        assert!(f.is_ok());
560
561        let f = scimfilter::parse("emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]");
562        eprintln!("{:?}", f);
563
564        assert_eq!(
565            f,
566            Ok(ScimFilter::Or(
567                Box::new(ScimFilter::Complex(
568                    "emails".to_string(),
569                    Box::new(ScimComplexFilter::And(
570                        Box::new(ScimComplexFilter::Equal(
571                            "type".to_string(),
572                            Value::String("work".to_string())
573                        )),
574                        Box::new(ScimComplexFilter::Contains(
575                            "value".to_string(),
576                            Value::String("@example.com".to_string())
577                        ))
578                    ))
579                )),
580                Box::new(ScimFilter::Complex(
581                    "ims".to_string(),
582                    Box::new(ScimComplexFilter::And(
583                        Box::new(ScimComplexFilter::Equal(
584                            "type".to_string(),
585                            Value::String("xmpp".to_string())
586                        )),
587                        Box::new(ScimComplexFilter::Contains(
588                            "value".to_string(),
589                            Value::String("@foo.com".to_string())
590                        ))
591                    ))
592                ))
593            ))
594        );
595    }
596
597    #[test]
598    fn test_scimfilter_precedence_1() {
599        let f = scimfilter::parse("a pr or b pr and c pr or d pr");
600        eprintln!("{:?}", f);
601
602        assert!(
603            f == Ok(ScimFilter::Or(
604                Box::new(ScimFilter::Or(
605                    Box::new(ScimFilter::Present(AttrPath {
606                        a: "a".to_string(),
607                        s: None
608                    })),
609                    Box::new(ScimFilter::And(
610                        Box::new(ScimFilter::Present(AttrPath {
611                            a: "b".to_string(),
612                            s: None
613                        })),
614                        Box::new(ScimFilter::Present(AttrPath {
615                            a: "c".to_string(),
616                            s: None
617                        })),
618                    )),
619                )),
620                Box::new(ScimFilter::Present(AttrPath {
621                    a: "d".to_string(),
622                    s: None
623                }))
624            ))
625        );
626    }
627
628    #[test]
629    fn test_scimfilter_precedence_2() {
630        let f = scimfilter::parse("a pr and b pr or c pr and d pr");
631        eprintln!("{:?}", f);
632
633        assert!(
634            f == Ok(ScimFilter::Or(
635                Box::new(ScimFilter::And(
636                    Box::new(ScimFilter::Present(AttrPath {
637                        a: "a".to_string(),
638                        s: None
639                    })),
640                    Box::new(ScimFilter::Present(AttrPath {
641                        a: "b".to_string(),
642                        s: None
643                    })),
644                )),
645                Box::new(ScimFilter::And(
646                    Box::new(ScimFilter::Present(AttrPath {
647                        a: "c".to_string(),
648                        s: None
649                    })),
650                    Box::new(ScimFilter::Present(AttrPath {
651                        a: "d".to_string(),
652                        s: None
653                    })),
654                )),
655            ))
656        );
657    }
658
659    #[test]
660    fn test_scimfilter_precedence_3() {
661        let f = scimfilter::parse("a pr and (b pr or c pr) and d pr");
662        eprintln!("{:?}", f);
663
664        assert!(
665            f == Ok(ScimFilter::And(
666                Box::new(ScimFilter::And(
667                    Box::new(ScimFilter::Present(AttrPath {
668                        a: "a".to_string(),
669                        s: None
670                    })),
671                    Box::new(ScimFilter::Or(
672                        Box::new(ScimFilter::Present(AttrPath {
673                            a: "b".to_string(),
674                            s: None
675                        })),
676                        Box::new(ScimFilter::Present(AttrPath {
677                            a: "c".to_string(),
678                            s: None
679                        })),
680                    )),
681                )),
682                Box::new(ScimFilter::Present(AttrPath {
683                    a: "d".to_string(),
684                    s: None
685                })),
686            ))
687        );
688    }
689
690    #[test]
691    fn test_scimfilter_precedence_4() {
692        let f = scimfilter::parse("a pr and not (b pr or c pr) and d pr");
693        eprintln!("{:?}", f);
694
695        assert!(
696            f == Ok(ScimFilter::And(
697                Box::new(ScimFilter::And(
698                    Box::new(ScimFilter::Present(AttrPath {
699                        a: "a".to_string(),
700                        s: None
701                    })),
702                    Box::new(ScimFilter::Not(Box::new(ScimFilter::Or(
703                        Box::new(ScimFilter::Present(AttrPath {
704                            a: "b".to_string(),
705                            s: None
706                        })),
707                        Box::new(ScimFilter::Present(AttrPath {
708                            a: "c".to_string(),
709                            s: None
710                        })),
711                    )))),
712                )),
713                Box::new(ScimFilter::Present(AttrPath {
714                    a: "d".to_string(),
715                    s: None
716                })),
717            ))
718        );
719    }
720}