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