use serde::{Deserialize, Serialize}; /// API DTOs for OAuth2 endpoints - these define the wire format /// Separate from domain models to allow API versioning without affecting business logic #[derive(Debug, Serialize, Deserialize)] pub struct AuthorizeRequestDto { pub client_id: String, pub redirect_uri: String, pub response_type: String, pub scope: Option, pub state: Option, pub code_challenge: Option, pub code_challenge_method: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct TokenRequestDto { pub grant_type: String, pub code: Option, pub refresh_token: Option, pub redirect_uri: Option, pub client_id: Option, pub client_secret: Option, pub code_verifier: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct TokenResponseDto { pub access_token: String, pub token_type: String, pub expires_in: u64, #[serde(skip_serializing_if = "Option::is_none")] pub refresh_token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct ErrorResponseDto { pub error: String, #[serde(skip_serializing_if = "Option::is_none")] pub error_description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub error_uri: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct IntrospectionRequestDto { pub token: String, #[serde(skip_serializing_if = "Option::is_none")] pub token_type_hint: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct IntrospectionResponseDto { pub active: bool, #[serde(skip_serializing_if = "Option::is_none")] pub client_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub username: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] pub exp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub iat: Option, #[serde(skip_serializing_if = "Option::is_none")] pub sub: Option, #[serde(skip_serializing_if = "Option::is_none")] pub aud: Option, #[serde(skip_serializing_if = "Option::is_none")] pub iss: Option, #[serde(skip_serializing_if = "Option::is_none")] pub jti: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct RevocationRequestDto { pub token: String, #[serde(skip_serializing_if = "Option::is_none")] pub token_type_hint: Option, } // Conversion traits between DTOs and domain models impl From for AuthorizeRequestDto { fn from(req: crate::domain::AuthorizationRequest) -> Self { Self { client_id: req.client_id, redirect_uri: req.redirect_uri, response_type: req.response_type, scope: req.scope, state: req.state, code_challenge: req.code_challenge, code_challenge_method: req.code_challenge_method, } } } impl From for crate::domain::AuthorizationRequest { fn from(dto: AuthorizeRequestDto) -> Self { Self { client_id: dto.client_id, redirect_uri: dto.redirect_uri, response_type: dto.response_type, scope: dto.scope, state: dto.state, code_challenge: dto.code_challenge, code_challenge_method: dto.code_challenge_method, } } } impl From for TokenResponseDto { fn from(result: crate::domain::TokenResult) -> Self { Self { access_token: result.access_token, token_type: result.token_type, expires_in: result.expires_in, refresh_token: result.refresh_token, scope: result.scope, } } } impl From for ErrorResponseDto { fn from(error: crate::domain::OAuthError) -> Self { Self { error: error.error_code, error_description: error.description, error_uri: error.uri, } } }