Skip to main content

scim_proto/
filter.rs

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