scim_server/resource/value_objects/
extension.rs1use super::value_object_trait::{ExtensionAttribute, ValueObject};
16use crate::error::{ValidationError, ValidationResult};
17use crate::resource::value_objects::SchemaUri;
18use crate::schema::types::{AttributeDefinition, AttributeType};
19use base64::Engine;
20use serde::{Deserialize, Serialize};
21use serde_json::Value;
22use std::any::Any;
23use std::collections::HashMap;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ExtensionAttributeValue {
32 schema_uri: SchemaUri,
34 attribute_name: String,
36 value: Value,
38 #[serde(skip)]
40 definition: Option<AttributeDefinition>,
41}
42
43impl ExtensionAttributeValue {
44 pub fn new(
46 schema_uri: SchemaUri,
47 attribute_name: String,
48 value: Value,
49 definition: Option<AttributeDefinition>,
50 ) -> ValidationResult<Self> {
51 let ext_attr = Self {
52 schema_uri,
53 attribute_name,
54 value,
55 definition,
56 };
57
58 if let Some(ref def) = ext_attr.definition {
60 ext_attr.validate_value_against_definition(def)?;
61 }
62
63 Ok(ext_attr)
64 }
65
66 pub fn new_unchecked(schema_uri: SchemaUri, attribute_name: String, value: Value) -> Self {
71 Self {
72 schema_uri,
73 attribute_name,
74 value,
75 definition: None,
76 }
77 }
78
79 pub fn with_definition(mut self, definition: AttributeDefinition) -> ValidationResult<Self> {
81 self.validate_value_against_definition(&definition)?;
82 self.definition = Some(definition);
83 Ok(self)
84 }
85
86 pub fn schema_uri(&self) -> &SchemaUri {
88 &self.schema_uri
89 }
90
91 pub fn attribute_name(&self) -> &str {
93 &self.attribute_name
94 }
95
96 pub fn value(&self) -> &Value {
98 &self.value
99 }
100
101 pub fn definition(&self) -> Option<&AttributeDefinition> {
103 self.definition.as_ref()
104 }
105
106 pub fn extension_namespace(&self) -> String {
108 if let Some(parts) = self.schema_uri.as_str().split(':').last() {
110 if parts.contains(':') {
111 parts.to_string()
113 } else {
114 parts.to_string()
115 }
116 } else {
117 self.schema_uri.as_str().to_string()
119 }
120 }
121
122 fn validate_value_against_definition(
124 &self,
125 definition: &AttributeDefinition,
126 ) -> ValidationResult<()> {
127 if definition.name != self.attribute_name {
129 return Err(ValidationError::InvalidAttributeName {
130 actual: self.attribute_name.clone(),
131 expected: definition.name.clone(),
132 });
133 }
134
135 self.validate_value_type(definition)?;
137
138 if definition.required && matches!(self.value, Value::Null) {
140 return Err(ValidationError::RequiredAttributeMissing(
141 self.attribute_name.clone(),
142 ));
143 }
144
145 if !definition.canonical_values.is_empty() {
147 self.validate_canonical_values(definition)?;
148 }
149
150 Ok(())
151 }
152
153 fn validate_value_type(&self, definition: &AttributeDefinition) -> ValidationResult<()> {
155 let matches_type = match (&definition.data_type, &self.value) {
156 (AttributeType::String, Value::String(_)) => true,
157 (AttributeType::Boolean, Value::Bool(_)) => true,
158 (AttributeType::Integer, Value::Number(n)) if n.is_i64() => true,
159 (AttributeType::Decimal, Value::Number(_)) => true,
160 (AttributeType::DateTime, Value::String(s)) => {
161 chrono::DateTime::parse_from_rfc3339(s).is_ok()
163 }
164 (AttributeType::Binary, Value::String(s)) => {
165 base64::engine::general_purpose::STANDARD.decode(s).is_ok()
167 }
168 (AttributeType::Reference, Value::String(_)) => true, (AttributeType::Complex, Value::Object(_)) => true,
170 (_, Value::Null) => !definition.required,
171 _ => false,
172 };
173
174 if !matches_type {
175 return Err(ValidationError::InvalidAttributeType {
176 attribute: self.attribute_name.clone(),
177 expected: format!("{:?}", definition.data_type),
178 actual: self.get_value_type_name().to_string(),
179 });
180 }
181
182 Ok(())
183 }
184
185 fn validate_canonical_values(&self, definition: &AttributeDefinition) -> ValidationResult<()> {
187 if let Value::String(value_str) = &self.value {
188 if !definition.canonical_values.contains(value_str) {
189 return Err(ValidationError::InvalidCanonicalValue {
190 attribute: self.attribute_name.clone(),
191 value: value_str.clone(),
192 allowed: definition.canonical_values.clone(),
193 });
194 }
195 }
196 Ok(())
197 }
198
199 fn get_value_type_name(&self) -> &'static str {
201 match &self.value {
202 Value::Null => "null",
203 Value::Bool(_) => "boolean",
204 Value::Number(n) if n.is_i64() => "integer",
205 Value::Number(_) => "decimal",
206 Value::String(_) => "string",
207 Value::Array(_) => "array",
208 Value::Object(_) => "object",
209 }
210 }
211}
212
213impl ValueObject for ExtensionAttributeValue {
214 fn attribute_type(&self) -> AttributeType {
215 if let Some(ref def) = self.definition {
216 def.data_type.clone()
217 } else {
218 match &self.value {
220 Value::String(_) => AttributeType::String,
221 Value::Bool(_) => AttributeType::Boolean,
222 Value::Number(n) if n.is_i64() => AttributeType::Integer,
223 Value::Number(_) => AttributeType::Decimal,
224 Value::Object(_) => AttributeType::Complex,
225 _ => AttributeType::String, }
227 }
228 }
229
230 fn attribute_name(&self) -> &str {
231 &self.attribute_name
232 }
233
234 fn to_json(&self) -> ValidationResult<Value> {
235 Ok(self.value.clone())
236 }
237
238 fn validate_against_schema(&self, definition: &AttributeDefinition) -> ValidationResult<()> {
239 self.validate_value_against_definition(definition)
240 }
241
242 fn as_json_value(&self) -> Value {
243 self.value.clone()
244 }
245
246 fn supports_definition(&self, definition: &AttributeDefinition) -> bool {
247 definition.name == self.attribute_name
248 }
249
250 fn clone_boxed(&self) -> Box<dyn ValueObject> {
251 Box::new(self.clone())
252 }
253
254 fn as_any(&self) -> &dyn Any {
255 self
256 }
257}
258
259impl ExtensionAttribute for ExtensionAttributeValue {
260 fn schema_uri(&self) -> &str {
261 self.schema_uri.as_str()
262 }
263
264 fn extension_namespace(&self) -> &str {
265 self.schema_uri.as_str()
269 }
270
271 fn validate_extension_rules(&self) -> ValidationResult<()> {
272 if let Some(ref def) = self.definition {
275 self.validate_against_schema(def)
276 } else {
277 Ok(())
278 }
279 }
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct ExtensionCollection {
288 extensions: HashMap<String, Vec<ExtensionAttributeValue>>,
290}
291
292impl ExtensionCollection {
293 pub fn new() -> Self {
295 Self {
296 extensions: HashMap::new(),
297 }
298 }
299
300 pub fn add_attribute(&mut self, attribute: ExtensionAttributeValue) {
302 let schema_uri = attribute.schema_uri().as_str().to_string();
303 self.extensions
304 .entry(schema_uri)
305 .or_insert_with(Vec::new)
306 .push(attribute);
307 }
308
309 pub fn get_by_schema(&self, schema_uri: &str) -> Option<&Vec<ExtensionAttributeValue>> {
311 self.extensions.get(schema_uri)
312 }
313
314 pub fn get_attribute(
316 &self,
317 schema_uri: &str,
318 attribute_name: &str,
319 ) -> Option<&ExtensionAttributeValue> {
320 self.extensions
321 .get(schema_uri)?
322 .iter()
323 .find(|attr| attr.attribute_name() == attribute_name)
324 }
325
326 pub fn schema_uris(&self) -> Vec<&str> {
328 self.extensions.keys().map(|s| s.as_str()).collect()
329 }
330
331 pub fn all_attributes(&self) -> Vec<&ExtensionAttributeValue> {
333 self.extensions
334 .values()
335 .flat_map(|attrs| attrs.iter())
336 .collect()
337 }
338
339 pub fn remove_schema(&mut self, schema_uri: &str) -> Option<Vec<ExtensionAttributeValue>> {
341 self.extensions.remove(schema_uri)
342 }
343
344 pub fn is_empty(&self) -> bool {
346 self.extensions.is_empty()
347 }
348
349 pub fn len(&self) -> usize {
351 self.extensions.values().map(|v| v.len()).sum()
352 }
353
354 pub fn validate_all(&self) -> ValidationResult<()> {
356 for attributes in self.extensions.values() {
357 for attribute in attributes {
358 attribute.validate_extension_rules()?;
359 }
360 }
361 Ok(())
362 }
363
364 pub fn to_json(&self) -> ValidationResult<Value> {
369 let mut result = serde_json::Map::new();
370
371 for (schema_uri, attributes) in &self.extensions {
372 let mut schema_obj = serde_json::Map::new();
373
374 for attribute in attributes {
375 schema_obj.insert(attribute.attribute_name().to_string(), attribute.to_json()?);
376 }
377
378 result.insert(schema_uri.clone(), Value::Object(schema_obj));
379 }
380
381 Ok(Value::Object(result))
382 }
383
384 pub fn from_json(value: &Value) -> ValidationResult<Self> {
389 let mut collection = Self::new();
390
391 if let Value::Object(schema_map) = value {
392 for (schema_uri_str, schema_value) in schema_map {
393 let schema_uri = SchemaUri::new(schema_uri_str.clone())?;
394
395 if let Value::Object(attr_map) = schema_value {
396 for (attr_name, attr_value) in attr_map {
397 let ext_attr = ExtensionAttributeValue::new_unchecked(
398 schema_uri.clone(),
399 attr_name.clone(),
400 attr_value.clone(),
401 );
402 collection.add_attribute(ext_attr);
403 }
404 }
405 }
406 }
407
408 Ok(collection)
409 }
410}
411
412impl Default for ExtensionCollection {
413 fn default() -> Self {
414 Self::new()
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use crate::schema::types::{Mutability, Uniqueness};
422
423 fn create_test_schema_uri() -> SchemaUri {
424 SchemaUri::new("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User".to_string())
425 .unwrap()
426 }
427
428 fn create_test_definition() -> AttributeDefinition {
429 AttributeDefinition {
430 name: "employeeNumber".to_string(),
431 data_type: AttributeType::String,
432 multi_valued: false,
433 required: false,
434 case_exact: false,
435 mutability: Mutability::ReadWrite,
436 uniqueness: Uniqueness::None,
437 canonical_values: vec![],
438 sub_attributes: vec![],
439 returned: None,
440 }
441 }
442
443 #[test]
444 fn test_extension_attribute_creation() {
445 let schema_uri = create_test_schema_uri();
446 let definition = create_test_definition();
447 let value = Value::String("12345".to_string());
448
449 let ext_attr = ExtensionAttributeValue::new(
450 schema_uri.clone(),
451 "employeeNumber".to_string(),
452 value.clone(),
453 Some(definition),
454 )
455 .unwrap();
456
457 assert_eq!(ext_attr.schema_uri(), &schema_uri);
458 assert_eq!(ext_attr.attribute_name(), "employeeNumber");
459 assert_eq!(ext_attr.value(), &value);
460 }
461
462 #[test]
463 fn test_extension_attribute_validation() {
464 let schema_uri = create_test_schema_uri();
465 let definition = create_test_definition();
466
467 let valid_value = Value::String("12345".to_string());
469 let result = ExtensionAttributeValue::new(
470 schema_uri.clone(),
471 "employeeNumber".to_string(),
472 valid_value,
473 Some(definition.clone()),
474 );
475 assert!(result.is_ok());
476
477 let invalid_value = Value::Number(serde_json::Number::from(12345));
479 let result = ExtensionAttributeValue::new(
480 schema_uri.clone(),
481 "employeeNumber".to_string(),
482 invalid_value,
483 Some(definition),
484 );
485 assert!(result.is_err());
486 }
487
488 #[test]
489 fn test_extension_collection() {
490 let mut collection = ExtensionCollection::new();
491
492 let schema_uri = create_test_schema_uri();
493 let ext_attr = ExtensionAttributeValue::new_unchecked(
494 schema_uri.clone(),
495 "employeeNumber".to_string(),
496 Value::String("12345".to_string()),
497 );
498
499 collection.add_attribute(ext_attr);
500
501 assert_eq!(collection.len(), 1);
502 assert!(!collection.is_empty());
503 assert!(collection.get_by_schema(schema_uri.as_str()).is_some());
504 assert!(
505 collection
506 .get_attribute(schema_uri.as_str(), "employeeNumber")
507 .is_some()
508 );
509 }
510
511 #[test]
512 fn test_extension_collection_json_round_trip() {
513 let mut collection = ExtensionCollection::new();
514
515 let schema_uri = create_test_schema_uri();
516 let ext_attr = ExtensionAttributeValue::new_unchecked(
517 schema_uri.clone(),
518 "employeeNumber".to_string(),
519 Value::String("12345".to_string()),
520 );
521
522 collection.add_attribute(ext_attr);
523
524 let json = collection.to_json().unwrap();
526 let restored_collection = ExtensionCollection::from_json(&json).unwrap();
527
528 assert_eq!(collection.len(), restored_collection.len());
529 assert!(
530 restored_collection
531 .get_attribute(schema_uri.as_str(), "employeeNumber")
532 .is_some()
533 );
534 }
535
536 #[test]
537 fn test_value_object_trait_implementation() {
538 let schema_uri = create_test_schema_uri();
539 let ext_attr = ExtensionAttributeValue::new_unchecked(
540 schema_uri,
541 "employeeNumber".to_string(),
542 Value::String("12345".to_string()),
543 );
544
545 assert_eq!(ext_attr.attribute_type(), AttributeType::String);
546 assert_eq!(ext_attr.attribute_name(), "employeeNumber");
547 assert_eq!(ext_attr.as_json_value(), Value::String("12345".to_string()));
548 }
549
550 #[test]
551 fn test_extension_attribute_trait_implementation() {
552 let schema_uri = create_test_schema_uri();
553 let ext_attr = ExtensionAttributeValue::new_unchecked(
554 schema_uri.clone(),
555 "employeeNumber".to_string(),
556 Value::String("12345".to_string()),
557 );
558
559 assert_eq!(ext_attr.schema_uri(), &schema_uri);
560 assert!(ext_attr.validate_extension_rules().is_ok());
561 }
562}