Expand description
§SCIM Server Library for Rust
A comprehensive System for Cross-domain Identity Management (SCIM) server library that enables developers to implement SCIM-compliant identity providers with minimal effort.
§Overview
This library transforms SCIM from a complex enterprise integration challenge into a straightforward provider implementation task, allowing developers to focus on their core business logic while automatically gaining enterprise SSO and provisioning capabilities.
§Key Value Propositions
- For SaaS Developers: Add enterprise-grade SCIM provisioning without protocol expertise
- For Enterprise Customers: Seamless identity provisioning and deprovisioning
- For Integration Teams: Standards-compliant SCIM 2.0 implementation out of the box
§Table of Contents
- Two Main Components
- Table of Contents
- Features
- Installation
- MCP Integration
- Architecture Overview
- ETag Concurrency Control
- Logging Support
- Multi-Tenant Support
- Quick Start - Full SCIM Server
- Schema Discovery
- Provider Implementation
- Examples
- Schema Validation Utility
- Performance
- SCIM 2.0 Compliance
- Contributing
§Two Main Components
This library provides two distinct components:
ScimServer- Full-featured dynamic server for production SCIM endpoints with runtime resource registration and CRUD operationsSchemaDiscovery- Lightweight component for schema discovery and service provider configuration
§Features
- RFC Compliance: Full RFC 7643/7644 compliance for core User and Group schemas
- Type Safety: Compile-time guarantees preventing invalid operations
- Multi-Tenant Ready: Built-in tenant isolation and context management
- Provider Agnostic: Works with any storage backend via trait abstraction
- Async-First: Non-blocking operations with tokio integration
- Value Objects: Type-safe SCIM attribute handling with validation
- Schema-Driven: Dynamic resource types with runtime schema validation
- Comprehensive Logging: Structured logging with request IDs and tenant context
- Flexible Backends: Choose from env_logger, tracing, slog, or any log-compatible crate
- Auto-Discovery: Automatic provider capability detection
- ETag Concurrency Control: Built-in optimistic locking with conditional operations
- MCP Integration: AI agent support via Model Context Protocol (optional feature)
§Installation
Add this to your Cargo.toml:
[dependencies]
scim-server = "0.1.0"
# For async runtime
tokio = { version = "1.0", features = ["full"] }
# For logging (choose one)
env_logger = "0.10" # Simple logging
# OR
tracing-subscriber = "0.3" # Structured logging§Optional Features
[dependencies]
scim-server = { version = "0.1.0", features = ["mcp"] }mcp: Enables Model Context Protocol integration for AI agents
§MCP (Model Context Protocol) Integration
The SCIM server provides optional MCP integration for AI agent interactions. When enabled, the server exposes SCIM operations as structured tools that AI agents can discover and use.
§Enabling MCP Support
Add the MCP feature to your Cargo.toml:
[dependencies]
scim-server = { version = "0.1.0", features = ["mcp"] }§Basic MCP Server Setup
use scim_server::{ScimServer, providers::InMemoryProvider};
// Note: MCP integration requires the "mcp" feature
// use scim_server::mcp_integration::ScimMcpServer;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create SCIM server
let provider = InMemoryProvider::new();
let scim_server = ScimServer::new(provider)?;
// With MCP feature enabled:
// let mcp_server = ScimMcpServer::new(scim_server);
// let tools = mcp_server.get_tools();
// All operations automatically include ETag versioning
// mcp_server.run_stdio().await?;
println!("SCIM server created successfully");
Ok(())
}§Available MCP Tools with ETag Support
The MCP integration provides these tools for AI agents with automatic ETag versioning:
scim_create_user- Create new users with SCIM schema validation (returns ETag)scim_get_user- Retrieve user by ID with full attribute access (returns ETag)scim_update_user- Update user attributes with optional ETag conflict detectionscim_delete_user- Remove users with optional ETag safety checksscim_list_users- List all users with pagination supportscim_search_users- Search users by attributes with filteringscim_user_exists- Check user existence for validationscim_get_schemas- Retrieve all available schemas for AI understandingscim_get_schema- Get specific schema details for validationscim_server_info- Get server capabilities and supported operations
§AI Agent ETag Usage Pattern
use serde_json::json;
use scim_server::mcp_integration::ScimMcpServer;
use scim_server::providers::InMemoryProvider;
use scim_server::ScimServer;
// AI agent workflow with ETag versioning
let provider = InMemoryProvider::new();
let scim_server = ScimServer::new(provider).unwrap();
let mcp_server = ScimMcpServer::new(scim_server);
// 1. Create user - captures ETag for subsequent operations
let create_result = mcp_server.execute_tool(
"scim_create_user",
json!({
"user_data": {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "ai.agent@company.com",
"active": true
}
})
).await;
let metadata = create_result.metadata.unwrap();
let user_id = metadata["resource_id"].as_str().unwrap();
let etag = metadata["etag"].as_str().unwrap();
// 2. Conditional update using ETag - prevents lost updates
let update_result = mcp_server.execute_tool(
"scim_update_user",
json!({
"user_id": user_id,
"user_data": {"userName": "ai.agent@company.com", "active": false},
"expected_version": etag // ETag from create operation
})
).await;
// 3. Handle version conflicts automatically
if !update_result.success {
if update_result.content["is_version_conflict"].as_bool().unwrap_or(false) {
// AI agent should refresh and retry with current version
println!("Version conflict - resource modified by another client");
}
}§Multi-Tenant MCP Operations
AI agents can work with multi-tenant environments:
use serde_json::json;
use scim_server::mcp_integration::ScimMcpServer;
use scim_server::providers::InMemoryProvider;
use scim_server::ScimServer;
// Set up MCP server with InMemoryProvider
let provider = InMemoryProvider::new();
let scim_server = ScimServer::new(provider).unwrap();
let mcp_server = ScimMcpServer::new(scim_server);
// Create user in specific tenant
let result = mcp_server.execute_tool(
"scim_create_user",
json!({
"user_data": {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "ai.agent@company.com",
"active": true
},
"tenant_id": "enterprise-corp"
})
).await;
println!("MCP operation success: {}", result.success);§AI Agent Integration Benefits
- Schema Discovery: AI agents can introspect SCIM schemas for proper validation
- ETag Versioning: Automatic optimistic locking prevents lost updates in concurrent scenarios
- Type Safety: All operations include JSON schema validation for inputs
- Error Handling: Structured error responses with actionable information
- Version Conflict Detection: Clear indicators for AI agents to handle concurrent modifications
- Multi-Tenant: Automatic tenant isolation for enterprise scenarios
- Comprehensive CRUD: Full resource lifecycle management with version control
- Standards Compliance: SCIM 2.0 compliant operations for enterprise integration
§Custom MCP Server Configuration
use scim_server::mcp_integration::{ScimMcpServer, McpServerInfo};
let provider = InMemoryProvider::new();
let scim_server = ScimServer::new(provider)?;
// Custom server information for AI agent discovery
let server_info = McpServerInfo {
name: "Enterprise SCIM Server".to_string(),
version: "2.0.0".to_string(),
description: "Production SCIM server with AI agent support".to_string(),
supported_resource_types: vec!["User".to_string(), "Group".to_string()],
};
let mcp_server = ScimMcpServer::with_info(scim_server, server_info);See examples/mcp_server_example.rs for a complete MCP integration demonstration.
§When to Use MCP Integration
Use MCP integration when:
- Building AI agents that need identity management capabilities
- Creating automated provisioning systems with AI decision making
- Developing chatbots or virtual assistants with user management features
- Building intelligent HR systems with automated user lifecycle management
- Creating AI-powered compliance and audit systems
Use regular SCIM server when:
- Building traditional web applications with SCIM endpoints
- Integrating with existing identity providers (Okta, Azure AD, etc.)
- Creating standard enterprise SCIM provisioning bridges
- Building REST APIs for human operators or traditional applications
The MCP integration adds AI-specific tooling and structured schemas on top of the core SCIM functionality without changing the underlying SCIM compliance.
§Architecture Overview
The SCIM server library follows a layered architecture:
┌─────────────────────────────────────────────────┐
│ HTTP Layer │ ← Your web framework
│ (Axum, Actix, etc.) │
├─────────────────────────────────────────────────┤
│ SCIM Protocol Layer │ ← This library
│ (ScimServer, SchemaDiscovery) │
├─────────────────────────────────────────────────┤
│ Provider Abstraction │ ← ResourceProvider trait
│ (InMemoryProvider, your impl) │
├─────────────────────────────────────────────────┤
│ Storage Layer │ ← Your database/storage
│ (PostgreSQL, MongoDB, etc.) │
└─────────────────────────────────────────────────┘§ETag Concurrency Control
The library provides built-in optimistic concurrency control using ETags as specified in RFC 7232. This prevents accidental overwrites when multiple clients modify the same resource simultaneously.
§Automatic Version Management
All resources automatically include version information computed from their content:
use scim_server::resource::{conditional_provider::VersionedResource, version::ScimVersion};
use scim_server::{ScimServer, providers::InMemoryProvider};
use serde_json::json;
let provider = InMemoryProvider::new();
let server = ScimServer::new(provider)?;
// All operations automatically include version information
let context = scim_server::resource::RequestContext::with_generated_id();
// Create returns a versioned resource
let user_data = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe",
"active": true
});
let versioned = server.provider().create_versioned_resource("User", user_data, &context).await.unwrap();
println!("Resource version: {}", versioned.version().to_http_header());§Conditional Operations
Perform safe updates and deletes using expected versions:
use scim_server::resource::{
version::{ScimVersion, ConditionalResult},
conditional_provider::VersionedResource,
RequestContext,
};
use scim_server::{ScimServer, providers::InMemoryProvider};
use serde_json::json;
let provider = InMemoryProvider::new();
let server = ScimServer::new(provider)?;
let context = RequestContext::with_generated_id();
// Get current resource with version
let versioned = server.provider().get_versioned_resource("User", "123", &context).await.unwrap();
if let Some(versioned) = versioned {
let current_version = versioned.version().clone();
// Conditional update - only succeeds if version matches
let update_data = json!({
"id": "123",
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe",
"active": false // Changed field
});
match server.provider().conditional_update(
"User", "123", update_data, ¤t_version, &context
).await? {
ConditionalResult::Success(updated_resource) => {
println!("Update successful: {}", updated_resource.version().to_http_header());
},
ConditionalResult::VersionMismatch(conflict) => {
println!("Version conflict: {}", conflict.message);
},
ConditionalResult::NotFound => {
println!("Resource not found");
}
}
}§HTTP Integration
ETags work seamlessly with HTTP frameworks:
use scim_server::operation_handler::{ScimOperationHandler, ScimOperationRequest};
use scim_server::resource::version::ScimVersion;
use scim_server::{ScimServer, providers::InMemoryProvider};
use serde_json::json;
let provider = InMemoryProvider::new();
let server = ScimServer::new(provider)?;
let handler = ScimOperationHandler::new(server);
// Conditional update with ETag from HTTP If-Match header
let expected_version = ScimVersion::parse_http_header("W/\"abc123\"")?;
let update_request = ScimOperationRequest::update(
"User",
"123",
json!({"userName": "jane.doe", "active": true})
).with_expected_version(expected_version);
let response = handler.handle_operation(update_request).await;
if response.success {
if let Some(version_str) = response.metadata.additional.get("etag") {
println!("New weak ETag: {}", version_str);
}
}§Version Computation
Versions are computed deterministically from resource content using SHA-256 hashing:
- Content-Based: Versions change when any resource field changes
- Deterministic: Same content always produces the same version
- Collision-Resistant: Cryptographic hashing prevents accidental conflicts
- Provider-Agnostic: Works with any storage backend
§Benefits
- Prevent Lost Updates: Automatic detection of concurrent modifications
- Data Integrity: Ensure operations only proceed with expected resource state
- HTTP Compliance: Full RFC 7232 weak ETag support for web applications
- Zero Configuration: Works automatically with any ResourceProvider implementation
- Performance: Efficient hash-based versioning with minimal overhead
§Logging Support
The library uses the standard Rust log crate facade, allowing you to choose your preferred
logging backend. All SCIM operations are logged with structured information including:
- Request IDs for operation tracing
- Tenant context for multi-tenant deployments
- Resource lifecycle events (create, read, update, delete)
- Error conditions with full context
- Performance and debugging information
§Quick Logging Setup
// Simple logging for development
env_logger::init();
// Or with custom configuration
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.format_timestamp_secs()
.init();See examples/logging_example.rs for comprehensive logging demonstrations.
§Multi-Tenant Support
The library provides built-in multi-tenant capabilities:
use scim_server::{TenantContext, RequestContext};
use serde_json::json;
// Create tenant context
let tenant = TenantContext::new("customer-123".to_string(), "app-456".to_string());
let context = RequestContext::with_tenant_generated_id(tenant);
// Example user data
let user_data = json!({"userName": "john.doe", "active": true});
println!("Tenant ID: {}", context.tenant_context.unwrap().tenant_id);
println!("User data: {}", user_data);
// All operations are automatically scoped to this tenant
// let user = server.create_resource("User", user_data, &context).await?;§Quick Start - Full SCIM Server
use scim_server::{ScimServer, ResourceProvider, Resource, RequestContext, ScimOperation, ListQuery, create_user_resource_handler};
use std::collections::HashMap;
use tokio::sync::RwLock;
use std::sync::Arc;
use serde_json::Value;
use std::future::Future;
struct MyResourceProvider {
resources: Arc<RwLock<HashMap<String, Resource>>>,
}
impl MyResourceProvider {
fn new() -> Self {
Self {
resources: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("Provider error")]
struct MyError;
impl ResourceProvider for MyResourceProvider {
type Error = MyError;
fn create_resource(&self, resource_type: &str, data: Value, _context: &RequestContext) -> impl Future<Output = Result<Resource, Self::Error>> + Send {
async move {
Resource::from_json(resource_type.to_string(), data)
.map_err(|_| MyError)
}
}
fn get_resource(&self, _resource_type: &str, _id: &str, _context: &RequestContext) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send {
async move { Ok(None) }
}
fn update_resource(&self, resource_type: &str, _id: &str, data: Value, _context: &RequestContext) -> impl Future<Output = Result<Resource, Self::Error>> + Send {
async move {
Resource::from_json(resource_type.to_string(), data)
.map_err(|_| MyError)
}
}
fn delete_resource(&self, _resource_type: &str, _id: &str, _context: &RequestContext) -> impl Future<Output = Result<(), Self::Error>> + Send {
async move { Ok(()) }
}
fn list_resources(&self, _resource_type: &str, _query: Option<&ListQuery>, _context: &RequestContext) -> impl Future<Output = Result<Vec<Resource>, Self::Error>> + Send {
async move { Ok(vec![]) }
}
fn find_resource_by_attribute(&self, _resource_type: &str, _attribute: &str, _value: &Value, _context: &RequestContext) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send {
async move { Ok(None) }
}
fn resource_exists(&self, _resource_type: &str, _id: &str, _context: &RequestContext) -> impl Future<Output = Result<bool, Self::Error>> + Send {
async move { Ok(false) }
}
// Conditional operations are automatically available with default implementations
// Override conditional_update and conditional_delete for more efficient provider-specific implementations
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Implement your data access layer
let provider = MyResourceProvider::new();
// Create dynamic SCIM server
let mut server = ScimServer::new(provider)?;
// Register resource types with their operations
let user_schema = server.get_schema_by_id("urn:ietf:params:scim:schemas:core:2.0:User").unwrap().clone();
let user_handler = create_user_resource_handler(user_schema);
let _ = server.register_resource_type("User", user_handler, vec![ScimOperation::Create, ScimOperation::Read]);
// Use server for SCIM operations
let schemas = server.get_all_schemas();
println!("Available schemas: {}", schemas.len());
Ok(())
}§Schema Discovery
For schema discovery and service provider configuration only:
use scim_server::SchemaDiscovery;
let discovery = SchemaDiscovery::new()?;
// Get available schemas
let schemas = discovery.get_schemas().await?;
println!("Available schemas: {}", schemas.len());
// Get service provider configuration
let config = discovery.get_service_provider_config().await?;
println!("Bulk operations supported: {}", config.bulk_supported);§Provider Implementation
Implement the ResourceProvider trait for your storage backend:
use scim_server::{ResourceProvider, Resource, RequestContext, ListQuery};
use serde_json::Value;
use std::future::Future;
struct MyDatabaseProvider {
// Your database connection, etc.
}
#[derive(Debug, thiserror::Error)]
#[error("Database error")]
struct MyError;
impl ResourceProvider for MyDatabaseProvider {
type Error = MyError;
fn create_resource(&self, resource_type: &str, data: Value, context: &RequestContext) -> impl Future<Output = Result<Resource, Self::Error>> + Send {
async move {
// Implement your database creation logic here
Resource::from_json(resource_type.to_string(), data)
.map_err(|_| MyError)
}
}
// Implement other required methods...
}§Examples
This crate includes comprehensive examples in the examples/ directory:
basic_usage.rs- Simple SCIM server setupetag_concurrency_example.rs- ETag concurrency control and conditional operationsmcp_etag_example.rs- AI agent ETag usage with Model Context Protocolmulti_tenant_demo.rs- Multi-tenant operationslogging_example.rs- Comprehensive logging configurationoperation_handler_example.rs- Framework-agnostic operation handlingprovider_capabilities.rs- Automatic capability discovery
Run examples with:
cargo run --example basic_usage
cargo run --example etag_concurrency_example
cargo run --example mcp_etag_example --features mcp§Schema Validation Utility
This crate includes a command-line schema validation utility for testing and validating
SCIM schema files. The schema-validator binary helps ensure your schemas conform to
the expected format before using them in production.
§Usage
§During Development (with Cargo)
# Validate a single schema file
cargo run --bin schema-validator path/to/User.json
# Validate all schemas in a directory
cargo run --bin schema-validator ./path/to/schemas/§Standalone Installation
# Install the binary globally
cargo install --path . --bin schema-validator
# Then use directly
schema-validator path/to/User.json
schema-validator ./path/to/schemas/§From Published Crate (when available)
# Install from crates.io
cargo install scim-server --bin schema-validator
# Use anywhere
schema-validator /path/to/custom/schemas/§Features
- Schema File Validation: Validates JSON structure and SCIM schema format
- Directory Processing: Batch validation of multiple schema files
- Schema Registry Testing: Tests loading schemas into the registry
- Detailed Error Reporting: Clear error messages for debugging
- Schema Summary: Displays attribute counts and types for valid schemas
§Example Output
Validating schema file: path/to/User.json
✓ Schema is valid!
Schema Summary:
ID: urn:ietf:params:scim:schemas:core:2.0:User
Name: User
Attributes: 15
Required attributes: 2
Multi-valued attributes: 4
Required attribute names: id, userNameThe validator performs comprehensive checks including:
- JSON syntax validation
- Required field presence (id, name, attributes)
- Schema ID URI format validation
- Attribute structure validation
- Complex attribute sub-attribute validation
- Canonical values format checking
§Performance
The library is designed for high performance with:
- Zero-copy JSON processing where possible
- Async-first architecture for high concurrency
- Efficient value object system with minimal allocations
- Type-safe operations that compile to efficient code
Benchmarks show 40,000+ operations/second on modern hardware.
§SCIM 2.0 Compliance
This library implements the following SCIM 2.0 specifications:
- RFC 7643: SCIM Core Schema (User, Group, Schema definitions)
- RFC 7644: SCIM Protocol (HTTP operations, filtering, pagination)
§Supported Operations
- Resource CRUD (Create, Read, Update, Delete)
- Resource listing with pagination
- Attribute-based search and filtering
- Schema discovery and introspection
- Service provider configuration
- Multi-valued attribute handling
- Complex attribute validation
- Conditional operations with ETag support
- Optimistic concurrency control
§Standards Compliance
- Full User schema implementation
- Group schema support
- Extension schema framework
- JSON Schema validation
- HTTP status code compliance
- Error response formatting
- RFC 7232 weak ETag support for conditional operations
- RFC 7644 versioning requirements
§Contributing
Contributions are welcome! Please see the repository for:
- Issue reporting and feature requests
- Pull request guidelines
- Development setup instructions
- Testing requirements
§License
This project is licensed under the MIT License - see the LICENSE file for details.
Re-exports§
pub use error::BuildError;pub use error::ScimError;pub use error::ValidationError;pub use provider_capabilities::AuthenticationCapabilities;pub use provider_capabilities::BulkCapabilities;pub use provider_capabilities::CapabilityDiscovery;pub use provider_capabilities::CapabilityIntrospectable;pub use provider_capabilities::ExtendedCapabilities;pub use provider_capabilities::FilterCapabilities;pub use provider_capabilities::FilterOperator;pub use provider_capabilities::PaginationCapabilities;pub use provider_capabilities::ProviderCapabilities;pub use multi_tenant::RateLimit;pub use multi_tenant::ScimAuditConfig;pub use multi_tenant::ScimAuthScheme;pub use multi_tenant::ScimClientAuth;pub use multi_tenant::ScimClientConfig;pub use multi_tenant::ScimConfigurationError;pub use multi_tenant::ScimCustomAttribute;pub use multi_tenant::ScimEndpointConfig;pub use multi_tenant::ScimOperation;pub use multi_tenant::ScimRateLimits;pub use multi_tenant::ScimSchemaConfig;pub use multi_tenant::ScimSchemaExtension;pub use multi_tenant::ScimSearchConfig;pub use multi_tenant::ScimTenantConfiguration;pub use providers::InMemoryError;pub use providers::InMemoryProvider;pub use providers::InMemoryStats;pub use providers::StandardResourceProvider;pub use storage::InMemoryStorage;pub use storage::InMemoryStorageStats;pub use storage::StorageError;pub use storage::StorageKey;pub use storage::StoragePrefix;pub use storage::StorageProvider;pub use multi_tenant::SingleTenantAdapter;pub use multi_tenant::StaticTenantResolver;pub use multi_tenant::TenantResolver;pub use multi_tenant::TenantValidator;pub use multi_tenant::ToSingleTenant;pub use resource::Address;pub use resource::DatabaseMapper;pub use resource::DynamicResource;pub use resource::EmailAddress;pub use resource::IsolationLevel;pub use resource::ListQuery;pub use resource::Meta;pub use resource::Name;pub use resource::PhoneNumber;pub use resource::RequestContext;pub use resource::Resource;pub use resource::ResourceBuilder;pub use resource::ResourceProvider;pub use resource::ResourceProviderExt;pub use resource::SchemaResourceBuilder;pub use resource::TenantContext;pub use resource::TenantPermissions;pub use resource_handlers::create_group_resource_handler;pub use resource_handlers::create_user_resource_handler;pub use schema::AttributeDefinition;pub use schema::AttributeType;pub use schema::Mutability;pub use schema::Schema;pub use schema::SchemaRegistry;pub use schema::Uniqueness;pub use schema_discovery::SchemaDiscovery;pub use schema_discovery::ServiceProviderConfig;pub use scim_server::ScimServer;pub use operation_handler::OperationMetadata;pub use operation_handler::ScimOperationHandler;pub use operation_handler::ScimOperationRequest;pub use operation_handler::ScimOperationResponse;pub use operation_handler::ScimOperationType;pub use operation_handler::ScimQuery;pub use mcp_integration::McpServerInfo;pub use mcp_integration::ScimMcpServer;pub use mcp_integration::ScimToolResult;
Modules§
- auth
- Compile-time authentication system with type-level proofs.
- error
- Error types for SCIM server operations.
- mcp_
integration - MCP (Model Context Protocol) Integration for SCIM Server
- multi_
tenant - Multi-tenant SCIM server capabilities.
- operation_
handler - Operation Handler Foundation
- provider_
capabilities - Automated Provider Capability Discovery System
- providers
- Standard resource provider implementations.
- resource
- Resource model and provider trait for SCIM resources.
- resource_
handlers - Resource handler implementations using the dynamic schema approach.
- schema
- Schema definitions and validation for SCIM resources.
- schema_
discovery - Schema discovery implementation with state machine design.
- scim_
server - Dynamic SCIM server implementation with runtime resource type registration.
- storage
- Storage abstraction layer for SCIM resources.
Macros§
- impl_
value_ object - Helper macro for implementing ValueObject trait for existing value objects