scim_filter/parser/
model.rs

1use std::ops::Deref;
2use std::str::FromStr;
3
4use nom::Finish;
5use rust_decimal::Decimal;
6
7use super::filter;
8use crate::Error;
9
10#[derive(Debug, PartialEq)]
11pub enum Filter<'a> {
12    AttrExp(AttrExpData<'a>),
13    LogExp(LogExpData<'a>),
14    ValuePath(ValuePathData<'a>),
15    Sub(bool, Box<Filter<'a>>),
16}
17
18impl<'a> Filter<'a> {
19    pub fn sub_filter((not, filter): (bool, Filter<'a>)) -> Self {
20        Self::Sub(not, Box::new(filter))
21    }
22}
23
24#[derive(Debug, PartialEq)]
25pub enum AttrExpData<'a> {
26    Present(AttrPath),
27    Compare(AttrPath, CompareOp, CompValue<'a>),
28}
29
30#[derive(Debug, PartialEq)]
31pub struct AttrPath {
32    uri: Option<Uri>,
33    attr_name: AttrName,
34    sub_attr: Option<SubAttr>,
35}
36
37impl AttrPath {
38    pub fn new((uri, attr_name, sub_attr): (Option<Uri>, AttrName, Option<SubAttr>)) -> Self {
39        Self {
40            uri,
41            attr_name,
42            sub_attr,
43        }
44    }
45
46    pub fn attr_name(&self) -> &AttrName {
47        &self.attr_name
48    }
49    pub fn sub_attr(&self) -> &Option<SubAttr> {
50        &self.sub_attr
51    }
52}
53
54#[derive(Debug, Copy, Clone, PartialEq)]
55pub enum CompareOp {
56    Equal,
57    NotEqual,
58    Contains,
59    StartsWith,
60    EndsWith,
61    GreaterThan,
62    GreaterThanOrEqual,
63    LessThan,
64    LessThanOrEqual,
65}
66
67impl FromStr for CompareOp {
68    type Err = String;
69
70    fn from_str(s: &str) -> Result<Self, Self::Err> {
71        match s.to_lowercase().as_str() {
72            "eq" => Ok(Self::Equal),
73            "ne" => Ok(Self::NotEqual),
74            "co" => Ok(Self::Contains),
75            "sw" => Ok(Self::StartsWith),
76            "ew" => Ok(Self::EndsWith),
77            "gt" => Ok(Self::GreaterThan),
78            "ge" => Ok(Self::GreaterThanOrEqual),
79            "lt" => Ok(Self::LessThan),
80            "le" => Ok(Self::LessThanOrEqual),
81            _ => Err(format!("{} is not a valid operator", s)),
82        }
83    }
84}
85
86// https://datatracker.ietf.org/doc/html/rfc3986#appendix-A
87pub type Uri = String;
88
89#[derive(Debug, PartialEq)]
90pub struct AttrName(pub(crate) String);
91
92impl AttrName {
93    pub fn new<'a>((initial, name_chars): (Alpha<'a>, Vec<NameChar<'a>>)) -> Self {
94        Self(format!(
95            "{}{}",
96            initial,
97            name_chars.into_iter().collect::<String>()
98        ))
99    }
100
101    #[cfg(test)]
102    pub fn from_str<'a>(name: &str) -> Self {
103        Self(name.to_string())
104    }
105}
106
107impl Deref for AttrName {
108    type Target = str;
109
110    fn deref(&self) -> &Self::Target {
111        self.0.as_str()
112    }
113}
114
115type Alpha<'a> = &'a str;
116
117pub type NameChar<'a> = &'a str;
118
119pub type SubAttr = AttrName;
120
121// https://datatracker.ietf.org/doc/html/rfc7159
122#[derive(Clone, Debug, PartialEq)]
123pub enum CompValue<'a> {
124    False,
125    Null,
126    True,
127    Number(Decimal),
128    String(&'a str),
129}
130
131#[derive(Debug, PartialEq)]
132pub struct LogExpData<'a> {
133    pub left: Box<Filter<'a>>,
134    pub log_exp_operator: LogExpOperator,
135    pub right: Box<Filter<'a>>,
136}
137
138impl<'a> LogExpData<'a> {
139    pub fn new((left, log_exp_operator, right): (Filter<'a>, LogExpOperator, Filter<'a>)) -> Self {
140        Self {
141            left: Box::new(left),
142            log_exp_operator,
143            right: Box::new(right),
144        }
145    }
146}
147
148#[derive(Clone, Debug, PartialEq)]
149pub enum LogExpOperator {
150    And,
151    Or,
152}
153
154impl LogExpOperator {
155    pub fn is_or(&self) -> bool {
156        match self {
157            LogExpOperator::And => false,
158            LogExpOperator::Or => true,
159        }
160    }
161
162    pub fn is_and(&self) -> bool {
163        !self.is_or()
164    }
165}
166
167#[derive(Debug, PartialEq)]
168pub struct ValuePathData<'a> {
169    attr_path: AttrPath,
170    val_filter: ValFilter<'a>,
171}
172
173impl<'a> ValuePathData<'a> {
174    pub fn new((attr_path, val_filter): (AttrPath, ValFilter<'a>)) -> Self {
175        Self {
176            attr_path,
177            val_filter,
178        }
179    }
180    pub fn attr_path(&self) -> &AttrPath {
181        &self.attr_path
182    }
183    pub fn val_filter(&self) -> &ValFilter<'a> {
184        &self.val_filter
185    }
186}
187
188#[derive(Debug, PartialEq)]
189pub enum ValFilter<'a> {
190    AttrExp(AttrExpData<'a>),
191    LogExp(LogExpData<'a>),
192    SubFilter(bool, Box<ValFilter<'a>>),
193}
194
195impl<'a> ValFilter<'a> {
196    pub fn attr_exp(data: AttrExpData<'a>) -> Self {
197        ValFilter::AttrExp(data)
198    }
199
200    pub fn log_exp(data: LogExpData<'a>) -> Self {
201        Self::LogExp(data)
202    }
203
204    pub fn sub_filter((not, val_filter): (bool, ValFilter<'a>)) -> Self {
205        Self::SubFilter(not, Box::new(val_filter))
206    }
207}
208
209/// main API entrance for this module, given a filter string,
210/// it generates an Result with a possible parsed Expression struct
211pub(crate) fn scim_filter_parser(input: &str) -> Result<Filter, Error> {
212    let (remain, expression) = filter(input).map_err(|e| e.to_owned()).finish()?;
213    if !remain.is_empty() {
214        return Err(Error::WrongFilterFormat(
215            input.to_owned(),
216            remain.to_owned(),
217        ));
218    }
219    Ok(expression)
220}