use crate::passwords;
use serde::{Deserialize, Serialize};
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "linux")]
use linux::Backend;
#[cfg(target_os = "macos")]
use macos::Backend;
#[cfg(target_os = "windows")]
use windows::Backend;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Secret {
name: String,
value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SecretError {
NotFound,
AlreadyExists,
EmptyValue,
PlatformError(String),
}
pub(crate) trait SecretStoreBackend {
fn add(secret: &Secret) -> Result<(), SecretError>;
fn get(name: &str) -> Result<Secret, SecretError>;
fn remove(name: &str) -> Result<(), SecretError>;
fn update(secret: &Secret) -> Result<(), SecretError>;
}
impl Secret {
pub fn new(name: String, value: String) -> Self {
Secret { name, value }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn value(&self) -> &str {
&self.value
}
}
pub fn add(secret: &Secret) -> Result<(), SecretError> {
Backend::add(secret)
}
pub fn create_random(name: &str) -> Result<Secret, SecretError> {
let generator = passwords::Generator::default();
let secret = Secret::new(name.to_string(), generator.generate(64));
match Backend::add(&secret) {
Ok(_) => Ok(secret),
Err(e) => Err(e),
}
}
pub fn get(name: &str) -> Result<Secret, SecretError> {
Backend::get(name)
}
pub fn remove(name: &str) -> Result<(), SecretError> {
Backend::remove(name)
}
pub fn update(secret: &Secret) -> Result<(), SecretError> {
Backend::update(secret)
}
pub fn upsert(secret: &Secret) -> Result<(), SecretError> {
match Backend::update(secret) {
Ok(_) => Ok(()),
Err(SecretError::NotFound) => Backend::add(secret),
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Cleanup {
name: String,
}
impl Cleanup {
fn new(name: &str) -> Self {
let _ = remove(name);
Cleanup {
name: name.to_string(),
}
}
}
impl Drop for Cleanup {
fn drop(&mut self) {
let _ = remove(&self.name);
}
}
#[test]
fn new_object() {
let _cleanup = Cleanup::new("my_secret");
let secret = Secret::new("my_secret".to_string(), "my_value".to_string());
assert_eq!(secret.name(), "my_secret");
assert_eq!(secret.value(), "my_value");
}
#[test]
fn add_secret() {
let _cleanup = Cleanup::new("test_secret");
let secret = Secret::new("test_secret".to_string(), "test_value".to_string());
let result = add(&secret);
assert!(result.is_ok());
let retrieved = get("test_secret").unwrap();
assert_eq!(retrieved, secret);
}
#[test]
fn add_random_secret() {
let _cleanup = Cleanup::new("random_secret");
let secret = create_random("random_secret").unwrap();
assert_eq!(secret.name(), "random_secret");
let retrieved = get("random_secret").unwrap();
assert_eq!(retrieved, secret);
assert!(retrieved.value().len() == 64);
}
#[test]
fn update_secret() {
let _cleanup = Cleanup::new("update_secret");
let secret = Secret::new("update_secret".to_string(), "initial_value".to_string());
let result = add(&secret);
assert!(result.is_ok());
let updated_secret = Secret::new("update_secret".to_string(), "updated_value".to_string());
let update_result = update(&updated_secret);
assert!(update_result.is_ok());
let retrieved = get("update_secret").unwrap();
assert_eq!(retrieved, updated_secret);
}
#[test]
fn duplicate_secret() {
let _cleanup = Cleanup::new("duplicate_secret");
let secret = Secret::new("duplicate_secret".to_string(), "value1".to_string());
let result1 = add(&secret);
assert!(result1.is_ok());
let duplicate_secret = Secret::new("duplicate_secret".to_string(), "value2".to_string());
let result2 = add(&duplicate_secret);
assert!(matches!(result2, Err(SecretError::AlreadyExists)));
let retrieved = get("duplicate_secret").unwrap();
assert_eq!(retrieved, secret);
}
#[test]
fn remove_nonexistent_secret() {
let result = remove("nonexistent_secret");
assert!(matches!(result, Err(SecretError::NotFound)));
}
}