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