scim-server 0.4.0

A comprehensive SCIM 2.0 server library for Rust with multi-tenant support and type-safe operations
Documentation
//! Multi-tenant SCIM server capabilities.
//!
//! This module provides the core infrastructure for multi-tenant SCIM operations,
//! including tenant resolution, multi-tenant resource providers, and tenant-aware
//! error handling.
//!
//! # Architecture
//!
//! The multi-tenant system is built around several key concepts:
//!
//! * **Tenant Resolution**: Mapping authentication credentials to tenant contexts
//! * **Multi-Tenant Providers**: Resource providers that understand tenant isolation
//! * **Enhanced Context**: Request contexts that carry tenant information
//! * **Isolation Levels**: Different levels of tenant data separation
//!
//! # Example Usage
//!
//! ```rust,no_run
//! use scim_server::multi_tenant::{
//!     TenantValidator, TenantResolver, StaticTenantResolver
//! };
//! use scim_server::{TenantContext, RequestContext, Resource, ResourceProvider};
//! use serde_json::json;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Set up tenant resolver
//!     let resolver = StaticTenantResolver::new();
//!     resolver.add_tenant("api-key-123", TenantContext::new(
//!         "tenant-a".to_string(),
//!         "client-a".to_string()
//!     )).await;
//!
//!     // Use in multi-tenant operations
//!     let tenant_context = resolver.resolve_tenant("api-key-123").await?;
//!     let context = RequestContext::with_tenant_generated_id(tenant_context);
//!
//!     let user_data = json!({
//!         "userName": "john.doe",
//!         "displayName": "John Doe"
//!     });
//!
//!     Ok(())
//! }
//! ```

pub mod adapter;

pub mod provider;
pub mod resolver;
pub mod scim_config;

// Re-export key types for convenience
pub use adapter::{SingleTenantAdapter, ToSingleTenant};

// SCIM-focused configuration (recommended)
pub use scim_config::{
    RateLimit, ScimAuditConfig, ScimAuthScheme, ScimClientAuth, ScimClientConfig,
    ScimConfigurationError, ScimCustomAttribute, ScimEndpointConfig, ScimOperation, ScimRateLimits,
    ScimSchemaConfig, ScimSchemaExtension, ScimSearchConfig, ScimTenantConfiguration,
};

pub use provider::TenantValidator;
pub use resolver::{StaticTenantResolver, StaticTenantResolverBuilder, TenantResolver};

// Re-export core types from resource module
pub use crate::resource::{IsolationLevel, TenantContext, TenantPermissions};

#[cfg(test)]
mod tests {
    use super::*;
    use crate::resource::RequestContext;

    #[test]
    fn test_module_exports() {
        // Test that all expected types are accessible
        let _resolver = StaticTenantResolver::new();

        // Test core type re-exports
        let tenant_context = TenantContext::new("test".to_string(), "client".to_string());
        let _context = RequestContext::with_tenant_generated_id(tenant_context);
    }

    #[test]
    fn test_isolation_levels() {
        assert_eq!(IsolationLevel::default(), IsolationLevel::Standard);

        let strict = IsolationLevel::Strict;
        let standard = IsolationLevel::Standard;
        let shared = IsolationLevel::Shared;

        assert_ne!(strict, standard);
        assert_ne!(standard, shared);
        assert_ne!(strict, shared);
    }

    #[test]
    fn test_tenant_permissions() {
        let default_perms = TenantPermissions::default();
        assert!(default_perms.can_create);
        assert!(default_perms.can_read);
        assert!(default_perms.can_update);
        assert!(default_perms.can_delete);
        assert!(default_perms.can_list);
        assert!(default_perms.max_users.is_none());
        assert!(default_perms.max_groups.is_none());
    }

    #[test]
    fn test_tenant_context_operations() {
        let context = TenantContext::new("test-tenant".to_string(), "test-client".to_string())
            .with_isolation_level(IsolationLevel::Strict);

        assert_eq!(context.tenant_id, "test-tenant");
        assert_eq!(context.client_id, "test-client");
        assert_eq!(context.isolation_level, IsolationLevel::Strict);

        assert!(context.can_perform_operation("create"));
        assert!(context.can_perform_operation("read"));
        assert!(!context.can_perform_operation("invalid"));

        assert!(context.check_user_limit(100));
        assert!(context.check_group_limit(50));
    }

    #[test]
    fn test_tenant_context_with_limits() {
        let mut permissions = TenantPermissions::default();
        permissions.max_users = Some(10);
        permissions.max_groups = Some(5);

        let context = TenantContext::new("test".to_string(), "client".to_string())
            .with_permissions(permissions);

        assert!(context.check_user_limit(5));
        assert!(!context.check_user_limit(10));
        assert!(context.check_group_limit(3));
        assert!(!context.check_group_limit(5));
    }

    #[test]
    fn test_request_context_usage() {
        let tenant_context = TenantContext::new("test".to_string(), "client".to_string());
        let context = RequestContext::with_tenant_generated_id(tenant_context.clone());

        assert_eq!(context.tenant_id(), Some("test"));
        assert_eq!(context.client_id(), Some("client"));
        assert!(context.is_multi_tenant());
        assert!(context.can_perform_operation("read"));
        assert!(context.validate_operation("create").is_ok());
    }

    #[test]
    fn test_single_tenant_context() {
        let context = RequestContext::new("req-123".to_string());
        assert_eq!(context.request_id, "req-123");
        assert!(!context.is_multi_tenant());
        assert_eq!(context.tenant_id(), None);
    }
}