1use crate::storage::{StorageError, StorageKey, StoragePrefix, StorageProvider, StorageStats};
102use serde_json::Value;
103use sqlx::{Row, SqlitePool};
104
105pub struct SqliteStorage {
110 pool: SqlitePool,
111}
112
113impl SqliteStorage {
114 pub async fn new() -> Result<Self, StorageError> {
122 Self::new_with_path("scim_data/scim_server.db").await
123 }
124
125 pub async fn new_with_path(database_path: &str) -> Result<Self, StorageError> {
138 use sqlx::sqlite::SqliteConnectOptions;
139 use std::path::Path;
140
141 if let Some(parent) = Path::new(database_path).parent() {
143 if !parent.exists() {
144 std::fs::create_dir_all(parent).map_err(|e| {
145 StorageError::configuration(format!(
146 "Failed to create directory {}: {}",
147 parent.display(),
148 e
149 ))
150 })?;
151 }
152 }
153
154 let options = SqliteConnectOptions::new()
156 .filename(database_path)
157 .create_if_missing(true)
158 .foreign_keys(true);
159
160 let pool = SqlitePool::connect_with(options).await.map_err(|e| {
161 StorageError::configuration(format!(
162 "Failed to connect to SQLite database at {}: {}",
163 database_path, e
164 ))
165 })?;
166
167 let storage = Self { pool };
168 storage.initialize_schema().await?;
169 Ok(storage)
170 }
171
172 pub async fn new_in_memory() -> Result<Self, StorageError> {
177 let pool = SqlitePool::connect(":memory:").await.map_err(|e| {
178 StorageError::configuration(format!("Failed to create in-memory SQLite: {}", e))
179 })?;
180
181 let storage = Self { pool };
182 storage.initialize_schema().await?;
183 Ok(storage)
184 }
185
186 async fn initialize_schema(&self) -> Result<(), StorageError> {
188 sqlx::query(
189 r#"
190 CREATE TABLE IF NOT EXISTS scim_resources (
191 tenant_id TEXT NOT NULL,
192 resource_type TEXT NOT NULL,
193 resource_id TEXT NOT NULL,
194 data TEXT NOT NULL,
195 PRIMARY KEY (tenant_id, resource_type, resource_id)
196 )
197 "#,
198 )
199 .execute(&self.pool)
200 .await
201 .map_err(|e| StorageError::internal(format!("Failed to create schema: {}", e)))?;
202
203 Ok(())
204 }
205
206 fn extract_attribute_value(data: &Value, attribute_path: &str) -> Option<String> {
208 let parts: Vec<&str> = attribute_path.split('.').collect();
209 let mut current = data;
210
211 for part in parts {
212 if let Ok(index) = part.parse::<usize>() {
213 current = current.get(index)?;
215 } else {
216 current = current.get(part)?;
218 }
219 }
220
221 match current {
223 Value::String(s) => Some(s.clone()),
224 Value::Number(n) => Some(n.to_string()),
225 Value::Bool(b) => Some(b.to_string()),
226 _ => current.as_str().map(|s| s.to_string()),
227 }
228 }
229}
230
231impl StorageProvider for SqliteStorage {
232 type Error = StorageError;
233
234 async fn put(&self, key: StorageKey, data: Value) -> Result<Value, Self::Error> {
235 let data_str = serde_json::to_string(&data)
236 .map_err(|e| StorageError::serialization(format!("Failed to serialize data: {}", e)))?;
237
238 sqlx::query(
239 "INSERT OR REPLACE INTO scim_resources (tenant_id, resource_type, resource_id, data) VALUES (?, ?, ?, ?)"
240 )
241 .bind(key.tenant_id())
242 .bind(key.resource_type())
243 .bind(key.resource_id())
244 .bind(&data_str)
245 .execute(&self.pool)
246 .await
247 .map_err(|e| StorageError::internal(format!("Failed to store resource: {}", e)))?;
248
249 Ok(data)
250 }
251
252 async fn get(&self, key: StorageKey) -> Result<Option<Value>, Self::Error> {
253 let row = sqlx::query(
254 "SELECT data FROM scim_resources WHERE tenant_id = ? AND resource_type = ? AND resource_id = ?"
255 )
256 .bind(key.tenant_id())
257 .bind(key.resource_type())
258 .bind(key.resource_id())
259 .fetch_optional(&self.pool)
260 .await
261 .map_err(|e| StorageError::internal(format!("Failed to fetch resource: {}", e)))?;
262
263 match row {
264 Some(row) => {
265 let data_str: String = row.get("data");
266 let value = serde_json::from_str(&data_str).map_err(|e| {
267 StorageError::serialization(format!("Failed to deserialize data: {}", e))
268 })?;
269 Ok(Some(value))
270 }
271 None => Ok(None),
272 }
273 }
274
275 async fn delete(&self, key: StorageKey) -> Result<bool, Self::Error> {
276 let result = sqlx::query(
277 "DELETE FROM scim_resources WHERE tenant_id = ? AND resource_type = ? AND resource_id = ?"
278 )
279 .bind(key.tenant_id())
280 .bind(key.resource_type())
281 .bind(key.resource_id())
282 .execute(&self.pool)
283 .await
284 .map_err(|e| StorageError::internal(format!("Failed to delete resource: {}", e)))?;
285
286 Ok(result.rows_affected() > 0)
287 }
288
289 async fn list(
290 &self,
291 prefix: StoragePrefix,
292 offset: usize,
293 limit: usize,
294 ) -> Result<Vec<(StorageKey, Value)>, Self::Error> {
295 let rows = sqlx::query(
296 "SELECT resource_id, data FROM scim_resources
297 WHERE tenant_id = ? AND resource_type = ?
298 ORDER BY resource_id
299 LIMIT ? OFFSET ?",
300 )
301 .bind(prefix.tenant_id())
302 .bind(prefix.resource_type())
303 .bind(limit as i64)
304 .bind(offset as i64)
305 .fetch_all(&self.pool)
306 .await
307 .map_err(|e| StorageError::internal(format!("Failed to list resources: {}", e)))?;
308
309 let mut results = Vec::new();
310 for row in rows {
311 let resource_id: String = row.get("resource_id");
312 let data_str: String = row.get("data");
313 let data: Value = serde_json::from_str(&data_str).map_err(|e| {
314 StorageError::serialization(format!("Failed to deserialize data: {}", e))
315 })?;
316
317 let key = StorageKey::new(prefix.tenant_id(), prefix.resource_type(), resource_id);
318 results.push((key, data));
319 }
320
321 Ok(results)
322 }
323
324 async fn find_by_attribute(
325 &self,
326 prefix: StoragePrefix,
327 attribute: &str,
328 value: &str,
329 ) -> Result<Vec<(StorageKey, Value)>, Self::Error> {
330 let rows = sqlx::query(
331 "SELECT resource_id, data FROM scim_resources WHERE tenant_id = ? AND resource_type = ?"
332 )
333 .bind(prefix.tenant_id())
334 .bind(prefix.resource_type())
335 .fetch_all(&self.pool)
336 .await
337 .map_err(|e| StorageError::internal(format!("Failed to search resources: {}", e)))?;
338
339 let mut results = Vec::new();
340 for row in rows {
341 let resource_id: String = row.get("resource_id");
342 let data_str: String = row.get("data");
343 let data: Value = serde_json::from_str(&data_str).map_err(|e| {
344 StorageError::serialization(format!("Failed to deserialize data: {}", e))
345 })?;
346
347 if let Some(attr_value) = Self::extract_attribute_value(&data, attribute) {
348 if attr_value == value {
349 let key =
350 StorageKey::new(prefix.tenant_id(), prefix.resource_type(), resource_id);
351 results.push((key, data));
352 }
353 }
354 }
355
356 Ok(results)
357 }
358
359 async fn exists(&self, key: StorageKey) -> Result<bool, Self::Error> {
360 let row = sqlx::query(
361 "SELECT 1 FROM scim_resources WHERE tenant_id = ? AND resource_type = ? AND resource_id = ?"
362 )
363 .bind(key.tenant_id())
364 .bind(key.resource_type())
365 .bind(key.resource_id())
366 .fetch_optional(&self.pool)
367 .await
368 .map_err(|e| StorageError::internal(format!("Failed to check resource existence: {}", e)))?;
369
370 Ok(row.is_some())
371 }
372
373 async fn count(&self, prefix: StoragePrefix) -> Result<usize, Self::Error> {
374 let row = sqlx::query(
375 "SELECT COUNT(*) as count FROM scim_resources WHERE tenant_id = ? AND resource_type = ?"
376 )
377 .bind(prefix.tenant_id())
378 .bind(prefix.resource_type())
379 .fetch_one(&self.pool)
380 .await
381 .map_err(|e| StorageError::internal(format!("Failed to count resources: {}", e)))?;
382
383 let count: i64 = row.get("count");
384 Ok(count as usize)
385 }
386
387 async fn list_tenants(&self) -> Result<Vec<String>, Self::Error> {
388 let rows = sqlx::query("SELECT DISTINCT tenant_id FROM scim_resources ORDER BY tenant_id")
389 .fetch_all(&self.pool)
390 .await
391 .map_err(|e| StorageError::internal(format!("Failed to list tenants: {}", e)))?;
392
393 let tenants = rows.into_iter().map(|row| row.get("tenant_id")).collect();
394 Ok(tenants)
395 }
396
397 async fn list_resource_types(&self, tenant_id: &str) -> Result<Vec<String>, Self::Error> {
398 let rows = sqlx::query(
399 "SELECT DISTINCT resource_type FROM scim_resources WHERE tenant_id = ? ORDER BY resource_type"
400 )
401 .bind(tenant_id)
402 .fetch_all(&self.pool)
403 .await
404 .map_err(|e| StorageError::internal(format!("Failed to list resource types: {}", e)))?;
405
406 let types = rows
407 .into_iter()
408 .map(|row| row.get("resource_type"))
409 .collect();
410 Ok(types)
411 }
412
413 async fn list_all_resource_types(&self) -> Result<Vec<String>, Self::Error> {
414 let rows =
415 sqlx::query("SELECT DISTINCT resource_type FROM scim_resources ORDER BY resource_type")
416 .fetch_all(&self.pool)
417 .await
418 .map_err(|e| {
419 StorageError::internal(format!("Failed to list all resource types: {}", e))
420 })?;
421
422 let types = rows
423 .into_iter()
424 .map(|row| row.get("resource_type"))
425 .collect();
426 Ok(types)
427 }
428
429 async fn clear(&self) -> Result<(), Self::Error> {
430 sqlx::query("DELETE FROM scim_resources")
431 .execute(&self.pool)
432 .await
433 .map_err(|e| StorageError::internal(format!("Failed to clear storage: {}", e)))?;
434
435 Ok(())
436 }
437
438 async fn stats(&self) -> Result<StorageStats, Self::Error> {
439 let tenant_count_row =
440 sqlx::query("SELECT COUNT(DISTINCT tenant_id) as count FROM scim_resources")
441 .fetch_one(&self.pool)
442 .await
443 .map_err(|e| StorageError::internal(format!("Failed to count tenants: {}", e)))?;
444 let tenant_count: i64 = tenant_count_row.get("count");
445
446 let resource_type_count_row = sqlx::query(
447 "SELECT COUNT(DISTINCT tenant_id || '/' || resource_type) as count FROM scim_resources",
448 )
449 .fetch_one(&self.pool)
450 .await
451 .map_err(|e| StorageError::internal(format!("Failed to count resource types: {}", e)))?;
452 let resource_type_count: i64 = resource_type_count_row.get("count");
453
454 let total_resources_row = sqlx::query("SELECT COUNT(*) as count FROM scim_resources")
455 .fetch_one(&self.pool)
456 .await
457 .map_err(|e| {
458 StorageError::internal(format!("Failed to count total resources: {}", e))
459 })?;
460 let total_resources: i64 = total_resources_row.get("count");
461
462 Ok(StorageStats {
463 tenant_count: tenant_count as usize,
464 resource_type_count: resource_type_count as usize,
465 total_resources: total_resources as usize,
466 })
467 }
468}