use scim_server::{
BulkCapabilities, CapabilityIntrospectable, ExtendedCapabilities, PaginationCapabilities,
RequestContext, Resource, ResourceProvider, ScimOperation, ScimServer,
create_user_resource_handler, resource::versioned::VersionedResource,
};
use serde_json::{Value, json};
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use tokio::sync::RwLock;
struct AdvancedProvider {
resources: Arc<RwLock<HashMap<String, Resource>>>,
bulk_support: bool,
max_page_size: usize,
}
impl AdvancedProvider {
fn new() -> Self {
Self {
resources: Arc::new(RwLock::new(HashMap::new())),
bulk_support: true,
max_page_size: 500,
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("Provider error: {message}")]
struct ProviderError {
message: String,
}
impl ResourceProvider for AdvancedProvider {
type Error = ProviderError;
fn create_resource(
&self,
resource_type: &str,
data: Value,
_context: &RequestContext,
) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send {
let resource_type = resource_type.to_string();
let resources = self.resources.clone();
async move {
let resource = Resource::from_json(resource_type, data).map_err(|e| ProviderError {
message: format!("Failed to create resource: {}", e),
})?;
let id = resource.get_id().unwrap_or("unknown").to_string();
let versioned = VersionedResource::new(resource.clone());
resources.write().await.insert(id, resource);
Ok(versioned)
}
}
fn get_resource(
&self,
_resource_type: &str,
id: &str,
_context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send {
let id = id.to_string();
let resources = self.resources.clone();
async move {
Ok(resources
.read()
.await
.get(&id)
.map(|r| VersionedResource::new(r.clone())))
}
}
fn update_resource(
&self,
resource_type: &str,
id: &str,
data: Value,
_expected_version: Option<
&scim_server::resource::version::ScimVersion<scim_server::resource::version::Raw>,
>,
_context: &RequestContext,
) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send {
let resource_type = resource_type.to_string();
let id = id.to_string();
let resources = self.resources.clone();
async move {
let resource = Resource::from_json(resource_type, data).map_err(|e| ProviderError {
message: format!("Failed to update resource: {}", e),
})?;
let versioned = VersionedResource::new(resource.clone());
resources.write().await.insert(id, resource);
Ok(versioned)
}
}
fn delete_resource(
&self,
_resource_type: &str,
id: &str,
_expected_version: Option<
&scim_server::resource::version::ScimVersion<scim_server::resource::version::Raw>,
>,
_context: &RequestContext,
) -> impl Future<Output = Result<(), Self::Error>> + Send {
let id = id.to_string();
let resources = self.resources.clone();
async move {
resources.write().await.remove(&id);
Ok(())
}
}
fn list_resources(
&self,
_resource_type: &str,
_query: Option<&scim_server::ListQuery>,
_context: &RequestContext,
) -> impl Future<Output = Result<Vec<VersionedResource>, Self::Error>> + Send {
let resources = self.resources.clone();
async move {
Ok(resources
.read()
.await
.values()
.map(|r| VersionedResource::new(r.clone()))
.collect::<Vec<_>>())
}
}
fn find_resources_by_attribute(
&self,
_resource_type: &str,
_attribute: &str,
_value: &str,
_context: &RequestContext,
) -> impl Future<Output = Result<Vec<VersionedResource>, Self::Error>> + Send {
async move { Ok(Vec::new()) }
}
fn patch_resource(
&self,
_resource_type: &str,
_id: &str,
_patch_request: &Value,
_expected_version: Option<
&scim_server::resource::version::ScimVersion<scim_server::resource::version::Raw>,
>,
_context: &RequestContext,
) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send {
async move {
Err(ProviderError {
message: "Patch not implemented".to_string(),
})
}
}
fn resource_exists(
&self,
_resource_type: &str,
id: &str,
_context: &RequestContext,
) -> impl Future<Output = Result<bool, Self::Error>> + Send {
let id = id.to_string();
let resources = self.resources.clone();
async move { Ok(resources.read().await.contains_key(&id)) }
}
}
impl CapabilityIntrospectable for AdvancedProvider {
fn get_provider_specific_capabilities(&self) -> ExtendedCapabilities {
ExtendedCapabilities {
etag_supported: true,
patch_supported: true,
change_password_supported: true,
sort_supported: true,
custom_capabilities: {
let mut custom = HashMap::new();
custom.insert(
"advanced_filtering".to_string(),
json!({"regex_supported": true, "case_insensitive": true}),
);
custom.insert("transaction_support".to_string(), json!(true));
custom
},
}
}
fn get_bulk_limits(&self) -> Option<BulkCapabilities> {
Some(BulkCapabilities {
supported: self.bulk_support,
max_operations: Some(100),
max_payload_size: Some(1024 * 1024), fail_on_errors_supported: true,
})
}
fn get_pagination_limits(&self) -> Option<scim_server::PaginationCapabilities> {
Some(PaginationCapabilities {
supported: true,
default_page_size: Some(20),
max_page_size: Some(self.max_page_size),
cursor_based_supported: true,
})
}
fn get_authentication_capabilities(&self) -> Option<scim_server::AuthenticationCapabilities> {
Some(scim_server::AuthenticationCapabilities {
schemes: vec![scim_server::AuthenticationScheme {
name: "OAuth 2.0 Bearer Token".to_string(),
description: "OAuth 2.0 Bearer Token authentication".to_string(),
spec_uri: Some("https://tools.ietf.org/html/rfc6750".to_string()),
documentation_uri: Some("https://example.com/auth-docs".to_string()),
auth_type: "oauth2".to_string(),
primary: true,
}],
mfa_supported: true,
token_refresh_supported: true,
})
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 SCIM Server Automated Capability Discovery Example");
println!("====================================================\n");
let provider = AdvancedProvider::new();
let mut server = ScimServer::new(provider)?;
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);
server.register_resource_type(
"User",
user_handler,
vec![
ScimOperation::Create,
ScimOperation::Read,
ScimOperation::Update,
ScimOperation::Delete,
ScimOperation::List,
ScimOperation::Search,
],
)?;
println!("📋 Server Configuration:");
println!(
"- Registered resource types: {:?}",
server
.get_supported_resource_types()
.iter()
.collect::<Vec<_>>()
);
println!(
"- User operations: {:?}",
server.get_supported_operations("User")
);
println!();
println!("🔍 Discovering Capabilities Automatically...");
let capabilities = server.discover_capabilities_with_introspection()?;
println!("\n📊 **DISCOVERED CAPABILITIES**");
println!("==============================");
println!("📋 Schemas:");
for schema in &capabilities.supported_schemas {
println!(" ✓ {}", schema);
}
println!("\n🎯 Resource Types & Operations:");
for (resource_type, operations) in &capabilities.supported_operations {
println!(" 📁 {}:", resource_type);
for op in operations {
println!(" ✓ {:?}", op);
}
}
println!("\n🔍 Filter Capabilities:");
println!(
" Supported: {}",
capabilities.filter_capabilities.supported
);
println!(
" Max Results: {:?}",
capabilities.filter_capabilities.max_results
);
println!(
" Complex Filters: {}",
capabilities.filter_capabilities.complex_filters_supported
);
println!("\n 📋 Filterable Attributes:");
for (resource_type, attributes) in &capabilities.filter_capabilities.filterable_attributes {
println!(" {} -> {:?}", resource_type, attributes);
}
println!("\n 🎛️ Supported Operators:");
for operator in &capabilities.filter_capabilities.supported_operators {
println!(" ✓ {:?}", operator);
}
println!("\n📦 Bulk Operations:");
println!(" Supported: {}", capabilities.bulk_capabilities.supported);
if let Some(max_ops) = capabilities.bulk_capabilities.max_operations {
println!(" Max Operations: {}", max_ops);
}
if let Some(max_size) = capabilities.bulk_capabilities.max_payload_size {
println!(" Max Payload: {} bytes", max_size);
}
println!(
" Fail on Errors: {}",
capabilities.bulk_capabilities.fail_on_errors_supported
);
println!("\n📄 Pagination:");
println!(
" Supported: {}",
capabilities.pagination_capabilities.supported
);
if let Some(default_size) = capabilities.pagination_capabilities.default_page_size {
println!(" Default Page Size: {}", default_size);
}
if let Some(max_size) = capabilities.pagination_capabilities.max_page_size {
println!(" Max Page Size: {}", max_size);
}
println!(
" Cursor-based: {}",
capabilities.pagination_capabilities.cursor_based_supported
);
println!("\n🔐 Authentication:");
for scheme in &capabilities.authentication_capabilities.schemes {
println!(" ✓ {} ({})", scheme.name, scheme.auth_type);
if scheme.primary {
println!(" [PRIMARY]");
}
}
println!(
" MFA Supported: {}",
capabilities.authentication_capabilities.mfa_supported
);
println!(
" Token Refresh: {}",
capabilities
.authentication_capabilities
.token_refresh_supported
);
println!("\n⚡ Extended Features:");
println!(
" ETag Support: {}",
capabilities.extended_capabilities.etag_supported
);
println!(
" PATCH Support: {}",
capabilities.extended_capabilities.patch_supported
);
println!(
" Password Change: {}",
capabilities.extended_capabilities.change_password_supported
);
println!(
" Sorting: {}",
capabilities.extended_capabilities.sort_supported
);
println!("\n 🎛️ Custom Capabilities:");
for (key, value) in &capabilities.extended_capabilities.custom_capabilities {
println!(" {} -> {}", key, value);
}
println!("\n🌐 **RFC 7644 SERVICE PROVIDER CONFIG**");
println!("======================================");
let service_config = server.get_service_provider_config_with_introspection()?;
println!("📋 SCIM ServiceProviderConfig (auto-generated):");
println!("{}", serde_json::to_string_pretty(&service_config)?);
println!("\n🔍 **CAPABILITY QUERIES**");
println!("=========================");
println!(
"Can create Users? {}",
server.supports_operation("User", &ScimOperation::Create)
);
println!(
"Can search Users? {}",
server.supports_operation("User", &ScimOperation::Search)
);
println!(
"Can create Groups? {}",
server.supports_operation("Group", &ScimOperation::Create)
);
println!("\n🎯 **DYNAMIC CAPABILITY UPDATES**");
println!("=================================");
println!(
"Before: Supported resource types = {:?}",
server
.get_supported_resource_types()
.iter()
.collect::<Vec<_>>()
);
println!("Note: Capabilities automatically reflect current server state!");
println!("When you register new resource types or change provider settings,");
println!("the discovered capabilities update automatically.");
println!("\n✨ **KEY BENEFITS**");
println!("==================");
println!("✓ No manual capability configuration required");
println!("✓ Capabilities always match actual server state");
println!("✓ RFC 7644 compliant ServiceProviderConfig");
println!("✓ Real-time capability introspection");
println!("✓ Type-safe capability constraints");
println!("✓ Automatic schema-based filter capability discovery");
Ok(())
}