Validation Overview
This section covers the validation architecture and concepts in the SCIM Server library. While the library provides comprehensive built-in validation based on SCIM schemas, you can extend it with custom validation for business rules, compliance requirements, and organization-specific constraints.
What is Custom Validation?
Custom validation in SCIM Server allows you to:
- Enforce business rules - Complex validation logic beyond schema constraints
- Implement compliance requirements - GDPR, HIPAA, or industry-specific rules
- Add organization-specific constraints - Custom attribute validation
- Integrate with external systems - Real-time validation against external APIs
- Implement cross-field validation - Dependencies between multiple attributes
Validation Architecture
Validation Pipeline
The SCIM Server validation pipeline processes requests in this order:
- Schema Validation - Built-in SCIM schema compliance
- Type Validation - Data type checking and format validation
- Custom Validation - Your business logic
- Storage Validation - Database constraints and uniqueness checks
#![allow(unused)] fn main() { use scim_server::validation::{ ValidationPipeline, ValidatorChain, SchemaValidator, CustomValidator, ValidationResult }; let validation_pipeline = ValidationPipeline::builder() .add_validator(SchemaValidator::new()) .add_validator(TypeValidator::new()) .add_validator(CustomBusinessRuleValidator::new()) .add_validator(ComplianceValidator::new()) .build(); }
Core Components
The validation system consists of several key components:
ValidationContext
Provides context information during validation:
#![allow(unused)] fn main() { pub struct ValidationContext { pub tenant_id: String, pub operation: Operation, pub resource_type: ResourceType, pub authenticated_user: Option<String>, pub client_info: ClientInfo, pub timestamp: DateTime<Utc>, } pub enum Operation { Create, Update, Patch, Delete, BulkCreate, BulkUpdate, } }
ValidationError
Represents validation failures with detailed information:
#![allow(unused)] fn main() { pub struct ValidationError { pub code: String, pub message: String, pub field_path: Option<String>, pub severity: ValidationSeverity, pub details: Option<serde_json::Value>, } impl ValidationError { pub fn new(code: &str, message: &str) -> Self { Self { code: code.to_string(), message: message.to_string(), field_path: None, severity: ValidationSeverity::Error, details: None, } } pub fn with_field(mut self, field_path: &str) -> Self { self.field_path = Some(field_path.to_string()); self } pub fn with_severity(mut self, severity: ValidationSeverity) -> Self { self.severity = severity; self } } }
Custom Validator Trait
Implement the CustomValidator trait for your validation logic:
#![allow(unused)] fn main() { use scim_server::validation::{CustomValidator, ValidationContext, ValidationError}; use scim_server::models::{User, Group}; use async_trait::async_trait; #[async_trait] pub trait CustomValidator: Send + Sync { /// Validate a user during creation or update async fn validate_user( &self, user: &User, context: &ValidationContext, ) -> Result<(), ValidationError>; /// Validate a group during creation or update async fn validate_group( &self, group: &Group, context: &ValidationContext, ) -> Result<(), ValidationError>; /// Validate patch operations before applying async fn validate_patch_operations( &self, resource_type: &str, resource_id: &str, operations: &[PatchOperation], context: &ValidationContext, ) -> Result<(), ValidationError>; /// Custom validation for batch operations (individual operations in sequence) async fn validate_batch_operation( &self, resource_type: &str, operation_type: &str, data: &serde_json::Value, context: &ValidationContext, ) -> Result<(), ValidationError> { // Default implementation - override if needed // Validate each operation individually since bulk operations aren't implemented match operation_type { "CREATE" => self.validate_create(resource_type, data, context).await, "UPDATE" => self.validate_update(resource_type, data, context).await, "PATCH" => { // For patch operations, extract patch operations from data if let Ok(operations) = serde_json::from_value::<Vec<PatchOperation>>(data.clone()) { self.validate_patch(resource_type, &operations, context).await } else { Err(ValidationError::InvalidData("Invalid patch operations".to_string())) } }, _ => Ok(()) } } } }
Validation Strategies
1. Synchronous vs Asynchronous
- Synchronous validation - Fast, local checks (regex, length, format)
- Asynchronous validation - External API calls, database lookups
2. Fail-Fast vs Collect-All
- Fail-Fast - Stop on first validation error
- Collect-All - Gather all validation errors before failing
3. Severity Levels
#![allow(unused)] fn main() { pub enum ValidationSeverity { Error, // Blocks the operation Warning, // Logs but allows operation Info, // Informational only } }
Integration Points
Server Configuration
Register validators during server startup:
#![allow(unused)] fn main() { use scim_server::ScimServerBuilder; let server = ScimServerBuilder::new() .with_provider(my_provider) .add_validator(BusinessRuleValidator::new()) .add_validator(ComplianceValidator::new()) .build(); }
Tenant-Specific Validation
Different validation rules per tenant:
#![allow(unused)] fn main() { pub struct TenantValidatorRegistry { validators: HashMap<String, Vec<Box<dyn CustomValidator>>>, } impl TenantValidatorRegistry { pub async fn validate_for_tenant( &self, tenant_id: &str, user: &User, context: &ValidationContext, ) -> Result<(), ValidationError> { if let Some(validators) = self.validators.get(tenant_id) { for validator in validators { validator.validate_user(user, context).await?; } } Ok(()) } } }
Error Handling
Validation Error Aggregation
#![allow(unused)] fn main() { pub struct ValidationResult { pub errors: Vec<ValidationError>, pub warnings: Vec<ValidationError>, } impl ValidationResult { pub fn is_valid(&self) -> bool { self.errors.is_empty() } pub fn add_error(&mut self, error: ValidationError) { match error.severity { ValidationSeverity::Error => self.errors.push(error), ValidationSeverity::Warning => self.warnings.push(error), ValidationSeverity::Info => { /* Log only */ } } } } }
Client Error Response
Validation errors are returned as SCIM-compliant error responses:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "400",
"scimType": "invalidValue",
"detail": "Validation failed",
"errors": [
{
"code": "INVALID_EMAIL_DOMAIN",
"message": "Email domain 'example.com' is not allowed",
"field": "emails[0].value"
}
]
}
Next Steps
- Basic Validation - Simple business rule validators
- Advanced Validation - External integrations and complex logic
- Field-Level Validation - Custom attribute validators
- Configuration - Configurable validation rules