use std::time::Duration;
use chrono::{DateTime, Utc};
use ironflow_store::entities::LeaseRequest;
use serde::{Deserialize, Serialize};
use crate::error::ApiError;
pub const DEFAULT_LEASE_TTL_SECS: u64 = 90;
pub const MAX_LEASE_TTL_SECS: u64 = 3600;
#[derive(Debug, Clone, Deserialize)]
pub struct RenewLeaseRequest {
pub worker_id: String,
#[serde(default)]
pub lease_ttl_secs: Option<u64>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RenewLeaseResponse {
pub lease_expires_at: DateTime<Utc>,
}
pub fn validate_lease_ttl(
worker_id: Option<String>,
lease_ttl_secs: Option<u64>,
) -> Result<Option<LeaseRequest>, ApiError> {
let Some(worker_id) = worker_id else {
return Ok(None);
};
let worker_id = worker_id.trim().to_string();
if worker_id.is_empty() {
return Err(ApiError::BadRequest("worker_id must not be blank".into()));
}
let ttl_secs = lease_ttl_secs.unwrap_or(DEFAULT_LEASE_TTL_SECS);
if ttl_secs == 0 || ttl_secs > MAX_LEASE_TTL_SECS {
return Err(ApiError::BadRequest(format!(
"lease_ttl_secs must be between 1 and {MAX_LEASE_TTL_SECS}"
)));
}
Ok(Some(LeaseRequest {
worker_id,
ttl: Duration::from_secs(ttl_secs),
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_worker_id_means_no_lease() {
assert!(validate_lease_ttl(None, Some(90)).unwrap().is_none());
}
#[test]
fn default_ttl_is_applied() {
let lease = validate_lease_ttl(Some("worker-1".to_string()), None)
.unwrap()
.unwrap();
assert_eq!(lease.ttl.as_secs(), DEFAULT_LEASE_TTL_SECS);
assert_eq!(lease.worker_id, "worker-1");
}
#[test]
fn worker_id_is_trimmed() {
let lease = validate_lease_ttl(Some(" worker-1 ".to_string()), None)
.unwrap()
.unwrap();
assert_eq!(lease.worker_id, "worker-1");
}
#[test]
fn blank_worker_id_is_rejected() {
let err = validate_lease_ttl(Some(" ".to_string()), None).unwrap_err();
assert!(matches!(err, ApiError::BadRequest(_)));
}
#[test]
fn zero_ttl_is_rejected() {
let err = validate_lease_ttl(Some("worker-1".to_string()), Some(0)).unwrap_err();
assert!(matches!(err, ApiError::BadRequest(_)));
}
#[test]
fn ttl_above_max_is_rejected() {
let err = validate_lease_ttl(Some("worker-1".to_string()), Some(MAX_LEASE_TTL_SECS + 1))
.unwrap_err();
assert!(matches!(err, ApiError::BadRequest(_)));
}
#[test]
fn max_ttl_is_accepted() {
let lease = validate_lease_ttl(Some("worker-1".to_string()), Some(MAX_LEASE_TTL_SECS))
.unwrap()
.unwrap();
assert_eq!(lease.ttl.as_secs(), MAX_LEASE_TTL_SECS);
}
}