use serde::{Deserialize, Serialize};
use crate::common::{GrantId, Provider};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CustomAuthSettings {
Google {
refresh_token: String,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
client_secret: Option<String>,
},
Microsoft {
refresh_token: String,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
client_secret: Option<String>,
},
Imap {
imap_host: String,
imap_port: u16,
imap_username: String,
imap_password: String,
smtp_host: String,
smtp_port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
smtp_username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
smtp_password: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GrantSettings {
AccessToken {
access_token: String,
},
Custom(CustomAuthSettings),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GrantStatus {
Valid,
Invalid,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Grant {
pub id: GrantId,
pub provider: Provider,
#[serde(skip_serializing_if = "Option::is_none")]
pub grant_status: Option<GrantStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub settings: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateGrantRequest {
pub provider: Provider,
pub settings: GrantSettings,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<GrantStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl CreateGrantRequest {
pub fn builder(provider: Provider) -> CreateGrantRequestBuilder {
CreateGrantRequestBuilder::new(provider)
}
pub fn from_access_token(provider: Provider, access_token: String) -> Self {
Self {
provider,
settings: GrantSettings::AccessToken { access_token },
state: None,
scope: None,
metadata: None,
}
}
pub fn from_custom_auth(provider: Provider, settings: CustomAuthSettings) -> Self {
Self {
provider,
settings: GrantSettings::Custom(settings),
state: None,
scope: None,
metadata: None,
}
}
}
#[derive(Debug, Clone)]
pub struct CreateGrantRequestBuilder {
provider: Provider,
settings: Option<GrantSettings>,
grant_status: Option<GrantStatus>,
scope: Option<Vec<String>>,
metadata: Option<serde_json::Value>,
}
impl CreateGrantRequestBuilder {
pub fn new(provider: Provider) -> Self {
Self {
provider,
settings: None,
grant_status: None,
scope: None,
metadata: None,
}
}
pub fn settings(mut self, settings: GrantSettings) -> Self {
self.settings = Some(settings);
self
}
pub fn access_token(mut self, token: String) -> Self {
self.settings = Some(GrantSettings::AccessToken {
access_token: token,
});
self
}
pub fn custom_auth(mut self, auth: CustomAuthSettings) -> Self {
self.settings = Some(GrantSettings::Custom(auth));
self
}
pub fn grant_status(mut self, status: GrantStatus) -> Self {
self.grant_status = Some(status);
self
}
pub fn scope(mut self, scope: Vec<String>) -> Self {
self.scope = Some(scope);
self
}
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> CreateGrantRequest {
CreateGrantRequest {
provider: self.provider,
settings: self.settings.expect("settings are required"),
state: self.grant_status,
scope: self.scope,
metadata: self.metadata,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct UpdateGrantRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub settings: Option<GrantSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<GrantStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl UpdateGrantRequest {
pub fn builder() -> UpdateGrantRequestBuilder {
UpdateGrantRequestBuilder::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct UpdateGrantRequestBuilder {
settings: Option<GrantSettings>,
state: Option<GrantStatus>,
scope: Option<Vec<String>>,
metadata: Option<serde_json::Value>,
}
impl UpdateGrantRequestBuilder {
pub fn settings(mut self, settings: GrantSettings) -> Self {
self.settings = Some(settings);
self
}
pub fn state(mut self, state: GrantStatus) -> Self {
self.state = Some(state);
self
}
pub fn scope(mut self, scope: Vec<String>) -> Self {
self.scope = Some(scope);
self
}
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> UpdateGrantRequest {
UpdateGrantRequest {
settings: self.settings,
state: self.state,
scope: self.scope,
metadata: self.metadata,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grant_serialization() {
let grant = Grant {
id: GrantId::new("grant_123"),
provider: Provider::Google,
grant_status: Some(GrantStatus::Valid),
email: Some("user@example.com".to_string()),
scope: Some(vec![
"https://www.googleapis.com/auth/gmail.readonly".to_string()
]),
user_timezone: None,
created_at: Some(1234567890),
updated_at: Some(1234567890),
provider_user_id: None,
ip: None,
state: None,
user_agent: None,
settings: None,
metadata: None,
};
let json = serde_json::to_string(&grant).unwrap();
assert!(json.contains("grant_123"));
assert!(json.contains("google"));
assert!(json.contains("user@example.com"));
}
#[test]
fn test_grant_deserialization() {
let json = r#"{
"id": "grant_123",
"provider": "google",
"grant_status": "valid",
"email": "user@example.com",
"scope": ["https://www.googleapis.com/auth/gmail.readonly"],
"created_at": 1234567890,
"updated_at": 1234567890
}"#;
let grant: Grant = serde_json::from_str(json).unwrap();
assert_eq!(grant.id.as_str(), "grant_123");
assert_eq!(grant.provider, Provider::Google);
assert_eq!(grant.grant_status, Some(GrantStatus::Valid));
assert_eq!(grant.email, Some("user@example.com".to_string()));
}
#[test]
fn test_grant_status_serialization() {
assert_eq!(
serde_json::to_string(&GrantStatus::Valid).unwrap(),
r#""valid""#
);
assert_eq!(
serde_json::to_string(&GrantStatus::Invalid).unwrap(),
r#""invalid""#
);
}
#[test]
fn test_create_grant_request_builder() {
let request = CreateGrantRequest::builder(Provider::Google)
.access_token("token_123".to_string())
.scope(vec![
"https://www.googleapis.com/auth/gmail.readonly".to_string()
])
.grant_status(GrantStatus::Valid)
.build();
assert_eq!(request.provider, Provider::Google);
assert!(request.scope.is_some());
assert_eq!(request.state, Some(GrantStatus::Valid));
assert!(request.metadata.is_none());
}
#[test]
fn test_create_grant_request_with_metadata() {
let metadata = serde_json::json!({"team": "engineering"});
let request = CreateGrantRequest::builder(Provider::Microsoft)
.access_token("token_456".to_string())
.metadata(metadata.clone())
.build();
assert_eq!(request.provider, Provider::Microsoft);
assert_eq!(request.metadata, Some(metadata));
}
#[test]
fn test_create_grant_from_access_token() {
let request =
CreateGrantRequest::from_access_token(Provider::Google, "token_123".to_string());
assert_eq!(request.provider, Provider::Google);
assert!(matches!(
request.settings,
GrantSettings::AccessToken { .. }
));
}
#[test]
fn test_create_grant_from_custom_auth() {
let custom_auth = CustomAuthSettings::Imap {
imap_host: "imap.example.com".to_string(),
imap_port: 993,
imap_username: "user@example.com".to_string(),
imap_password: "password".to_string(),
smtp_host: "smtp.example.com".to_string(),
smtp_port: 587,
smtp_username: None,
smtp_password: None,
};
let request = CreateGrantRequest::from_custom_auth(Provider::Imap, custom_auth);
assert_eq!(request.provider, Provider::Imap);
assert!(matches!(request.settings, GrantSettings::Custom(_)));
}
#[test]
fn test_update_grant_request_builder() {
let metadata = serde_json::json!({"updated": true});
let request = UpdateGrantRequest::builder()
.metadata(metadata.clone())
.scope(vec!["new_scope".to_string()])
.state(GrantStatus::Invalid)
.build();
assert_eq!(request.metadata, Some(metadata));
assert_eq!(request.scope, Some(vec!["new_scope".to_string()]));
assert_eq!(request.state, Some(GrantStatus::Invalid));
assert!(request.settings.is_none());
}
#[test]
fn test_update_grant_request_default() {
let request = UpdateGrantRequest::default();
assert!(request.settings.is_none());
assert!(request.state.is_none());
assert!(request.scope.is_none());
assert!(request.metadata.is_none());
}
}