keynest 0.4.0

Simple, offline, cross-platform secrets manager written in Rust
Documentation
//! In-memory secret storage.

use crate::error::StoreError;
use chrono::Local;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// In-memory secret store.
///
/// Holds all secrets in a HashMap with metadata.
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Store {
    secrets: HashMap<String, SecretEntry>,
    creation_date: String,
}

/// A single secret entry with key, value, and timestamp.
#[derive(Serialize, Deserialize, Debug)]
pub struct SecretEntry {
    key: String,
    value: String,
    updated: String,
}

impl SecretEntry {
    pub(crate) fn new(key: String, value: String) -> Self {
        Self {
            key,
            value,
            updated: Local::now().to_string(),
        }
    }

    /// Returns the secret key.
    pub fn key(&self) -> &str {
        &self.key
    }

    /// Returns the secret value.
    pub fn value(&self) -> &str {
        &self.value
    }

    /// Returns the last update timestamp.
    pub fn updated(&self) -> &str {
        &self.updated
    }

    pub(crate) fn update_value(&mut self, new_value: String) {
        self.value = new_value;
        self.updated = Local::now().to_string();
    }
}

impl Store {
    /// Creates a new empty store.
    pub fn new() -> Self {
        Store {
            secrets: HashMap::new(),
            creation_date: Local::now().to_string(),
        }
    }

    /// Stores a secret.
    ///
    /// # Errors
    ///
    /// Returns `StoreError::KeyAlreadyExists` if key already exists.
    pub fn set(&mut self, key: &str, value: &str) -> Result<(), StoreError> {
        if self.secrets.contains_key(key) {
            Err(StoreError::KeyAlreadyExists(key.to_string()))
        } else {
            self.secrets.insert(
                key.to_string(),
                SecretEntry::new(key.to_string(), value.to_string()),
            );
            Ok(())
        }
    }

    /// Retrieves a secret by key.
    pub fn get(&self, key: &str) -> Option<&str> {
        self.secrets.get(key).map(|e| e.value())
    }

    /// Removes a secret.
    ///
    /// # Errors
    ///
    /// Returns `StoreError::KeyNotFound` if key doesn't exist.
    pub fn remove(&mut self, key: &str) -> Result<(), StoreError> {
        if self.secrets.remove(key).is_some() {
            Ok(())
        } else {
            Err(StoreError::KeyNotFound(key.to_string()))
        }
    }

    /// Updates an existing secret.
    ///
    /// # Errors
    ///
    /// Returns `StoreError::KeyNotFound` if key doesn't exist.
    pub fn update(&mut self, key: &str, value: &str) -> Result<(), StoreError> {
        match self.secrets.get_mut(key) {
            Some(secret) => {
                secret.update_value(value.to_string());
                Ok(())
            }
            None => Err(StoreError::KeyNotFound(key.to_string())),
        }
    }

    /// Returns an iterator over all keys.
    pub fn keys(&self) -> impl Iterator<Item = &String> {
        self.secrets.keys()
    }

    /// Returns an iterator over all secret entries.
    pub fn entries(&self) -> impl Iterator<Item = &SecretEntry> {
        self.secrets.values()
    }

    /// Returns the creation date of the store.
    pub fn creation_date(&self) -> &str {
        &self.creation_date
    }

    /// Returns the number of secrets stored.
    pub fn len(&self) -> usize {
        self.secrets.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create_new_store_works() {
        let store = Store::new();
        assert_eq!(store.secrets.keys().count(), 0);
        assert_ne!(store.creation_date, "");
    }

    #[test]
    fn set_key_works() {
        let mut store = Store::new();
        store.set("A", "B").unwrap();
        assert_eq!(store.get("A").unwrap(), "B");
    }

    #[test]
    fn set_existing_key_fails() {
        let mut store = Store::new();
        store.set("A", "B").unwrap();
        match store.set("A", "C") {
            Err(StoreError::KeyAlreadyExists(k)) => assert_eq!(k, "A"),
            other => panic!("expected KeyAlreadyExists, got: {other:?}"),
        }
    }

    #[test]
    fn update_key_works() {
        let mut store = Store::new();
        store.set("A", "B").unwrap();
        store.update("A", "C").unwrap();
        assert_eq!(store.get("A").unwrap(), "C");
    }

    #[test]
    fn update_not_existing_key_fails() {
        let mut store = Store::new();
        match store.update("A", "B") {
            Err(StoreError::KeyNotFound(k)) => assert_eq!(k, "A"),
            other => panic!("expected KeyNotFound, got: {other:?}"),
        }
    }

    #[test]
    fn remove_key_works() {
        let mut store = Store::new();
        store.set("A", "B").unwrap();
        store.remove("A").unwrap();
        assert_eq!(store.get("A"), None);
    }

    #[test]
    fn remove_not_existing_key_fails() {
        let mut store = Store::new();
        match store.remove("A") {
            Err(StoreError::KeyNotFound(k)) => assert_eq!(k, "A"),
            other => panic!("expected KeyNotFound, got: {other:?}"),
        }
    }

    #[test]
    fn get_key_works() {
        let mut store = Store::new();
        store.set("A", "B").unwrap();
        assert_eq!(store.get("A").unwrap(), "B");
    }

    #[test]
    fn get_not_existing_key_fails() {
        let store = Store::new();
        assert_eq!(store.get("A"), None);
    }
}