1use super::registry::SchemaRegistry;
9use super::types::{AttributeDefinition, AttributeType, Uniqueness};
10use crate::error::{ValidationError, ValidationResult};
11use crate::resource::core::{RequestContext, Resource};
12use crate::resource::provider::ResourceProvider;
13use crate::resource::value_objects::SchemaUri;
14use serde_json::{Map, Value};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum OperationContext {
24 Create,
26 Update,
28 Patch,
30}
31
32impl SchemaRegistry {
33 pub async fn validate_json_resource_with_provider<P>(
49 &self,
50 resource_type: &str,
51 resource_json: &Value,
52 context: OperationContext,
53 provider: &P,
54 request_context: &RequestContext,
55 ) -> ValidationResult<()>
56 where
57 P: ResourceProvider,
58 {
59 self.validate_json_resource_with_context(resource_type, resource_json, context)?;
61
62 self.validate_uniqueness_constraints(
64 resource_type,
65 resource_json,
66 context,
67 provider,
68 request_context,
69 )
70 .await?;
71
72 Ok(())
73 }
74
75 async fn validate_uniqueness_constraints<P>(
77 &self,
78 resource_type: &str,
79 resource_json: &Value,
80 context: OperationContext,
81 provider: &P,
82 request_context: &RequestContext,
83 ) -> ValidationResult<()>
84 where
85 P: ResourceProvider,
86 {
87 let schema = match resource_type {
89 "User" => self.get_user_schema(),
90 "Group" => self.get_group_schema(),
91 _ => return Ok(()), };
93
94 for attr in &schema.attributes {
96 if attr.uniqueness == Uniqueness::Server {
97 if let Some(value) = resource_json.get(&attr.name) {
98 let exclude_id = match context {
100 OperationContext::Update | OperationContext::Patch => {
101 resource_json.get("id").and_then(|v| v.as_str())
102 }
103 OperationContext::Create => None,
104 };
105
106 let existing = provider
108 .find_resource_by_attribute(
109 resource_type,
110 &attr.name,
111 value,
112 request_context,
113 )
114 .await
115 .map_err(|e| ValidationError::Custom {
116 message: format!("Failed to check uniqueness: {}", e),
117 })?;
118
119 if let Some(existing_resource) = existing {
120 let is_same_resource = exclude_id
122 .map(|current_id| {
123 existing_resource.id.as_ref().map(|id| id.as_str())
124 == Some(current_id)
125 })
126 .unwrap_or(false);
127
128 if !is_same_resource {
129 return Err(ValidationError::ServerUniquenessViolation {
130 attribute: attr.name.clone(),
131 value: value.to_string(),
132 });
133 }
134 }
135 }
136 }
137 }
138
139 Ok(())
140 }
141
142 pub fn validate_resource_hybrid(&self, resource: &Resource) -> ValidationResult<()> {
148 for schema_uri in &resource.schemas {
153 if let Some(schema) = self.get_schema_by_id(schema_uri.as_str()) {
154 self.validate_against_schema(resource, schema)?;
155 } else {
156 return Err(ValidationError::UnknownSchemaUri {
157 uri: schema_uri.as_str().to_string(),
158 });
159 }
160 }
161
162 self.validate_schema_combinations(&resource.schemas)?;
164
165 self.validate_multi_valued_attributes(&resource.attributes)?;
167
168 self.validate_complex_attributes(&resource.attributes)?;
170
171 self.validate_attribute_characteristics(&resource.attributes)?;
173
174 Ok(())
175 }
176
177 fn validate_against_schema(
179 &self,
180 resource: &Resource,
181 schema: &super::types::Schema,
182 ) -> ValidationResult<()> {
183 let resource_json = resource.to_json()?;
185
186 self.validate_resource(schema, &resource_json)
188 }
189
190 pub fn validate_json_resource_with_context(
209 &self,
210 resource_type: &str,
211 resource_json: &Value,
212 context: OperationContext,
213 ) -> ValidationResult<()> {
214 if let Some(schemas_value) = resource_json.get("schemas") {
216 if let Some(schemas_array) = schemas_value.as_array() {
217 if schemas_array.is_empty() {
218 return Err(ValidationError::EmptySchemas);
219 }
220
221 let mut seen_schemas = std::collections::HashSet::new();
223 for schema_value in schemas_array {
224 if let Some(schema_uri) = schema_value.as_str() {
225 if !seen_schemas.insert(schema_uri) {
226 return Err(ValidationError::DuplicateSchemaUri {
227 uri: schema_uri.to_string(),
228 });
229 }
230 }
231 }
232 } else {
233 return Err(ValidationError::MissingSchemas);
234 }
235 } else {
236 return Err(ValidationError::MissingSchemas);
237 }
238
239 if let Some(meta_value) = resource_json.get("meta") {
241 if let Some(meta_obj) = meta_value.as_object() {
242 if !meta_obj.contains_key("resourceType") {
243 return Err(ValidationError::MissingResourceType);
244 }
245 }
246 }
247
248 match context {
250 OperationContext::Create => {
251 if resource_json
253 .as_object()
254 .map(|obj| obj.contains_key("id"))
255 .unwrap_or(false)
256 {
257 return Err(ValidationError::ClientProvidedId);
258 }
259 }
260 OperationContext::Update | OperationContext::Patch => {
261 if !resource_json
263 .as_object()
264 .map(|obj| obj.contains_key("id"))
265 .unwrap_or(false)
266 {
267 return Err(ValidationError::MissingId);
268 }
269 }
270 }
271
272 if let Some(meta_value) = resource_json.get("meta") {
274 if let Some(meta_obj) = meta_value.as_object() {
275 let readonly_fields = ["created", "lastModified", "location", "version"];
276 let has_readonly = readonly_fields
277 .iter()
278 .any(|field| meta_obj.contains_key(*field));
279
280 if has_readonly {
281 match context {
282 OperationContext::Create => {
283 return Err(ValidationError::ClientProvidedMeta);
285 }
286 OperationContext::Update | OperationContext::Patch => {
287 }
290 }
291 }
292 }
293 }
294
295 self.validate_multi_valued_attributes_preliminary(resource_type, resource_json)?;
297
298 let resource = Resource::from_json(resource_type.to_string(), resource_json.clone())?;
300
301 self.validate_resource_hybrid(&resource)
303 }
304
305 fn resource_type_to_schema_uri(resource_type: &str) -> Option<&'static str> {
307 match resource_type {
308 "User" => Some("urn:ietf:params:scim:schemas:core:2.0:User"),
309 "Group" => Some("urn:ietf:params:scim:schemas:core:2.0:Group"),
310 _ => None,
311 }
312 }
313
314 fn validate_multi_valued_attributes_preliminary(
317 &self,
318 resource_type: &str,
319 resource_json: &Value,
320 ) -> ValidationResult<()> {
321 let obj = resource_json
322 .as_object()
323 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
324
325 let schema_uri = Self::resource_type_to_schema_uri(resource_type)
327 .ok_or_else(|| ValidationError::custom("Unknown resource type"))?;
328
329 let schema = self
331 .get_schema(schema_uri)
332 .ok_or_else(|| ValidationError::custom("Schema not found"))?;
333
334 if let Some(emails_value) = obj.get("emails") {
336 if let Some(emails_array) = emails_value.as_array() {
337 if let Some(emails_attr) =
339 schema.attributes.iter().find(|attr| attr.name == "emails")
340 {
341 self.validate_required_sub_attributes(emails_attr, emails_array)?;
342 }
343 }
344 }
345
346 Ok(())
350 }
351
352 fn validate_schema_combinations(&self, schemas: &[SchemaUri]) -> ValidationResult<()> {
354 if schemas.is_empty() {
355 return Err(ValidationError::MissingSchemas);
356 }
357
358 let schema_strings: Vec<String> = schemas.iter().map(|s| s.as_str().to_string()).collect();
360
361 let has_user_schema = schema_strings.iter().any(|s| s.contains("User"));
363 let has_group_schema = schema_strings.iter().any(|s| s.contains("Group"));
364
365 if !has_user_schema && !has_group_schema {
366 return Err(ValidationError::custom(
367 "Resource must have at least one core schema",
368 ));
369 }
370
371 if has_user_schema && has_group_schema {
373 return Err(ValidationError::custom(
374 "Resource cannot have both User and Group schemas",
375 ));
376 }
377
378 Ok(())
379 }
380
381 fn validate_attribute(
383 &self,
384 attr_def: &AttributeDefinition,
385 value: &Value,
386 ) -> ValidationResult<()> {
387 if attr_def.required && value.is_null() {
389 return Err(ValidationError::MissingRequiredAttribute {
390 attribute: attr_def.name.clone(),
391 });
392 }
393
394 if value.is_null() {
396 return Ok(());
397 }
398
399 self.validate_attribute_value(attr_def, value)?;
401
402 Ok(())
409 }
410
411 fn validate_attribute_value(
413 &self,
414 attr_def: &AttributeDefinition,
415 value: &Value,
416 ) -> ValidationResult<()> {
417 self.validate_attribute_value_with_context(attr_def, value, None)
418 }
419
420 fn validate_attribute_value_with_context(
421 &self,
422 attr_def: &AttributeDefinition,
423 value: &Value,
424 parent_attr: Option<&str>,
425 ) -> ValidationResult<()> {
426 if value.is_null() && !attr_def.required {
428 return Ok(());
429 }
430
431 if value.is_null() && attr_def.required {
433 return Err(ValidationError::MissingRequiredAttribute {
434 attribute: attr_def.name.clone(),
435 });
436 }
437
438 match attr_def.data_type {
439 AttributeType::String => {
440 if !value.is_string() {
441 return Err(ValidationError::InvalidAttributeType {
442 attribute: attr_def.name.clone(),
443 expected: "string".to_string(),
444 actual: Self::get_value_type(value).to_string(),
445 });
446 }
447
448 let str_value = value.as_str().unwrap();
450 if attr_def.case_exact {
451 self.validate_case_exact_string(&attr_def.name, str_value)?;
453 }
454
455 if !attr_def.canonical_values.is_empty() {
457 self.validate_canonical_value_with_context(attr_def, str_value, parent_attr)?;
458 }
459 }
460 AttributeType::Boolean => {
461 if !value.is_boolean() {
462 return Err(ValidationError::InvalidAttributeType {
463 attribute: attr_def.name.clone(),
464 expected: "boolean".to_string(),
465 actual: Self::get_value_type(value).to_string(),
466 });
467 }
468 }
469 AttributeType::Decimal => {
470 if !value.is_number() {
471 return Err(ValidationError::InvalidAttributeType {
472 attribute: attr_def.name.clone(),
473 expected: "decimal".to_string(),
474 actual: Self::get_value_type(value).to_string(),
475 });
476 }
477 }
478 AttributeType::Integer => {
479 if !value.is_i64() {
480 return Err(ValidationError::InvalidAttributeType {
481 attribute: attr_def.name.clone(),
482 expected: "integer".to_string(),
483 actual: Self::get_value_type(value).to_string(),
484 });
485 }
486 }
487 AttributeType::DateTime => {
488 if let Some(date_str) = value.as_str() {
489 if !self.is_valid_datetime_format(date_str) {
490 return Err(ValidationError::InvalidDateTimeFormat {
491 attribute: attr_def.name.clone(),
492 value: date_str.to_string(),
493 });
494 }
495 } else {
496 return Err(ValidationError::InvalidAttributeType {
497 attribute: attr_def.name.clone(),
498 expected: "string (datetime)".to_string(),
499 actual: Self::get_value_type(value).to_string(),
500 });
501 }
502 }
503 AttributeType::Binary => {
504 if let Some(binary_str) = value.as_str() {
505 if !self.is_valid_base64(binary_str) {
506 return Err(ValidationError::InvalidBinaryData {
507 attribute: attr_def.name.clone(),
508 details: "Invalid base64 encoding".to_string(),
509 });
510 }
511 } else {
512 return Err(ValidationError::InvalidAttributeType {
513 attribute: attr_def.name.clone(),
514 expected: "string (base64)".to_string(),
515 actual: Self::get_value_type(value).to_string(),
516 });
517 }
518 }
519 AttributeType::Reference => {
520 if let Some(ref_str) = value.as_str() {
521 if !self.is_valid_uri_format(ref_str) {
522 return Err(ValidationError::InvalidReferenceUri {
523 attribute: attr_def.name.clone(),
524 uri: ref_str.to_string(),
525 });
526 }
527 } else {
528 return Err(ValidationError::InvalidAttributeType {
529 attribute: attr_def.name.clone(),
530 expected: "string (URI)".to_string(),
531 actual: Self::get_value_type(value).to_string(),
532 });
533 }
534 }
535 AttributeType::Complex => {
536 if value.is_array() {
537 self.validate_multi_valued_array(attr_def, value)?;
539 } else if value.is_object() {
540 self.validate_complex_attribute_structure(attr_def, value)?;
542 } else {
543 return Err(ValidationError::InvalidAttributeType {
544 attribute: attr_def.name.clone(),
545 expected: "object or array".to_string(),
546 actual: Self::get_value_type(value).to_string(),
547 });
548 }
549 }
550 }
551
552 Ok(())
553 }
554
555 fn validate_multi_valued_attributes(
557 &self,
558 attributes: &Map<String, Value>,
559 ) -> ValidationResult<()> {
560 for (attr_name, attr_value) in attributes {
561 if let Some(_attr_value_array) = attr_value.as_array() {
562 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
564 if attr_def.multi_valued {
565 self.validate_multi_valued_array(attr_def, attr_value)?;
566 }
567 }
568 }
569 }
570 Ok(())
571 }
572
573 fn validate_multi_valued_array(
575 &self,
576 attr_def: &AttributeDefinition,
577 value: &Value,
578 ) -> ValidationResult<()> {
579 let array = value
580 .as_array()
581 .ok_or_else(|| ValidationError::InvalidAttributeType {
582 attribute: attr_def.name.clone(),
583 expected: "array".to_string(),
584 actual: Self::get_value_type(value).to_string(),
585 })?;
586
587 for item in array {
588 match attr_def.data_type {
589 AttributeType::Complex => {
590 self.validate_complex_attribute_structure(attr_def, item)?;
591 }
592 _ => {
593 self.validate_attribute_value(attr_def, item)?;
594 }
595 }
596 }
597
598 if matches!(attr_def.data_type, AttributeType::Complex) {
600 self.validate_required_sub_attributes(attr_def, array)?;
601
602 let primary_count = array
604 .iter()
605 .filter(|item| {
606 item.get("primary")
607 .and_then(|p| p.as_bool())
608 .unwrap_or(false)
609 })
610 .count();
611
612 if primary_count > 1 {
613 return Err(ValidationError::MultiplePrimaryValues {
614 attribute: attr_def.name.clone(),
615 });
616 }
617 }
618
619 Ok(())
620 }
621
622 fn validate_required_sub_attributes(
624 &self,
625 attr_def: &AttributeDefinition,
626 array: &[Value],
627 ) -> ValidationResult<()> {
628 for item in array {
629 if let Some(obj) = item.as_object() {
630 for sub_attr in &attr_def.sub_attributes {
631 if sub_attr.required && !obj.contains_key(&sub_attr.name) {
632 return Err(ValidationError::MissingRequiredSubAttribute {
633 attribute: attr_def.name.clone(),
634 sub_attribute: sub_attr.name.clone(),
635 });
636 }
637 }
638 }
639 }
640 Ok(())
641 }
642
643 fn validate_complex_attributes(&self, attributes: &Map<String, Value>) -> ValidationResult<()> {
645 for (attr_name, attr_value) in attributes {
646 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
647 if matches!(attr_def.data_type, AttributeType::Complex) {
648 if attr_def.multi_valued {
649 continue;
651 } else {
652 self.validate_complex_attribute_structure(attr_def, attr_value)?;
654 }
655 }
656 }
657 }
658 Ok(())
659 }
660
661 fn validate_complex_attribute_structure(
663 &self,
664 attr_def: &AttributeDefinition,
665 value: &Value,
666 ) -> ValidationResult<()> {
667 let obj = value
668 .as_object()
669 .ok_or_else(|| ValidationError::InvalidAttributeType {
670 attribute: attr_def.name.clone(),
671 expected: "object".to_string(),
672 actual: Self::get_value_type(value).to_string(),
673 })?;
674
675 self.validate_known_sub_attributes(attr_def, obj)?;
677
678 self.validate_sub_attribute_types(attr_def, obj)?;
680
681 self.validate_no_nested_complex(attr_def, obj)?;
683
684 self.validate_required_sub_attributes_complex(attr_def, obj)?;
686
687 Ok(())
688 }
689
690 fn validate_known_sub_attributes(
692 &self,
693 attr_def: &AttributeDefinition,
694 obj: &Map<String, Value>,
695 ) -> ValidationResult<()> {
696 let known_sub_attrs: Vec<&str> = attr_def
697 .sub_attributes
698 .iter()
699 .map(|sa| sa.name.as_str())
700 .collect();
701
702 for key in obj.keys() {
703 if !known_sub_attrs.contains(&key.as_str()) {
704 return Err(ValidationError::UnknownSubAttribute {
705 attribute: attr_def.name.clone(),
706 sub_attribute: key.clone(),
707 });
708 }
709 }
710
711 Ok(())
712 }
713
714 fn validate_sub_attribute_types(
716 &self,
717 attr_def: &AttributeDefinition,
718 obj: &Map<String, Value>,
719 ) -> ValidationResult<()> {
720 for (key, value) in obj {
721 if let Some(sub_attr_def) = attr_def.sub_attributes.iter().find(|sa| sa.name == *key) {
722 self.validate_attribute_value_with_context(
723 sub_attr_def,
724 value,
725 Some(&attr_def.name),
726 )?;
727 }
728 }
729 Ok(())
730 }
731
732 fn validate_no_nested_complex(
734 &self,
735 attr_def: &AttributeDefinition,
736 _obj: &Map<String, Value>,
737 ) -> ValidationResult<()> {
738 for sub_attr in &attr_def.sub_attributes {
739 if matches!(sub_attr.data_type, AttributeType::Complex) {
740 return Err(ValidationError::NestedComplexAttributes {
741 attribute: format!("{}.{}", attr_def.name, sub_attr.name),
742 });
743 }
744 }
745 Ok(())
746 }
747
748 fn validate_required_sub_attributes_complex(
750 &self,
751 attr_def: &AttributeDefinition,
752 obj: &Map<String, Value>,
753 ) -> ValidationResult<()> {
754 for sub_attr in &attr_def.sub_attributes {
755 if sub_attr.required && !obj.contains_key(&sub_attr.name) {
756 return Err(ValidationError::MissingRequiredSubAttribute {
757 attribute: attr_def.name.clone(),
758 sub_attribute: sub_attr.name.clone(),
759 });
760 }
761 }
762 Ok(())
763 }
764
765 fn validate_attribute_characteristics(
767 &self,
768 attributes: &Map<String, Value>,
769 ) -> ValidationResult<()> {
770 for (attr_name, attr_value) in attributes {
774 self.validate_case_sensitivity(attr_name, attr_value)?;
776
777 }
779
780 Ok(())
781 }
782
783 fn validate_case_sensitivity(
785 &self,
786 attr_name: &str,
787 attr_value: &Value,
788 ) -> ValidationResult<()> {
789 if attr_name == "resourceType" && !attr_value.is_string() {
791 return Err(ValidationError::InvalidMetaStructure);
792 }
793
794 if let Some(attr_def) = self
796 .get_user_schema()
797 .attributes
798 .iter()
799 .find(|attr| attr.name == attr_name)
800 {
801 if attr_def.case_exact && attr_value.is_string() {
802 let str_value = attr_value.as_str().unwrap();
803 self.validate_case_exact_string(attr_name, str_value)?;
804 }
805 }
806
807 if attr_value.is_array() {
809 if let Some(array) = attr_value.as_array() {
810 for item in array {
811 self.validate_complex_case_sensitivity(attr_name, item)?;
812 }
813 }
814 }
815
816 Ok(())
817 }
818
819 fn validate_case_exact_string(&self, attr_name: &str, value: &str) -> ValidationResult<()> {
821 if attr_name == "resourceType" {
823 let allowed_types = ["User", "Group"];
824 if !allowed_types.contains(&value) {
825 return Err(ValidationError::InvalidResourceType {
826 resource_type: value.to_string(),
827 });
828 }
829 return Ok(());
830 }
831
832 if self.has_inconsistent_casing(value) {
835 return Err(ValidationError::CaseSensitivityViolation {
836 attribute: attr_name.to_string(),
837 details: format!(
838 "Attribute '{}' requires consistent casing but found mixed case in '{}'",
839 attr_name, value
840 ),
841 });
842 }
843 Ok(())
844 }
845
846 fn has_inconsistent_casing(&self, value: &str) -> bool {
848 if value.len() > 1 {
851 let has_upper = value.chars().any(|c| c.is_uppercase());
852 let has_lower = value.chars().any(|c| c.is_lowercase());
853
854 if has_upper && has_lower {
856 let first_char = value.chars().next().unwrap();
859 let rest = &value[1..];
860
861 if first_char.is_uppercase()
864 && rest.chars().any(|c| c.is_uppercase())
865 && rest.chars().any(|c| c.is_lowercase())
866 {
867 return true;
868 }
869 }
870 }
871 false
872 }
873
874 fn validate_canonical_value_with_context(
877 &self,
878 attr_def: &AttributeDefinition,
879 value: &str,
880 parent_attr: Option<&str>,
881 ) -> ValidationResult<()> {
882 if !attr_def.canonical_values.contains(&value.to_string()) {
887 let attribute_name = if let Some(parent) = parent_attr {
888 format!("{}.{}", parent, attr_def.name)
889 } else {
890 attr_def.name.clone()
891 };
892
893 return Err(ValidationError::InvalidCanonicalValue {
894 attribute: attribute_name,
895 value: value.to_string(),
896 allowed: attr_def.canonical_values.clone(),
897 });
898 }
899 Ok(())
900 }
901
902 fn validate_complex_case_sensitivity(
904 &self,
905 attr_name: &str,
906 value: &Value,
907 ) -> ValidationResult<()> {
908 if let Some(obj) = value.as_object() {
909 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
910 for (sub_attr_name, sub_attr_value) in obj {
911 if let Some(sub_attr_def) = attr_def
912 .sub_attributes
913 .iter()
914 .find(|sa| sa.name == *sub_attr_name)
915 {
916 if !sub_attr_def.case_exact && sub_attr_value.is_string() {
917 }
920 }
921 }
922 }
923 }
924 Ok(())
925 }
926
927 pub fn validate_resource(
932 &self,
933 schema: &super::types::Schema,
934 resource: &Value,
935 ) -> ValidationResult<()> {
936 let obj = resource
937 .as_object()
938 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
939
940 for attr_def in &schema.attributes {
942 if let Some(value) = obj.get(&attr_def.name) {
943 self.validate_attribute(attr_def, value)?;
944 } else if attr_def.required {
945 return Err(ValidationError::MissingRequiredAttribute {
946 attribute: attr_def.name.clone(),
947 });
948 }
949 }
950
951 for (field_name, _) in obj {
953 if !schema
954 .attributes
955 .iter()
956 .any(|attr| attr.name == *field_name)
957 {
958 if !["schemas", "id", "externalId", "meta"].contains(&field_name.as_str()) {
960 return Err(ValidationError::UnknownAttributeForSchema {
961 attribute: field_name.clone(),
962 schema: schema.id.clone(),
963 });
964 }
965 }
966 }
967
968 Ok(())
969 }
970}