mcp_gmailcal/config.rs
1use crate::errors::ConfigError;
2use dotenv::dotenv;
3use log::debug;
4use std::env;
5
6#[derive(Debug, Clone)]
7pub struct Config {
8 pub client_id: String,
9 pub client_secret: String,
10 pub refresh_token: String,
11 pub access_token: Option<String>,
12
13 /// Number of seconds before an access token expires that it should be refreshed.
14 /// Allows for proactive refresh to prevent working with nearly-expired tokens.
15 /// Can be configured with TOKEN_REFRESH_THRESHOLD_SECONDS environment variable.
16 pub token_refresh_threshold: u64,
17
18 /// Buffer time in seconds subtracted from the token's expiration time to ensure
19 /// we don't use tokens too close to their expiry time. Provides a safety margin.
20 /// Can be configured with TOKEN_EXPIRY_BUFFER_SECONDS environment variable.
21 pub token_expiry_buffer: u64,
22}
23
24impl Config {
25 pub fn from_env() -> Result<Self, ConfigError> {
26 // Attempt to load .env file if present
27 // If DOTENV_PATH is set, use that path, otherwise use default
28 if let Ok(path) = std::env::var("DOTENV_PATH") {
29 let _ = dotenv::from_path(path);
30 } else {
31 let _ = dotenv();
32 }
33
34 debug!("Loading Gmail OAuth configuration from environment");
35
36 // Get required variables
37 let client_id = env::var("GMAIL_CLIENT_ID")
38 .map_err(|_| ConfigError::MissingEnvVar("GMAIL_CLIENT_ID".to_string()))?;
39
40 let client_secret = env::var("GMAIL_CLIENT_SECRET")
41 .map_err(|_| ConfigError::MissingEnvVar("GMAIL_CLIENT_SECRET".to_string()))?;
42
43 let refresh_token = env::var("GMAIL_REFRESH_TOKEN")
44 .map_err(|_| ConfigError::MissingEnvVar("GMAIL_REFRESH_TOKEN".to_string()))?;
45
46 // Get optional access token
47 let access_token = env::var("GMAIL_ACCESS_TOKEN").ok();
48
49 // Get token expiry configuration with defaults
50 let token_refresh_threshold = get_token_refresh_threshold_seconds();
51 let token_expiry_buffer = get_token_expiry_buffer_seconds();
52
53 debug!("OAuth configuration loaded successfully");
54 debug!("Token refresh threshold: {} seconds", token_refresh_threshold);
55 debug!("Token expiry buffer: {} seconds", token_expiry_buffer);
56
57 Ok(Config {
58 client_id,
59 client_secret,
60 refresh_token,
61 access_token,
62 token_refresh_threshold,
63 token_expiry_buffer,
64 })
65 }
66}
67
68// API URL constants
69pub const GMAIL_API_BASE_URL: &str = "https://gmail.googleapis.com/gmail/v1";
70pub const OAUTH_TOKEN_URL: &str = "https://oauth2.googleapis.com/token";
71
72// Configuration utility functions
73
74/// Returns the total token expiry time in seconds.
75///
76/// This value controls how long the application considers the token valid.
77/// Default is 3540 seconds (59 minutes) if not configured.
78///
79/// Environment variable: TOKEN_EXPIRY_SECONDS
80pub fn get_token_expiry_seconds() -> u64 {
81 std::env::var("TOKEN_EXPIRY_SECONDS")
82 .ok()
83 .and_then(|s| s.parse::<u64>().ok())
84 .unwrap_or(3540) // Default 59 minutes if not configured
85}
86
87/// Returns the buffer time in seconds subtracted from token expiry time.
88///
89/// This buffer ensures we don't use tokens that are too close to expiry.
90/// It's subtracted from the token's actual expiry time to create a safety margin.
91/// Default is 60 seconds (1 minute) if not configured.
92///
93/// Environment variable: TOKEN_EXPIRY_BUFFER_SECONDS
94pub fn get_token_expiry_buffer_seconds() -> u64 {
95 std::env::var("TOKEN_EXPIRY_BUFFER_SECONDS")
96 .ok()
97 .and_then(|s| s.parse::<u64>().ok())
98 .unwrap_or(60) // Default 1 minute if not configured
99}
100
101/// Returns the threshold in seconds before token expiry that a refresh should be triggered.
102///
103/// This controls how soon before a token expires that the application should proactively
104/// refresh it. This prevents using nearly-expired tokens which might expire during operations.
105/// Default is 300 seconds (5 minutes) if not configured.
106///
107/// Environment variable: TOKEN_REFRESH_THRESHOLD_SECONDS
108pub fn get_token_refresh_threshold_seconds() -> u64 {
109 std::env::var("TOKEN_REFRESH_THRESHOLD_SECONDS")
110 .ok()
111 .and_then(|s| s.parse::<u64>().ok())
112 .unwrap_or(300) // Default 5 minutes if not configured
113}