scim_server/operation_handler/
core.rs1use 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
17pub struct ScimOperationHandler<P: ResourceProvider> {
22 pub(super) server: ScimServer<P>,
23}
24
25#[derive(Debug, Clone, PartialEq)]
30pub struct ScimOperationRequest {
31 pub operation: ScimOperationType,
33 pub resource_type: String,
35 pub resource_id: Option<String>,
37 pub data: Option<Value>,
39 pub query: Option<ScimQuery>,
41 pub tenant_context: Option<TenantContext>,
43 pub request_id: Option<String>,
45 pub expected_version: Option<ScimVersion>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub enum ScimOperationType {
52 Create,
54 Get,
56 Update,
58 Delete,
60 List,
62 Search,
64 GetSchemas,
66 GetSchema,
68 Exists,
70}
71
72#[derive(Debug, Clone, PartialEq)]
74pub struct ScimQuery {
75 pub count: Option<usize>,
77 pub start_index: Option<usize>,
79 pub filter: Option<String>,
81 pub attributes: Option<Vec<String>>,
83 pub excluded_attributes: Option<Vec<String>>,
85 pub search_attribute: Option<String>,
87 pub search_value: Option<Value>,
89}
90
91#[derive(Debug, Clone, PartialEq)]
96pub struct ScimOperationResponse {
97 pub success: bool,
99 pub data: Option<Value>,
101 pub error: Option<String>,
103 pub error_code: Option<String>,
105 pub metadata: OperationMetadata,
107}
108
109#[derive(Debug, Clone, PartialEq)]
114pub struct OperationMetadata {
115 pub resource_type: Option<String>,
117 pub resource_id: Option<String>,
119 pub resource_count: Option<usize>,
121 pub total_results: Option<usize>,
123 pub request_id: String,
125 pub tenant_id: Option<String>,
127 pub schemas: Option<Vec<String>>,
129 pub additional: HashMap<String, Value>,
131}
132
133impl<P: ResourceProvider + Sync> ScimOperationHandler<P> {
134 pub fn new(server: ScimServer<P>) -> Self {
136 Self { server }
137 }
138
139 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 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 pub(super) fn server(&self) -> &ScimServer<P> {
220 &self.server
221 }
222}