use crate::error::{Error, ErrorCode, Result};
use keyring::Entry;
use serde_json::Value;
const SERVICE_NAME: &str = "homeboy";
fn keyring_error(e: keyring::Error) -> Error {
Error::new(
ErrorCode::InternalUnexpected,
format!("Keychain error: {}", e),
Value::Null,
)
}
pub fn store(project_id: &str, variable_name: &str, value: &str) -> Result<()> {
let key = format!("{}:{}", project_id, variable_name);
let entry = Entry::new(SERVICE_NAME, &key).map_err(keyring_error)?;
entry.set_password(value).map_err(keyring_error)?;
Ok(())
}
pub fn get(project_id: &str, variable_name: &str) -> Result<Option<String>> {
let key = format!("{}:{}", project_id, variable_name);
let entry = Entry::new(SERVICE_NAME, &key).map_err(keyring_error)?;
match entry.get_password() {
Ok(value) => Ok(Some(value)),
Err(keyring::Error::NoEntry) => Ok(None),
Err(e) => Err(keyring_error(e)),
}
}
pub fn delete(project_id: &str, variable_name: &str) -> Result<()> {
let key = format!("{}:{}", project_id, variable_name);
let entry = Entry::new(SERVICE_NAME, &key).map_err(keyring_error)?;
match entry.delete_credential() {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(keyring_error(e)),
}
}
pub fn exists(project_id: &str, variable_name: &str) -> bool {
get(project_id, variable_name)
.map(|v| v.is_some())
.unwrap_or(false)
}
pub fn clear_project(project_id: &str, variable_names: &[&str]) -> Result<()> {
for name in variable_names {
let _ = delete(project_id, name);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore]
fn test_store_and_get() {
let project_id = "test-project";
let var_name = "test_token";
let value = "secret_value_123";
store(project_id, var_name, value).unwrap();
let retrieved = get(project_id, var_name).unwrap();
assert_eq!(retrieved, Some(value.to_string()));
delete(project_id, var_name).unwrap();
let after_delete = get(project_id, var_name).unwrap();
assert_eq!(after_delete, None);
}
}