use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Secret {
pub id: Uuid,
pub key: String,
#[serde(skip_serializing)]
pub value: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretMetadata {
pub id: Uuid,
pub key: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub const DEFAULT_ROTATION_BATCH_SIZE: u32 = 100;
pub const MAX_ROTATION_BATCH_SIZE: u32 = 1000;
#[derive(Debug, Clone)]
pub struct RotationRequest {
pub to_version: i32,
pub batch_size: u32,
pub after_id: Option<Uuid>,
}
impl RotationRequest {
pub fn new(to_version: i32) -> Self {
Self {
to_version,
batch_size: DEFAULT_ROTATION_BATCH_SIZE,
after_id: None,
}
}
pub fn with_batch_size(mut self, batch_size: u32) -> Self {
self.batch_size = batch_size;
self
}
pub fn after(mut self, id: Uuid) -> Self {
self.after_id = Some(id);
self
}
pub fn effective_batch_size(&self) -> u32 {
self.batch_size.clamp(1, MAX_ROTATION_BATCH_SIZE)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotationBatch {
pub to_version: i32,
pub rotated: u64,
pub failed: u64,
pub remaining: u64,
pub last_id: Option<Uuid>,
}
impl RotationBatch {
pub fn is_complete(&self) -> bool {
self.last_id.is_none() || self.remaining == 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyVersionStatus {
pub active: i32,
pub configured: Vec<i32>,
pub in_use: Vec<i32>,
pub missing: Vec<i32>,
pub retirable: Vec<i32>,
}
impl KeyVersionStatus {
pub fn is_consistent(&self) -> bool {
self.missing.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secret_serde_excludes_value() {
let secret = Secret {
id: Uuid::now_v7(),
key: "test/my-secret".to_string(),
value: "super-secret-value-should-not-appear".to_string(),
created_at: Utc::now(),
updated_at: Utc::now(),
};
let json = serde_json::to_string(&secret).expect("serialize");
assert!(!json.contains("super-secret-value-should-not-appear"));
assert!(json.contains("test/my-secret"));
}
#[test]
fn secret_preserves_value_in_struct() {
let secret = Secret {
id: Uuid::now_v7(),
key: "k".to_string(),
value: "v".to_string(),
created_at: Utc::now(),
updated_at: Utc::now(),
};
assert_eq!(secret.value, "v");
}
#[test]
fn secret_metadata_serde_has_no_value() {
let meta = SecretMetadata {
id: Uuid::now_v7(),
key: "my/key".to_string(),
created_at: Utc::now(),
updated_at: Utc::now(),
};
let json = serde_json::to_string(&meta).expect("serialize");
assert!(json.contains("my/key"));
assert!(!json.contains("value"));
}
}