Troubleshooting
This guide helps you diagnose and resolve common issues when working with the SCIM Server library. It covers typical problems, debugging techniques, and performance optimization.
Common Issues
Connection and Network Problems
"Connection refused" or "Unable to connect"
Symptoms:
- Server fails to start
- Client connections are rejected
- Network timeouts
Causes & Solutions:
#![allow(unused)] fn main() { // Check if port is already in use use tokio::net::TcpListener; // This will fail if port is occupied let listener = TcpListener::bind("0.0.0.0:3000").await?; }
Solution 1: Change port
#![allow(unused)] fn main() { // Try a different port let listener = TcpListener::bind("0.0.0.0:3001").await?; }
Solution 2: Find and kill process using port
# Find process using port 3000
lsof -i :3000
# Kill the process
kill -9 <PID>
Solution 3: Bind to correct interface
#![allow(unused)] fn main() { // For local development let listener = TcpListener::bind("127.0.0.1:3000").await?; // For production (all interfaces) let listener = TcpListener::bind("0.0.0.0:3000").await?; }
Database Connection Issues
Error: "Failed to connect to database"
#![allow(unused)] fn main() { use scim_server::DatabaseProvider; // Add connection retry logic async fn connect_with_retry(url: &str, max_retries: u32) -> Result<DatabaseProvider, Error> { let mut attempts = 0; loop { match DatabaseProvider::new(url).await { Ok(provider) => return Ok(provider), Err(e) if attempts < max_retries => { attempts += 1; println!("Connection attempt {} failed: {}. Retrying in 5 seconds...", attempts, e); tokio::time::sleep(Duration::from_secs(5)).await; }, Err(e) => return Err(e.into()), } } } }
Common database URL formats:
#![allow(unused)] fn main() { // PostgreSQL "postgresql://username:password@localhost:5432/scim_db" // SQLite "sqlite:./scim.db" "sqlite::memory:" // For testing // MySQL "mysql://username:password@localhost:3306/scim_db" }
Authentication and Authorization Issues
"401 Unauthorized" responses
Check authentication configuration:
#![allow(unused)] fn main() { async fn debug_auth_middleware( headers: HeaderMap, request: Request, next: Next, ) -> Result<Response, StatusCode> { // Log all headers for debugging for (name, value) in headers.iter() { println!("Header: {} = {:?}", name, value); } // Check for Authorization header if let Some(auth_header) = headers.get("Authorization") { println!("Auth header found: {:?}", auth_header); } else { println!("No Authorization header found"); return Err(StatusCode::UNAUTHORIZED); } Ok(next.run(request).await) } }
Common authentication problems:
#![allow(unused)] fn main() { // Problem: Missing Bearer prefix // Wrong: "abc123def456" // Correct: "Bearer abc123def456" // Problem: Expired tokens let now = chrono::Utc::now().timestamp() as usize; if token_claims.exp < now { return Err(StatusCode::UNAUTHORIZED); } // Problem: Incorrect Base64 encoding for Basic auth use base64::{Engine as _, engine::general_purpose}; let encoded = general_purpose::STANDARD.encode("username:password"); println!("Correct Basic auth: Basic {}", encoded); }
"403 Forbidden" responses
Debug permission checking:
#![allow(unused)] fn main() { fn debug_permissions(user_perms: &[String], required_perm: &str) -> bool { println!("User permissions: {:?}", user_perms); println!("Required permission: {}", required_perm); let has_permission = user_perms.contains(&required_perm.to_string()); println!("Permission granted: {}", has_permission); has_permission } }
Resource and Data Issues
"404 Not Found" for existing resources
Check tenant isolation:
#![allow(unused)] fn main() { // Make sure you're using the correct tenant ID let user = provider.get_user("correct-tenant-id", &user_id).await?; // Debug tenant data let all_tenants = provider.list_all_tenants().await?; println!("Available tenants: {:?}", all_tenants); }
Verify resource IDs:
#![allow(unused)] fn main() { // UUIDs are case-sensitive let user_id = "2819c223-7f76-453a-919d-413861904646"; // Correct let user_id = "2819C223-7F76-453A-919D-413861904646"; // Wrong case // Check if resource exists if let Some(user) = provider.get_user(tenant_id, user_id).await? { println!("User found: {}", user.username()); } else { println!("User not found with ID: {}", user_id); // List all users to debug let all_users = provider.list_users(tenant_id, &ListOptions::default()).await?; for user in all_users.resources { println!("Existing user: {} ({})", user.username(), user.id()); } } }
"409 Conflict" on resource creation
Check for duplicate constraints:
#![allow(unused)] fn main() { // Debug uniqueness violations match provider.create_user(tenant_id, user).await { Err(ProviderError::Conflict(msg)) => { println!("Conflict detected: {}", msg); // Check for existing username if let Some(existing) = provider.find_user_by_username(tenant_id, &user.username()).await? { println!("User with username '{}' already exists: {}", user.username(), existing.id()); } // Check for existing email if let Some(email) = user.primary_email() { if let Some(existing) = provider.find_user_by_email(tenant_id, email).await? { println!("User with email '{}' already exists: {}", email, existing.id()); } } }, Ok(created_user) => println!("User created successfully: {}", created_user.id()), Err(e) => println!("Unexpected error: {}", e), } }
"412 Precondition Failed" on updates
ETag version conflicts:
#![allow(unused)] fn main() { // Always fetch latest version before update let mut user = provider.get_user(tenant_id, user_id).await? .ok_or("User not found")?; println!("Current user version: {}", user.meta().version); // Make your changes user.set_given_name("Updated Name"); // Update with current version match provider.update_user(tenant_id, user).await { Ok(updated_user) => { println!("Update successful. New version: {}", updated_user.meta().version); }, Err(ProviderError::VersionConflict { current_version, provided_version }) => { println!("Version conflict: expected {}, got {}", current_version, provided_version); // Retry with fresh data let fresh_user = provider.get_user(tenant_id, user_id).await?; // Apply changes to fresh user and retry }, Err(e) => println!("Update failed: {}", e), } }
Validation and Schema Issues
"400 Bad Request" with validation errors
Debug JSON parsing:
#![allow(unused)] fn main() { use serde_json; // Test JSON deserialization let json_str = r#"{"invalid": "json"}"#; match serde_json::from_str::<ScimUser>(json_str) { Ok(user) => println!("User parsed successfully"), Err(e) => { println!("JSON parsing failed: {}", e); println!("Error location: line {}, column {}", e.line(), e.column()); } } }
Validate required fields:
#![allow(unused)] fn main() { // Check for missing required fields match ScimUser::builder() .username("test@example.com") // Missing other required fields .build() { Ok(user) => println!("User created: {}", user.id()), Err(ValidationError::RequiredField(field)) => { println!("Missing required field: {}", field); }, Err(e) => println!("Validation error: {}", e), } }
Schema validation debugging:
#![allow(unused)] fn main() { // Check schema compliance let user = ScimUser::builder() .username("test@example.com") .given_name("Test") .family_name("User") .build()?; // Validate against schema match schema_registry.validate_user(&user) { Ok(_) => println!("User is valid according to schema"), Err(ValidationError::SchemaViolation { field, message }) => { println!("Schema violation in field '{}': {}", field, message); }, Err(e) => println!("Validation error: {}", e), } }
Debugging Techniques
Enable Detailed Logging
use tracing::{info, warn, error, debug}; use tracing_subscriber::{EnvFilter, fmt}; // Set up comprehensive logging fn setup_logging() { let filter = EnvFilter::from_default_env() .add_directive("scim_server=debug".parse().unwrap()) .add_directive("sqlx=info".parse().unwrap()) .add_directive("hyper=info".parse().unwrap()); fmt() .with_env_filter(filter) .with_target(true) .with_thread_ids(true) .with_file(true) .with_line_number(true) .init(); } // Use in your application #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { setup_logging(); info!("Starting SCIM server"); // Your application code... Ok(()) }
Environment variables for logging:
# Enable debug logging
export RUST_LOG=scim_server=debug,sqlx=info
# Enable trace-level logging (very verbose)
export RUST_LOG=scim_server=trace
# Log only errors
export RUST_LOG=scim_server=error
Request/Response Debugging
#![allow(unused)] fn main() { use axum::{ body::Body, extract::Request, middleware::Next, response::Response, }; async fn debug_middleware( request: Request, next: Next, ) -> Response { let method = request.method().clone(); let uri = request.uri().clone(); let headers = request.headers().clone(); // Log request debug!("Incoming request: {} {}", method, uri); for (name, value) in headers.iter() { debug!("Request header: {}: {:?}", name, value); } let start = std::time::Instant::now(); let response = next.run(request).await; let duration = start.elapsed(); // Log response debug!( "Response: {} {} - {} in {:?}", method, uri, response.status(), duration ); response } // Add to your router let app = Router::new() .nest("/scim/v2", scim_routes()) .layer(middleware::from_fn(debug_middleware)); }
Database Query Debugging
#![allow(unused)] fn main() { // Enable SQL query logging for sqlx use sqlx::postgres::PgPoolOptions; let pool = PgPoolOptions::new() .max_connections(10) .connect(&database_url) .await?; // Queries will be logged when RUST_LOG includes sqlx=debug }
Provider State Inspection
#![allow(unused)] fn main() { // For InMemoryProvider, add debugging methods impl InMemoryProvider { pub async fn debug_state(&self) { let users = self.users.read().await; let groups = self.groups.read().await; println!("=== Provider State Debug ==="); for (tenant_id, tenant_users) in users.iter() { println!("Tenant '{}' has {} users:", tenant_id, tenant_users.len()); for (user_id, user) in tenant_users.iter() { println!(" User: {} ({})", user.username(), user_id); } } for (tenant_id, tenant_groups) in groups.iter() { println!("Tenant '{}' has {} groups:", tenant_id, tenant_groups.len()); for (group_id, group) in tenant_groups.iter() { println!(" Group: {} ({}) with {} members", group.display_name(), group_id, group.members().len()); } } println!("=== End Debug ==="); } } // Use in your code provider.debug_state().await; }
Performance Issues
Slow Database Queries
Add query timing:
#![allow(unused)] fn main() { use std::time::Instant; async fn timed_query<T, F, Fut>(operation: F) -> Result<T, Error> where F: FnOnce() -> Fut, Fut: Future<Output = Result<T, Error>>, { let start = Instant::now(); let result = operation().await; let duration = start.elapsed(); if duration > Duration::from_millis(100) { warn!("Slow query detected: {:?}", duration); } result } // Usage let users = timed_query(|| provider.list_users(tenant_id, &options)).await?; }
Optimize filtering:
#![allow(unused)] fn main() { // Current approach: Load all users then filter in memory // Note: Database-level filtering is not yet implemented let all_users = provider.list_users(tenant_id, &ListOptions::default()).await?; let filtered: Vec<_> = all_users.resources.into_iter() .filter(|u| u.department() == Some("Engineering")) .collect(); // For large datasets, consider implementing pagination let options = ListOptions::builder() .count(Some(50)) // Limit results .start_index(Some(1)) .build(); let paginated_users = provider.list_users(tenant_id, &options).await?; }
Memory Issues
Monitor memory usage:
#![allow(unused)] fn main() { use sysinfo::{System, SystemExt}; fn log_memory_usage() { let mut system = System::new_all(); system.refresh_memory(); let used = system.used_memory(); let total = system.total_memory(); let percentage = (used as f64 / total as f64) * 100.0; info!("Memory usage: {} MB / {} MB ({:.1}%)", used / 1024 / 1024, total / 1024 / 1024, percentage); } // Check periodically tokio::spawn(async { loop { log_memory_usage(); tokio::time::sleep(Duration::from_secs(60)).await; } }); }
Optimize large result sets:
#![allow(unused)] fn main() { // Use pagination for large datasets let mut start_index = 1; let page_size = 100; loop { let options = ListOptions::builder() .start_index(start_index) .count(page_size) .build(); let page = provider.list_users(tenant_id, &options).await?; // Process page for user in page.resources { process_user(user).await?; } // Check if we're done if page.resources.len() < page_size { break; } start_index += page_size; } }
Health Checks and Monitoring
Implement Health Endpoints
#![allow(unused)] fn main() { use axum::{response::Json, http::StatusCode}; use serde_json::json; async fn health_check( State(state): State<AppState>, ) -> Result<Json<serde_json::Value>, StatusCode> { // Check provider health let provider_health = state.provider.health_check().await .map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?; // Check database connectivity let db_status = match state.provider.get_user("health-check", "non-existent").await { Ok(None) | Err(ProviderError::NotFound { .. }) => "healthy", Err(_) => "unhealthy", }; let response = json!({ "status": "healthy", "timestamp": chrono::Utc::now(), "version": env!("CARGO_PKG_VERSION"), "components": { "provider": provider_health.status, "database": db_status } }); Ok(Json(response)) } // Add to router let app = Router::new() .route("/health", get(health_check)) .nest("/scim/v2", scim_routes()); }
Metrics Collection
#![allow(unused)] fn main() { use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; #[derive(Clone)] struct Metrics { requests_total: Arc<AtomicU64>, requests_errors: Arc<AtomicU64>, users_created: Arc<AtomicU64>, users_updated: Arc<AtomicU64>, } impl Metrics { fn new() -> Self { Self { requests_total: Arc::new(AtomicU64::new(0)), requests_errors: Arc::new(AtomicU64::new(0)), users_created: Arc::new(AtomicU64::new(0)), users_updated: Arc::new(AtomicU64::new(0)), } } fn increment_requests(&self) { self.requests_total.fetch_add(1, Ordering::Relaxed); } fn increment_errors(&self) { self.requests_errors.fetch_add(1, Ordering::Relaxed); } } async fn metrics_endpoint( State(metrics): State<Metrics>, ) -> Json<serde_json::Value> { Json(json!({ "requests_total": metrics.requests_total.load(Ordering::Relaxed), "requests_errors": metrics.requests_errors.load(Ordering::Relaxed), "users_created": metrics.users_created.load(Ordering::Relaxed), "users_updated": metrics.users_updated.load(Ordering::Relaxed), })) } }
Testing and Validation
Unit Test Debugging
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; use tokio_test; #[tokio::test] async fn debug_user_creation() { // Enable logging in tests let _ = tracing_subscriber::fmt::try_init(); let provider = InMemoryProvider::new(); let tenant_id = "test-tenant"; // Create test user let user = ScimUser::builder() .username("test@example.com") .given_name("Test") .family_name("User") .build() .unwrap(); println!("Creating user: {:?}", user); let created = provider.create_user(tenant_id, user).await.unwrap(); println!("Created user with ID: {}", created.id()); // Verify creation let fetched = provider.get_user(tenant_id, created.id()).await.unwrap(); assert!(fetched.is_some()); println!("Test passed: user creation and retrieval"); } } }
Integration Test Setup
#![allow(unused)] fn main() { // Create test helper functions pub async fn setup_test_server() -> (TestServer, String) { let provider = InMemoryProvider::new(); let scim_server = ScimServer::builder() .provider(provider) .build(); let app = create_app(scim_server); let server = TestServer::new(app).unwrap(); let tenant_id = "test-tenant".to_string(); (server, tenant_id) } pub fn create_test_user() -> ScimUser { ScimUser::builder() .username("test@example.com") .given_name("Test") .family_name("User") .email("test@example.com") .active(true) .build() .unwrap() } #[tokio::test] async fn integration_test_user_lifecycle() { let (server, tenant_id) = setup_test_server().await; let user = create_test_user(); // Create user let response = server .post(&format!("/scim/v2/{}/Users", tenant_id)) .json(&user) .await; assert_eq!(response.status_code(), 201); let created_user: ScimUser = response.json(); println!("Created user: {}", created_user.id()); // Get user let response = server .get(&format!("/scim/v2/{}/Users/{}", tenant_id, created_user.id())) .await; assert_eq!(response.status_code(), 200); // Update user let mut updated_user = created_user.clone(); updated_user.set_given_name("Updated"); let response = server .put(&format!("/scim/v2/{}/Users/{}", tenant_id, created_user.id())) .json(&updated_user) .await; assert_eq!(response.status_code(), 200); // Delete user let response = server .delete(&format!("/scim/v2/{}/Users/{}", tenant_id, created_user.id())) .await; assert_eq!(response.status_code(), 204); } }
Getting Help
Collect Diagnostic Information
When reporting issues, include:
#![allow(unused)] fn main() { // Version information println!("SCIM Server version: {}", env!("CARGO_PKG_VERSION")); println!("Rust version: {}", env!("RUSTC_VERSION")); // System information use sysinfo::{System, SystemExt}; let mut system = System::new_all(); system.refresh_all(); println!("OS: {} {}", system.name().unwrap_or("Unknown"), system.os_version().unwrap_or("Unknown")); println!("Total memory: {} MB", system.total_memory() / 1024 / 1024); // Configuration println!("Database URL: {}", env::var("DATABASE_URL").unwrap_or("Not set".to_string())); println!("Log level: {}", env::var("RUST_LOG").unwrap_or("Not set".to_string())); }
Enable Maximum Logging
# Set maximum verbosity
export RUST_LOG=trace
# Run with backtrace
export RUST_BACKTRACE=full
# Run your application
cargo run
Create Minimal Reproduction
// Create the smallest possible example that reproduces the issue use scim_server::{ScimServer, InMemoryProvider, ScimUser}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let provider = InMemoryProvider::new(); let scim_server = ScimServer::builder().provider(provider).build(); // Minimal reproduction of your issue let user = ScimUser::builder() .username("test@example.com") .build()?; let result = scim_server.create_user("tenant-1", user).await; println!("Result: {:?}", result); Ok(()) }
This troubleshooting guide should help you identify and resolve most common issues with the SCIM Server library. For additional help, check the GitHub Issues or create a new issue with your diagnostic information.