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 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}