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 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
112peg::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}