scim_server/operation_handler/
core.rs

1//! Core operation handler infrastructure
2//!
3//! This module contains the foundational types and main dispatcher logic for SCIM operations.
4//! It provides the central handler struct and operation dispatch functionality that other
5//! operation handler modules depend on.
6
7use crate::{
8    ResourceProvider, ScimServer,
9    resource::version::ScimVersion,
10    resource::{RequestContext, TenantContext},
11};
12use log::{debug, info, warn};
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::collections::HashMap;
16
17/// Framework-agnostic operation handler for SCIM operations
18///
19/// This handler provides a structured interface for performing SCIM operations
20/// without being tied to any specific transport layer (HTTP, MCP, etc.).
21pub struct ScimOperationHandler<P: ResourceProvider> {
22    pub(super) server: ScimServer<P>,
23}
24
25/// Structured request for SCIM operations
26///
27/// This type encapsulates all the information needed to perform a SCIM operation
28/// in a transport-agnostic way.
29#[derive(Debug, Clone, PartialEq)]
30pub struct ScimOperationRequest {
31    /// The type of operation to perform
32    pub operation: ScimOperationType,
33    /// The resource type (e.g., "User", "Group")
34    pub resource_type: String,
35    /// Resource ID for operations that target a specific resource
36    pub resource_id: Option<String>,
37    /// Data payload for create/update operations
38    pub data: Option<Value>,
39    /// Query parameters for list/search operations
40    pub query: Option<ScimQuery>,
41    /// Tenant context for multi-tenant operations
42    pub tenant_context: Option<TenantContext>,
43    /// Request ID for tracing and correlation
44    pub request_id: Option<String>,
45    /// Expected version for conditional operations
46    pub expected_version: Option<ScimVersion>,
47}
48
49/// Types of SCIM operations supported by the handler
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub enum ScimOperationType {
52    /// Create a new resource
53    Create,
54    /// Get a specific resource by ID
55    Get,
56    /// Update an existing resource
57    Update,
58    /// Delete a resource
59    Delete,
60    /// List resources with optional pagination and filtering
61    List,
62    /// Search resources by attribute
63    Search,
64    /// Get all available schemas
65    GetSchemas,
66    /// Get a specific schema by ID
67    GetSchema,
68    /// Check if a resource exists
69    Exists,
70}
71
72/// Query parameters for list and search operations
73#[derive(Debug, Clone, PartialEq)]
74pub struct ScimQuery {
75    /// Maximum number of results to return
76    pub count: Option<usize>,
77    /// Starting index for pagination
78    pub start_index: Option<usize>,
79    /// Filter expression for search
80    pub filter: Option<String>,
81    /// Attributes to include in results
82    pub attributes: Option<Vec<String>>,
83    /// Attributes to exclude from results
84    pub excluded_attributes: Option<Vec<String>>,
85    /// Specific attribute to search on
86    pub search_attribute: Option<String>,
87    /// Value to search for
88    pub search_value: Option<Value>,
89}
90
91/// Structured response from SCIM operations
92///
93/// This type provides a consistent response format across all operation types
94/// and transport layers.
95#[derive(Debug, Clone, PartialEq)]
96pub struct ScimOperationResponse {
97    /// Whether the operation succeeded
98    pub success: bool,
99    /// The primary data returned by the operation
100    pub data: Option<Value>,
101    /// Error message if the operation failed
102    pub error: Option<String>,
103    /// Error code for programmatic handling
104    pub error_code: Option<String>,
105    /// Additional metadata about the operation including version information
106    pub metadata: OperationMetadata,
107}
108
109/// Metadata about a SCIM operation
110///
111/// Contains contextual information about the operation including version data
112/// for ETag-based concurrency control.
113#[derive(Debug, Clone, PartialEq)]
114pub struct OperationMetadata {
115    /// Resource type involved in the operation
116    pub resource_type: Option<String>,
117    /// Resource ID if applicable
118    pub resource_id: Option<String>,
119    /// Number of resources returned (for list operations)
120    pub resource_count: Option<usize>,
121    /// Total number of resources available (for pagination)
122    pub total_results: Option<usize>,
123    /// Request ID for tracing
124    pub request_id: String,
125    /// Tenant ID if applicable
126    pub tenant_id: Option<String>,
127    /// Resource schemas involved
128    pub schemas: Option<Vec<String>>,
129    /// Additional metadata including version information
130    pub additional: HashMap<String, Value>,
131}
132
133impl<P: ResourceProvider + Sync> ScimOperationHandler<P> {
134    /// Create a new operation handler with the given SCIM server.
135    pub fn new(server: ScimServer<P>) -> Self {
136        Self { server }
137    }
138
139    /// Handle a structured SCIM operation request.
140    ///
141    /// This is the main entry point that dispatches to specific operation handlers
142    /// based on the operation type.
143    pub async fn handle_operation(&self, request: ScimOperationRequest) -> ScimOperationResponse {
144        let request_id = request
145            .request_id
146            .clone()
147            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
148
149        info!(
150            "SCIM operation handler processing {:?} for {} (request: '{}')",
151            request.operation, request.resource_type, request_id
152        );
153
154        let context = self.create_request_context(&request, &request_id);
155
156        let result = match request.operation {
157            ScimOperationType::Create => {
158                super::handlers::crud::handle_create(self, request, &context).await
159            }
160            ScimOperationType::Get => {
161                super::handlers::crud::handle_get(self, request, &context).await
162            }
163            ScimOperationType::Update => {
164                super::handlers::crud::handle_update(self, request, &context).await
165            }
166            ScimOperationType::Delete => {
167                super::handlers::crud::handle_delete(self, request, &context).await
168            }
169            ScimOperationType::List => {
170                super::handlers::query::handle_list(self, request, &context).await
171            }
172            ScimOperationType::Search => {
173                super::handlers::query::handle_search(self, request, &context).await
174            }
175            ScimOperationType::GetSchemas => {
176                super::handlers::schema::handle_get_schemas(self, request, &context).await
177            }
178            ScimOperationType::GetSchema => {
179                super::handlers::schema::handle_get_schema(self, request, &context).await
180            }
181            ScimOperationType::Exists => {
182                super::handlers::utility::handle_exists(self, request, &context).await
183            }
184        };
185
186        match &result {
187            Ok(_) => {
188                debug!(
189                    "SCIM operation handler completed successfully (request: '{}')",
190                    request_id
191                );
192            }
193            Err(e) => {
194                warn!(
195                    "SCIM operation handler failed: {} (request: '{}')",
196                    e, request_id
197                );
198            }
199        }
200
201        result.unwrap_or_else(|e| super::errors::create_error_response(e, request_id))
202    }
203
204    /// Create a RequestContext from the operation request.
205    pub(super) fn create_request_context(
206        &self,
207        request: &ScimOperationRequest,
208        request_id: &str,
209    ) -> RequestContext {
210        match &request.tenant_context {
211            Some(tenant_ctx) => {
212                RequestContext::with_tenant(request_id.to_string(), tenant_ctx.clone())
213            }
214            None => RequestContext::new(request_id.to_string()),
215        }
216    }
217
218    /// Get access to the underlying SCIM server.
219    pub(super) fn server(&self) -> &ScimServer<P> {
220        &self.server
221    }
222}