1use super::registry::SchemaRegistry;
9use super::types::{AttributeDefinition, AttributeType, Uniqueness};
10use crate::error::{ValidationError, ValidationResult};
11use crate::providers::ResourceProvider;
12use crate::resource::value_objects::SchemaUri;
13use crate::resource::{RequestContext, Resource};
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 value_str = match value {
108 serde_json::Value::String(s) => s.as_str(),
109 _ => {
110 return Err(ValidationError::Custom {
111 message: format!(
112 "Attribute '{}' must be a string for uniqueness validation",
113 attr.name
114 ),
115 });
116 }
117 };
118
119 let existing = provider
120 .find_resources_by_attribute(
121 resource_type,
122 &attr.name,
123 value_str,
124 request_context,
125 )
126 .await
127 .map_err(|e| ValidationError::Custom {
128 message: format!("Failed to check uniqueness: {}", e),
129 })?;
130
131 if !existing.is_empty() {
133 let existing_resource = &existing[0];
134 let is_same_resource = exclude_id
136 .map(|current_id| {
137 existing_resource
138 .resource()
139 .id
140 .as_ref()
141 .map(|id| id.as_str())
142 == Some(current_id)
143 })
144 .unwrap_or(false);
145
146 if !is_same_resource {
147 return Err(ValidationError::ServerUniquenessViolation {
148 attribute: attr.name.clone(),
149 value: value.to_string(),
150 });
151 }
152 }
153 }
154 }
155 }
156
157 Ok(())
158 }
159
160 pub fn validate_resource_hybrid(&self, resource: &Resource) -> ValidationResult<()> {
166 for schema_uri in &resource.schemas {
171 if let Some(schema) = self.get_schema_by_id(schema_uri.as_str()) {
172 self.validate_against_schema(resource, schema)?;
173 } else {
174 return Err(ValidationError::UnknownSchemaUri {
175 uri: schema_uri.as_str().to_string(),
176 });
177 }
178 }
179
180 self.validate_schema_combinations(&resource.schemas)?;
182
183 self.validate_multi_valued_attributes(&resource.attributes)?;
185
186 self.validate_complex_attributes(&resource.attributes)?;
188
189 self.validate_attribute_characteristics(&resource.attributes)?;
191
192 Ok(())
193 }
194
195 fn validate_against_schema(
197 &self,
198 resource: &Resource,
199 schema: &super::types::Schema,
200 ) -> ValidationResult<()> {
201 let resource_json = resource.to_json()?;
203
204 self.validate_resource(schema, &resource_json)
206 }
207
208 pub fn validate_json_resource_with_context(
227 &self,
228 resource_type: &str,
229 resource_json: &Value,
230 context: OperationContext,
231 ) -> ValidationResult<()> {
232 if let Some(schemas_value) = resource_json.get("schemas") {
234 if let Some(schemas_array) = schemas_value.as_array() {
235 if schemas_array.is_empty() {
236 return Err(ValidationError::EmptySchemas);
237 }
238
239 let mut seen_schemas = std::collections::HashSet::new();
241 for schema_value in schemas_array {
242 if let Some(schema_uri) = schema_value.as_str() {
243 if !seen_schemas.insert(schema_uri) {
244 return Err(ValidationError::DuplicateSchemaUri {
245 uri: schema_uri.to_string(),
246 });
247 }
248 }
249 }
250 } else {
251 return Err(ValidationError::MissingSchemas);
252 }
253 } else {
254 return Err(ValidationError::MissingSchemas);
255 }
256
257 if let Some(meta_value) = resource_json.get("meta") {
259 if let Some(meta_obj) = meta_value.as_object() {
260 if !meta_obj.contains_key("resourceType") {
261 return Err(ValidationError::MissingResourceType);
262 }
263 }
264 }
265
266 match context {
268 OperationContext::Create => {
269 if resource_json
271 .as_object()
272 .map(|obj| obj.contains_key("id"))
273 .unwrap_or(false)
274 {
275 return Err(ValidationError::ClientProvidedId);
276 }
277 }
278 OperationContext::Update | OperationContext::Patch => {
279 if !resource_json
281 .as_object()
282 .map(|obj| obj.contains_key("id"))
283 .unwrap_or(false)
284 {
285 return Err(ValidationError::MissingId);
286 }
287 }
288 }
289
290 if let Some(meta_value) = resource_json.get("meta") {
292 if let Some(meta_obj) = meta_value.as_object() {
293 let readonly_fields = ["created", "lastModified", "location", "version"];
294 let has_readonly = readonly_fields
295 .iter()
296 .any(|field| meta_obj.contains_key(*field));
297
298 if has_readonly {
299 match context {
300 OperationContext::Create => {
301 return Err(ValidationError::ClientProvidedMeta);
303 }
304 OperationContext::Update | OperationContext::Patch => {
305 }
308 }
309 }
310 }
311 }
312
313 self.validate_multi_valued_attributes_preliminary(resource_type, resource_json)?;
315
316 let resource = Resource::from_json(resource_type.to_string(), resource_json.clone())?;
318
319 self.validate_resource_hybrid(&resource)
321 }
322
323 fn resource_type_to_schema_uri(resource_type: &str) -> Option<&'static str> {
325 match resource_type {
326 "User" => Some("urn:ietf:params:scim:schemas:core:2.0:User"),
327 "Group" => Some("urn:ietf:params:scim:schemas:core:2.0:Group"),
328 _ => None,
329 }
330 }
331
332 fn validate_multi_valued_attributes_preliminary(
335 &self,
336 resource_type: &str,
337 resource_json: &Value,
338 ) -> ValidationResult<()> {
339 let obj = resource_json
340 .as_object()
341 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
342
343 let schema_uri = Self::resource_type_to_schema_uri(resource_type)
345 .ok_or_else(|| ValidationError::custom("Unknown resource type"))?;
346
347 let schema = self
349 .get_schema(schema_uri)
350 .ok_or_else(|| ValidationError::custom("Schema not found"))?;
351
352 if let Some(emails_value) = obj.get("emails") {
354 if let Some(emails_array) = emails_value.as_array() {
355 if let Some(emails_attr) =
357 schema.attributes.iter().find(|attr| attr.name == "emails")
358 {
359 self.validate_required_sub_attributes(emails_attr, emails_array)?;
360 }
361 }
362 }
363
364 Ok(())
368 }
369
370 fn validate_schema_combinations(&self, schemas: &[SchemaUri]) -> ValidationResult<()> {
372 if schemas.is_empty() {
373 return Err(ValidationError::MissingSchemas);
374 }
375
376 let schema_strings: Vec<String> = schemas.iter().map(|s| s.as_str().to_string()).collect();
378
379 let has_user_schema = schema_strings.iter().any(|s| s.contains("User"));
381 let has_group_schema = schema_strings.iter().any(|s| s.contains("Group"));
382
383 if !has_user_schema && !has_group_schema {
384 return Err(ValidationError::custom(
385 "Resource must have at least one core schema",
386 ));
387 }
388
389 if has_user_schema && has_group_schema {
391 return Err(ValidationError::custom(
392 "Resource cannot have both User and Group schemas",
393 ));
394 }
395
396 Ok(())
397 }
398
399 fn validate_attribute(
401 &self,
402 attr_def: &AttributeDefinition,
403 value: &Value,
404 ) -> ValidationResult<()> {
405 if attr_def.required && value.is_null() {
407 return Err(ValidationError::MissingRequiredAttribute {
408 attribute: attr_def.name.clone(),
409 });
410 }
411
412 if value.is_null() {
414 return Ok(());
415 }
416
417 self.validate_attribute_value(attr_def, value)?;
419
420 Ok(())
427 }
428
429 fn validate_attribute_value(
431 &self,
432 attr_def: &AttributeDefinition,
433 value: &Value,
434 ) -> ValidationResult<()> {
435 self.validate_attribute_value_with_context(attr_def, value, None)
436 }
437
438 fn validate_attribute_value_with_context(
439 &self,
440 attr_def: &AttributeDefinition,
441 value: &Value,
442 parent_attr: Option<&str>,
443 ) -> ValidationResult<()> {
444 if value.is_null() && !attr_def.required {
446 return Ok(());
447 }
448
449 if value.is_null() && attr_def.required {
451 return Err(ValidationError::MissingRequiredAttribute {
452 attribute: attr_def.name.clone(),
453 });
454 }
455
456 match attr_def.data_type {
457 AttributeType::String => {
458 if !value.is_string() {
459 return Err(ValidationError::InvalidAttributeType {
460 attribute: attr_def.name.clone(),
461 expected: "string".to_string(),
462 actual: Self::get_value_type(value).to_string(),
463 });
464 }
465
466 let str_value = value.as_str().unwrap();
468 if attr_def.case_exact {
469 self.validate_case_exact_string(&attr_def.name, str_value)?;
471 }
472
473 if !attr_def.canonical_values.is_empty() {
475 self.validate_canonical_value_with_context(attr_def, str_value, parent_attr)?;
476 }
477 }
478 AttributeType::Boolean => {
479 if !value.is_boolean() {
480 return Err(ValidationError::InvalidAttributeType {
481 attribute: attr_def.name.clone(),
482 expected: "boolean".to_string(),
483 actual: Self::get_value_type(value).to_string(),
484 });
485 }
486 }
487 AttributeType::Decimal => {
488 if !value.is_number() {
489 return Err(ValidationError::InvalidAttributeType {
490 attribute: attr_def.name.clone(),
491 expected: "decimal".to_string(),
492 actual: Self::get_value_type(value).to_string(),
493 });
494 }
495 }
496 AttributeType::Integer => {
497 if !value.is_i64() {
498 return Err(ValidationError::InvalidAttributeType {
499 attribute: attr_def.name.clone(),
500 expected: "integer".to_string(),
501 actual: Self::get_value_type(value).to_string(),
502 });
503 }
504 }
505 AttributeType::DateTime => {
506 if let Some(date_str) = value.as_str() {
507 if !self.is_valid_datetime_format(date_str) {
508 return Err(ValidationError::InvalidDateTimeFormat {
509 attribute: attr_def.name.clone(),
510 value: date_str.to_string(),
511 });
512 }
513 } else {
514 return Err(ValidationError::InvalidAttributeType {
515 attribute: attr_def.name.clone(),
516 expected: "string (datetime)".to_string(),
517 actual: Self::get_value_type(value).to_string(),
518 });
519 }
520 }
521 AttributeType::Binary => {
522 if let Some(binary_str) = value.as_str() {
523 if !self.is_valid_base64(binary_str) {
524 return Err(ValidationError::InvalidBinaryData {
525 attribute: attr_def.name.clone(),
526 details: "Invalid base64 encoding".to_string(),
527 });
528 }
529 } else {
530 return Err(ValidationError::InvalidAttributeType {
531 attribute: attr_def.name.clone(),
532 expected: "string (base64)".to_string(),
533 actual: Self::get_value_type(value).to_string(),
534 });
535 }
536 }
537 AttributeType::Reference => {
538 if let Some(ref_str) = value.as_str() {
539 if !self.is_valid_uri_format(ref_str) {
540 return Err(ValidationError::InvalidReferenceUri {
541 attribute: attr_def.name.clone(),
542 uri: ref_str.to_string(),
543 });
544 }
545 } else {
546 return Err(ValidationError::InvalidAttributeType {
547 attribute: attr_def.name.clone(),
548 expected: "string (URI)".to_string(),
549 actual: Self::get_value_type(value).to_string(),
550 });
551 }
552 }
553 AttributeType::Complex => {
554 if value.is_array() {
555 self.validate_multi_valued_array(attr_def, value)?;
557 } else if value.is_object() {
558 self.validate_complex_attribute_structure(attr_def, value)?;
560 } else {
561 return Err(ValidationError::InvalidAttributeType {
562 attribute: attr_def.name.clone(),
563 expected: "object or array".to_string(),
564 actual: Self::get_value_type(value).to_string(),
565 });
566 }
567 }
568 }
569
570 Ok(())
571 }
572
573 fn validate_multi_valued_attributes(
575 &self,
576 attributes: &Map<String, Value>,
577 ) -> ValidationResult<()> {
578 for (attr_name, attr_value) in attributes {
579 if let Some(_attr_value_array) = attr_value.as_array() {
580 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
582 if attr_def.multi_valued {
583 self.validate_multi_valued_array(attr_def, attr_value)?;
584 }
585 }
586 }
587 }
588 Ok(())
589 }
590
591 fn validate_multi_valued_array(
593 &self,
594 attr_def: &AttributeDefinition,
595 value: &Value,
596 ) -> ValidationResult<()> {
597 let array = value
598 .as_array()
599 .ok_or_else(|| ValidationError::InvalidAttributeType {
600 attribute: attr_def.name.clone(),
601 expected: "array".to_string(),
602 actual: Self::get_value_type(value).to_string(),
603 })?;
604
605 for item in array {
606 match attr_def.data_type {
607 AttributeType::Complex => {
608 self.validate_complex_attribute_structure(attr_def, item)?;
609 }
610 _ => {
611 self.validate_attribute_value(attr_def, item)?;
612 }
613 }
614 }
615
616 if matches!(attr_def.data_type, AttributeType::Complex) {
618 self.validate_required_sub_attributes(attr_def, array)?;
619
620 let primary_count = array
622 .iter()
623 .filter(|item| {
624 item.get("primary")
625 .and_then(|p| p.as_bool())
626 .unwrap_or(false)
627 })
628 .count();
629
630 if primary_count > 1 {
631 return Err(ValidationError::MultiplePrimaryValues {
632 attribute: attr_def.name.clone(),
633 });
634 }
635 }
636
637 Ok(())
638 }
639
640 fn validate_required_sub_attributes(
642 &self,
643 attr_def: &AttributeDefinition,
644 array: &[Value],
645 ) -> ValidationResult<()> {
646 for item in array {
647 if let Some(obj) = item.as_object() {
648 for sub_attr in &attr_def.sub_attributes {
649 if sub_attr.required && !obj.contains_key(&sub_attr.name) {
650 return Err(ValidationError::MissingRequiredSubAttribute {
651 attribute: attr_def.name.clone(),
652 sub_attribute: sub_attr.name.clone(),
653 });
654 }
655 }
656 }
657 }
658 Ok(())
659 }
660
661 fn validate_complex_attributes(&self, attributes: &Map<String, Value>) -> ValidationResult<()> {
663 for (attr_name, attr_value) in attributes {
664 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
665 if matches!(attr_def.data_type, AttributeType::Complex) {
666 if attr_def.multi_valued {
667 continue;
669 } else {
670 self.validate_complex_attribute_structure(attr_def, attr_value)?;
672 }
673 }
674 }
675 }
676 Ok(())
677 }
678
679 fn validate_complex_attribute_structure(
681 &self,
682 attr_def: &AttributeDefinition,
683 value: &Value,
684 ) -> ValidationResult<()> {
685 let obj = value
686 .as_object()
687 .ok_or_else(|| ValidationError::InvalidAttributeType {
688 attribute: attr_def.name.clone(),
689 expected: "object".to_string(),
690 actual: Self::get_value_type(value).to_string(),
691 })?;
692
693 self.validate_known_sub_attributes(attr_def, obj)?;
695
696 self.validate_sub_attribute_types(attr_def, obj)?;
698
699 self.validate_no_nested_complex(attr_def, obj)?;
701
702 self.validate_required_sub_attributes_complex(attr_def, obj)?;
704
705 Ok(())
706 }
707
708 fn validate_known_sub_attributes(
710 &self,
711 attr_def: &AttributeDefinition,
712 obj: &Map<String, Value>,
713 ) -> ValidationResult<()> {
714 let known_sub_attrs: Vec<&str> = attr_def
715 .sub_attributes
716 .iter()
717 .map(|sa| sa.name.as_str())
718 .collect();
719
720 for key in obj.keys() {
721 if !known_sub_attrs.contains(&key.as_str()) {
722 return Err(ValidationError::UnknownSubAttribute {
723 attribute: attr_def.name.clone(),
724 sub_attribute: key.clone(),
725 });
726 }
727 }
728
729 Ok(())
730 }
731
732 fn validate_sub_attribute_types(
734 &self,
735 attr_def: &AttributeDefinition,
736 obj: &Map<String, Value>,
737 ) -> ValidationResult<()> {
738 for (key, value) in obj {
739 if let Some(sub_attr_def) = attr_def.sub_attributes.iter().find(|sa| sa.name == *key) {
740 self.validate_attribute_value_with_context(
741 sub_attr_def,
742 value,
743 Some(&attr_def.name),
744 )?;
745 }
746 }
747 Ok(())
748 }
749
750 fn validate_no_nested_complex(
752 &self,
753 attr_def: &AttributeDefinition,
754 _obj: &Map<String, Value>,
755 ) -> ValidationResult<()> {
756 for sub_attr in &attr_def.sub_attributes {
757 if matches!(sub_attr.data_type, AttributeType::Complex) {
758 return Err(ValidationError::NestedComplexAttributes {
759 attribute: format!("{}.{}", attr_def.name, sub_attr.name),
760 });
761 }
762 }
763 Ok(())
764 }
765
766 fn validate_required_sub_attributes_complex(
768 &self,
769 attr_def: &AttributeDefinition,
770 obj: &Map<String, Value>,
771 ) -> ValidationResult<()> {
772 for sub_attr in &attr_def.sub_attributes {
773 if sub_attr.required && !obj.contains_key(&sub_attr.name) {
774 return Err(ValidationError::MissingRequiredSubAttribute {
775 attribute: attr_def.name.clone(),
776 sub_attribute: sub_attr.name.clone(),
777 });
778 }
779 }
780 Ok(())
781 }
782
783 fn validate_attribute_characteristics(
785 &self,
786 attributes: &Map<String, Value>,
787 ) -> ValidationResult<()> {
788 for (attr_name, attr_value) in attributes {
792 self.validate_case_sensitivity(attr_name, attr_value)?;
794
795 }
797
798 Ok(())
799 }
800
801 fn validate_case_sensitivity(
803 &self,
804 attr_name: &str,
805 attr_value: &Value,
806 ) -> ValidationResult<()> {
807 if attr_name == "resourceType" && !attr_value.is_string() {
809 return Err(ValidationError::InvalidMetaStructure);
810 }
811
812 if let Some(attr_def) = self
814 .get_user_schema()
815 .attributes
816 .iter()
817 .find(|attr| attr.name == attr_name)
818 {
819 if attr_def.case_exact && attr_value.is_string() {
820 let str_value = attr_value.as_str().unwrap();
821 self.validate_case_exact_string(attr_name, str_value)?;
822 }
823 }
824
825 if attr_value.is_array() {
827 if let Some(array) = attr_value.as_array() {
828 for item in array {
829 self.validate_complex_case_sensitivity(attr_name, item)?;
830 }
831 }
832 }
833
834 Ok(())
835 }
836
837 fn validate_case_exact_string(&self, attr_name: &str, value: &str) -> ValidationResult<()> {
839 if attr_name == "resourceType" {
841 let allowed_types = ["User", "Group"];
842 if !allowed_types.contains(&value) {
843 return Err(ValidationError::InvalidResourceType {
844 resource_type: value.to_string(),
845 });
846 }
847 return Ok(());
848 }
849
850 if self.has_inconsistent_casing(value) {
853 return Err(ValidationError::CaseSensitivityViolation {
854 attribute: attr_name.to_string(),
855 details: format!(
856 "Attribute '{}' requires consistent casing but found mixed case in '{}'",
857 attr_name, value
858 ),
859 });
860 }
861 Ok(())
862 }
863
864 fn has_inconsistent_casing(&self, value: &str) -> bool {
866 if value.len() > 1 {
869 let has_upper = value.chars().any(|c| c.is_uppercase());
870 let has_lower = value.chars().any(|c| c.is_lowercase());
871
872 if has_upper && has_lower {
874 let first_char = value.chars().next().unwrap();
877 let rest = &value[1..];
878
879 if first_char.is_uppercase()
882 && rest.chars().any(|c| c.is_uppercase())
883 && rest.chars().any(|c| c.is_lowercase())
884 {
885 return true;
886 }
887 }
888 }
889 false
890 }
891
892 fn validate_canonical_value_with_context(
895 &self,
896 attr_def: &AttributeDefinition,
897 value: &str,
898 parent_attr: Option<&str>,
899 ) -> ValidationResult<()> {
900 if !attr_def.canonical_values.contains(&value.to_string()) {
905 let attribute_name = if let Some(parent) = parent_attr {
906 format!("{}.{}", parent, attr_def.name)
907 } else {
908 attr_def.name.clone()
909 };
910
911 return Err(ValidationError::InvalidCanonicalValue {
912 attribute: attribute_name,
913 value: value.to_string(),
914 allowed: attr_def.canonical_values.clone(),
915 });
916 }
917 Ok(())
918 }
919
920 fn validate_complex_case_sensitivity(
922 &self,
923 attr_name: &str,
924 value: &Value,
925 ) -> ValidationResult<()> {
926 if let Some(obj) = value.as_object() {
927 if let Some(attr_def) = self.get_complex_attribute_definition(attr_name) {
928 for (sub_attr_name, sub_attr_value) in obj {
929 if let Some(sub_attr_def) = attr_def
930 .sub_attributes
931 .iter()
932 .find(|sa| sa.name == *sub_attr_name)
933 {
934 if !sub_attr_def.case_exact && sub_attr_value.is_string() {
935 }
938 }
939 }
940 }
941 }
942 Ok(())
943 }
944
945 pub fn validate_resource(
950 &self,
951 schema: &super::types::Schema,
952 resource: &Value,
953 ) -> ValidationResult<()> {
954 let obj = resource
955 .as_object()
956 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
957
958 for attr_def in &schema.attributes {
960 if let Some(value) = obj.get(&attr_def.name) {
961 self.validate_attribute(attr_def, value)?;
962 } else if attr_def.required {
963 return Err(ValidationError::MissingRequiredAttribute {
964 attribute: attr_def.name.clone(),
965 });
966 }
967 }
968
969 for (field_name, _) in obj {
971 if !schema
972 .attributes
973 .iter()
974 .any(|attr| attr.name == *field_name)
975 {
976 if !["schemas", "id", "externalId", "meta"].contains(&field_name.as_str()) {
978 return Err(ValidationError::UnknownAttributeForSchema {
979 attribute: field_name.clone(),
980 schema: schema.id.clone(),
981 });
982 }
983 }
984 }
985
986 Ok(())
987 }
988}