use crate::database::{DbAccessToken, DbAuditLog, DbAuthCode, DbOAuthClient}; use crate::domain::models::*; use anyhow::Result; /// Data Mapper pattern - responsible for moving data between domain objects and database pub trait DataMapper { fn to_domain(&self, db_model: Database) -> Result; fn to_database(&self, domain_model: &Domain) -> Result; } /// OAuth Client Data Mapper pub struct OAuthClientMapper; impl DataMapper for OAuthClientMapper { fn to_domain(&self, db_client: DbOAuthClient) -> Result { let redirect_uris: Vec = serde_json::from_str(&db_client.redirect_uris)?; let scopes: Vec = db_client .scopes .split_whitespace() .map(|s| s.to_string()) .collect(); let grant_types: Vec = db_client .grant_types .split_whitespace() .map(|s| s.to_string()) .collect(); let response_types: Vec = db_client .response_types .split_whitespace() .map(|s| s.to_string()) .collect(); Ok(OAuthClient { client_id: db_client.client_id, client_name: db_client.client_name, redirect_uris, scopes, grant_types, response_types, is_active: db_client.is_active, created_at: db_client.created_at, updated_at: db_client.updated_at, }) } fn to_database(&self, client: &OAuthClient) -> Result { Ok(DbOAuthClient { id: 0, // Will be set by database client_id: client.client_id.clone(), client_secret_hash: String::new(), // Will be set separately client_name: client.client_name.clone(), redirect_uris: serde_json::to_string(&client.redirect_uris)?, scopes: client.scopes.join(" "), grant_types: client.grant_types.join(" "), response_types: client.response_types.join(" "), created_at: client.created_at, updated_at: client.updated_at, is_active: client.is_active, }) } } /// Authorization Code Data Mapper pub struct AuthCodeMapper; impl DataMapper for AuthCodeMapper { fn to_domain(&self, db_code: DbAuthCode) -> Result { let scopes = db_code .scope .map(|s| { s.split_whitespace() .map(|scope| scope.to_string()) .collect() }) .unwrap_or_default(); Ok(AuthorizationCode { code: db_code.code, client_id: db_code.client_id, user_id: db_code.user_id, redirect_uri: db_code.redirect_uri, scopes, expires_at: db_code.expires_at, created_at: db_code.created_at, is_used: db_code.is_used, code_challenge: db_code.code_challenge, code_challenge_method: db_code.code_challenge_method, }) } fn to_database(&self, code: &AuthorizationCode) -> Result { let scope = if code.scopes.is_empty() { None } else { Some(code.scopes.join(" ")) }; Ok(DbAuthCode { id: 0, // Will be set by database code: code.code.clone(), client_id: code.client_id.clone(), user_id: code.user_id.clone(), redirect_uri: code.redirect_uri.clone(), scope, expires_at: code.expires_at, created_at: code.created_at, is_used: code.is_used, code_challenge: code.code_challenge.clone(), code_challenge_method: code.code_challenge_method.clone(), }) } } /// Access Token Data Mapper pub struct AccessTokenMapper; impl DataMapper for AccessTokenMapper { fn to_domain(&self, db_token: DbAccessToken) -> Result { let scopes = db_token .scope .map(|s| { s.split_whitespace() .map(|scope| scope.to_string()) .collect() }) .unwrap_or_default(); Ok(AccessToken { token_id: db_token.token_id, client_id: db_token.client_id, user_id: db_token.user_id, scopes, expires_at: db_token.expires_at, created_at: db_token.created_at, is_revoked: db_token.is_revoked, }) } fn to_database(&self, token: &AccessToken) -> Result { let scope = if token.scopes.is_empty() { None } else { Some(token.scopes.join(" ")) }; Ok(DbAccessToken { id: 0, // Will be set by database token_id: token.token_id.clone(), client_id: token.client_id.clone(), user_id: token.user_id.clone(), scope, expires_at: token.expires_at, created_at: token.created_at, is_revoked: token.is_revoked, token_hash: String::new(), // Will be set by service layer }) } } /// Audit Event Data Mapper pub struct AuditEventMapper; impl DataMapper for AuditEventMapper { fn to_domain(&self, db_log: DbAuditLog) -> Result { Ok(AuditEvent { event_type: db_log.event_type, client_id: db_log.client_id, user_id: db_log.user_id, ip_address: db_log.ip_address, user_agent: db_log.user_agent, details: db_log.details, success: db_log.success, timestamp: db_log.created_at, }) } fn to_database(&self, event: &AuditEvent) -> Result { Ok(DbAuditLog { id: 0, // Will be set by database event_type: event.event_type.clone(), client_id: event.client_id.clone(), user_id: event.user_id.clone(), ip_address: event.ip_address.clone(), user_agent: event.user_agent.clone(), details: event.details.clone(), created_at: event.timestamp, success: event.success, }) } } /// Registry of all data mappers pub struct MapperRegistry { client_mapper: OAuthClientMapper, auth_code_mapper: AuthCodeMapper, access_token_mapper: AccessTokenMapper, audit_event_mapper: AuditEventMapper, } impl MapperRegistry { pub fn new() -> Self { Self { client_mapper: OAuthClientMapper, auth_code_mapper: AuthCodeMapper, access_token_mapper: AccessTokenMapper, audit_event_mapper: AuditEventMapper, } } pub fn client_mapper(&self) -> &OAuthClientMapper { &self.client_mapper } pub fn auth_code_mapper(&self) -> &AuthCodeMapper { &self.auth_code_mapper } pub fn access_token_mapper(&self) -> &AccessTokenMapper { &self.access_token_mapper } pub fn audit_event_mapper(&self) -> &AuditEventMapper { &self.audit_event_mapper } } impl Default for MapperRegistry { fn default() -> Self { Self::new() } }