Skip to main content

kanidmd_lib/valueset/
url.rs

1use crate::prelude::*;
2use crate::schema::SchemaAttribute;
3use crate::valueset::{
4    DbValueSetV2, ScimResolveStatus, ValueSet, ValueSetResolveStatus, ValueSetScimPut,
5};
6use kanidm_proto::scim_v1::client::ScimUrls;
7use kanidm_proto::scim_v1::JsonValue;
8use smolset::SmolSet;
9
10#[derive(Debug, Clone)]
11pub struct ValueSetUrl {
12    set: SmolSet<[Url; 1]>,
13}
14
15impl ValueSetUrl {
16    pub fn new(b: Url) -> Box<Self> {
17        let mut set = SmolSet::new();
18        set.insert(b);
19        Box::new(ValueSetUrl { set })
20    }
21
22    pub fn push(&mut self, b: Url) -> bool {
23        self.set.insert(b)
24    }
25
26    pub fn from_dbvs2(data: Vec<Url>) -> Result<ValueSet, OperationError> {
27        let set = data.into_iter().collect();
28        Ok(Box::new(ValueSetUrl { set }))
29    }
30
31    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
32    // types, and Url is foreign.
33    #[allow(clippy::should_implement_trait)]
34    pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
35    where
36        T: IntoIterator<Item = Url>,
37    {
38        let set = iter.into_iter().collect();
39        Some(Box::new(ValueSetUrl { set }))
40    }
41}
42
43impl ValueSetScimPut for ValueSetUrl {
44    fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
45        let ScimUrls(url_set) = serde_json::from_value(value).map_err(|err| {
46            error!(?err, "SCIM URL syntax invalid");
47            OperationError::SC0007UrlSyntaxInvalid
48        })?;
49
50        let set = SmolSet::from_iter(url_set);
51
52        Ok(ValueSetResolveStatus::Resolved(Box::new(ValueSetUrl {
53            set,
54        })))
55    }
56}
57
58impl ValueSetT for ValueSetUrl {
59    fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
60        match value {
61            Value::Url(u) => Ok(self.set.insert(u)),
62            _ => {
63                debug_assert!(false);
64                Err(OperationError::InvalidValueState)
65            }
66        }
67    }
68
69    fn clear(&mut self) {
70        self.set.clear();
71    }
72
73    fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
74        match pv {
75            PartialValue::Url(u) => self.set.remove(u),
76            _ => false,
77        }
78    }
79
80    fn contains(&self, pv: &PartialValue) -> bool {
81        match pv {
82            PartialValue::Url(u) => self.set.contains(u),
83            _ => false,
84        }
85    }
86
87    fn substring(&self, _pv: &PartialValue) -> bool {
88        false
89    }
90
91    fn startswith(&self, _pv: &PartialValue) -> bool {
92        false
93    }
94
95    fn endswith(&self, _pv: &PartialValue) -> bool {
96        false
97    }
98
99    fn lessthan(&self, _pv: &PartialValue) -> bool {
100        false
101    }
102
103    fn len(&self) -> usize {
104        self.set.len()
105    }
106
107    fn generate_idx_eq_keys(&self) -> Vec<String> {
108        self.set.iter().map(|u| u.to_string()).collect()
109    }
110
111    fn syntax(&self) -> SyntaxType {
112        SyntaxType::Url
113    }
114
115    fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
116        true
117    }
118
119    fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
120        Box::new(self.set.iter().map(|i| i.to_string()))
121    }
122
123    fn to_scim_value(&self) -> Option<ScimResolveStatus> {
124        let mut iter = self.set.iter().map(|url| url.to_string());
125        if self.len() == 1 {
126            let v = iter.next().unwrap_or_default();
127            Some(v.into())
128        } else {
129            let mut arr = iter.collect::<Vec<_>>();
130            arr.sort();
131            Some(arr.into())
132        }
133    }
134
135    fn to_db_valueset_v2(&self) -> DbValueSetV2 {
136        DbValueSetV2::Url(self.set.iter().cloned().collect())
137    }
138
139    fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
140        Box::new(self.set.iter().cloned().map(PartialValue::Url))
141    }
142
143    fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
144        Box::new(self.set.iter().cloned().map(Value::Url))
145    }
146
147    fn equal(&self, other: &ValueSet) -> bool {
148        if let Some(other) = other.as_url_set() {
149            &self.set == other
150        } else {
151            debug_assert!(false);
152            false
153        }
154    }
155
156    fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
157        if let Some(b) = other.as_url_set() {
158            mergesets!(self.set, b)
159        } else {
160            debug_assert!(false);
161            Err(OperationError::InvalidValueState)
162        }
163    }
164
165    fn to_url_single(&self) -> Option<&Url> {
166        if self.set.len() == 1 {
167            self.set.iter().take(1).next()
168        } else {
169            None
170        }
171    }
172
173    fn as_url_set(&self) -> Option<&SmolSet<[Url; 1]>> {
174        Some(&self.set)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::ValueSetUrl;
181    use crate::prelude::{Url, ValueSet};
182
183    #[test]
184    fn test_scim_url() {
185        let u = Url::parse("https://idm.example.com").unwrap();
186        let vs: ValueSet = ValueSetUrl::new(u);
187        crate::valueset::scim_json_reflexive(&vs, r#""https://idm.example.com/""#);
188
189        // Test that we can parse json values into a valueset.
190        crate::valueset::scim_json_put_reflexive::<ValueSetUrl>(&vs, &[]);
191
192        // Check multivalued arrays.
193        let u1 = Url::parse("https://idm1.example.com").unwrap();
194        let u2 = Url::parse("https://idm2.example.com").unwrap();
195        let vs: ValueSet = ValueSetUrl::from_iter([u1, u2]).expect("Unable to create ValueSet");
196        crate::valueset::scim_json_reflexive(
197            &vs,
198            r#"["https://idm1.example.com/", "https://idm2.example.com/"]"#,
199        );
200        crate::valueset::scim_json_put_reflexive::<ValueSetUrl>(&vs, &[]);
201    }
202}