scim_server/operation_handler/
core.rs1use crate::{
8 ResourceProvider, ScimServer,
9 resource::version::RawVersion,
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<RawVersion>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub enum ScimOperationType {
52 Create,
54 Get,
56 Update,
58 Patch,
60 Delete,
62 List,
64 Search,
66 GetSchemas,
68 GetSchema,
70 Exists,
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub struct ScimQuery {
77 pub count: Option<usize>,
79 pub start_index: Option<usize>,
81 pub filter: Option<String>,
83 pub attributes: Option<Vec<String>>,
85 pub excluded_attributes: Option<Vec<String>>,
87 pub search_attribute: Option<String>,
89 pub search_value: Option<Value>,
91}
92
93#[derive(Debug, Clone, PartialEq)]
98pub struct ScimOperationResponse {
99 pub success: bool,
101 pub data: Option<Value>,
103 pub error: Option<String>,
105 pub error_code: Option<String>,
107 pub metadata: OperationMetadata,
109}
110
111#[derive(Debug, Clone, PartialEq)]
116pub struct OperationMetadata {
117 pub resource_type: Option<String>,
119 pub resource_id: Option<String>,
121 pub resource_count: Option<usize>,
123 pub total_results: Option<usize>,
125 pub request_id: String,
127 pub tenant_id: Option<String>,
129 pub schemas: Option<Vec<String>>,
131 pub additional: HashMap<String, Value>,
133}
134
135impl<P: ResourceProvider + Sync> ScimOperationHandler<P> {
136 pub fn new(server: ScimServer<P>) -> Self {
138 Self { server }
139 }
140
141 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 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 pub(super) fn server(&self) -> &ScimServer<P> {
225 &self.server
226 }
227}