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    /// Patch an existing resource with partial updates
59    Patch,
60    /// Delete a resource
61    Delete,
62    /// List resources with optional pagination and filtering
63    List,
64    /// Search resources by attribute
65    Search,
66    /// Get all available schemas
67    GetSchemas,
68    /// Get a specific schema by ID
69    GetSchema,
70    /// Check if a resource exists
71    Exists,
72}
73
74/// Query parameters for list and search operations
75#[derive(Debug, Clone, PartialEq)]
76pub struct ScimQuery {
77    /// Maximum number of results to return
78    pub count: Option<usize>,
79    /// Starting index for pagination
80    pub start_index: Option<usize>,
81    /// Filter expression for search
82    pub filter: Option<String>,
83    /// Attributes to include in results
84    pub attributes: Option<Vec<String>>,
85    /// Attributes to exclude from results
86    pub excluded_attributes: Option<Vec<String>>,
87    /// Specific attribute to search on
88    pub search_attribute: Option<String>,
89    /// Value to search for
90    pub search_value: Option<Value>,
91}
92
93/// Structured response from SCIM operations
94///
95/// This type provides a consistent response format across all operation types
96/// and transport layers.
97#[derive(Debug, Clone, PartialEq)]
98pub struct ScimOperationResponse {
99    /// Whether the operation succeeded
100    pub success: bool,
101    /// The primary data returned by the operation
102    pub data: Option<Value>,
103    /// Error message if the operation failed
104    pub error: Option<String>,
105    /// Error code for programmatic handling
106    pub error_code: Option<String>,
107    /// Additional metadata about the operation including version information
108    pub metadata: OperationMetadata,
109}
110
111/// Metadata about a SCIM operation
112///
113/// Contains contextual information about the operation including version data
114/// for ETag-based concurrency control.
115#[derive(Debug, Clone, PartialEq)]
116pub struct OperationMetadata {
117    /// Resource type involved in the operation
118    pub resource_type: Option<String>,
119    /// Resource ID if applicable
120    pub resource_id: Option<String>,
121    /// Number of resources returned (for list operations)
122    pub resource_count: Option<usize>,
123    /// Total number of resources available (for pagination)
124    pub total_results: Option<usize>,
125    /// Request ID for tracing
126    pub request_id: String,
127    /// Tenant ID if applicable
128    pub tenant_id: Option<String>,
129    /// Resource schemas involved
130    pub schemas: Option<Vec<String>>,
131    /// Additional metadata including version information
132    pub additional: HashMap<String, Value>,
133}
134
135impl<P: ResourceProvider + Sync> ScimOperationHandler<P> {
136    /// Create a new operation handler with the given SCIM server.
137    pub fn new(server: ScimServer<P>) -> Self {
138        Self { server }
139    }
140
141    /// Handle a structured SCIM operation request.
142    ///
143    /// This is the main entry point that dispatches to specific operation handlers
144    /// based on the operation type.
145    pub async fn handle_operation(&self, request: ScimOperationRequest) -> ScimOperationResponse {
146        let request_id = request
147            .request_id
148            .clone()
149            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
150
151        info!(
152            "SCIM operation handler processing {:?} for {} (request: '{}')",
153            request.operation, request.resource_type, request_id
154        );
155
156        let context = self.create_request_context(&request, &request_id);
157
158        let result = match request.operation {
159            ScimOperationType::Create => {
160                super::handlers::crud::handle_create(self, request, &context).await
161            }
162            ScimOperationType::Get => {
163                super::handlers::crud::handle_get(self, request, &context).await
164            }
165            ScimOperationType::Update => {
166                super::handlers::crud::handle_update(self, request, &context).await
167            }
168            ScimOperationType::Patch => {
169                super::handlers::crud::handle_patch(self, request, &context).await
170            }
171            ScimOperationType::Delete => {
172                super::handlers::crud::handle_delete(self, request, &context).await
173            }
174            ScimOperationType::List => {
175                super::handlers::query::handle_list(self, request, &context).await
176            }
177            ScimOperationType::Search => {
178                super::handlers::query::handle_search(self, request, &context).await
179            }
180            ScimOperationType::GetSchemas => {
181                super::handlers::schema::handle_get_schemas(self, request, &context).await
182            }
183            ScimOperationType::GetSchema => {
184                super::handlers::schema::handle_get_schema(self, request, &context).await
185            }
186            ScimOperationType::Exists => {
187                super::handlers::utility::handle_exists(self, request, &context).await
188            }
189        };
190
191        match &result {
192            Ok(_) => {
193                debug!(
194                    "SCIM operation handler completed successfully (request: '{}')",
195                    request_id
196                );
197            }
198            Err(e) => {
199                warn!(
200                    "SCIM operation handler failed: {} (request: '{}')",
201                    e, request_id
202                );
203            }
204        }
205
206        result.unwrap_or_else(|e| super::errors::create_error_response(e, request_id))
207    }
208
209    /// Create a RequestContext from the operation request.
210    pub(super) fn create_request_context(
211        &self,
212        request: &ScimOperationRequest,
213        request_id: &str,
214    ) -> RequestContext {
215        match &request.tenant_context {
216            Some(tenant_ctx) => {
217                RequestContext::with_tenant(request_id.to_string(), tenant_ctx.clone())
218            }
219            None => RequestContext::new(request_id.to_string()),
220        }
221    }
222
223    /// Get access to the underlying SCIM server.
224    pub(super) fn server(&self) -> &ScimServer<P> {
225        &self.server
226    }
227}