trust-registry 0.20.0

Trust Registry
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::{Arc, RwLock};

use crate::domain::key::TrustRecordKey;
use crate::domain::*;
use crate::storage::repository::*;

#[derive(Clone)]
pub struct LocalStorage {
    records: Arc<RwLock<HashMap<TrustRecordKey, TrustRecord>>>,
}

impl LocalStorage {
    pub fn new() -> Self {
        Self {
            records: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub async fn save(&self, record: TrustRecord) -> Result<(), RepositoryError> {
        let key = TrustRecordKey::from_record(&record);
        let mut records = self
            .records
            .write()
            .map_err(|_| RepositoryError::LockPoisoned)?;

        match records.entry(key.clone()) {
            Entry::Occupied(e) => Err(RepositoryError::RecordAlreadyExists(e.key().to_string())),
            Entry::Vacant(_) => {
                records.insert(key, record);
                Ok(())
            }
        }
    }

    pub fn with_records(records: Vec<TrustRecord>) -> Result<Self, RepositoryError> {
        let storage = Self::new();
        for record in records {
            let key = TrustRecordKey::from_record(&record);
            storage
                .records
                .write()
                .map_err(|_| RepositoryError::LockPoisoned)?
                .insert(key, record);
        }
        Ok(storage)
    }

    pub fn clear(&self) -> Result<(), RepositoryError> {
        self.records
            .write()
            .map_err(|_| RepositoryError::LockPoisoned)?
            .clear();
        Ok(())
    }

    fn matches_query(record: &TrustRecord, query: &TrustRecordQuery) -> bool {
        record.entity_id() == &query.entity_id
            && record.authority_id() == &query.authority_id
            && record.action() == &query.action
            && record.resource() == &query.resource
    }
}

impl Default for LocalStorage {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait::async_trait]
impl TrustRecordRepository for LocalStorage {
    async fn find_by_query(
        &self,
        query: TrustRecordQuery,
    ) -> Result<Option<TrustRecord>, RepositoryError> {
        let records = self
            .records
            .read()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        let result = records
            .values()
            .find(|&record| Self::matches_query(record, &query))
            .cloned();
        Ok(result)
    }
}

#[async_trait::async_trait]
impl TrustRecordAdminRepository for LocalStorage {
    async fn create(&self, record: TrustRecord) -> Result<(), RepositoryError> {
        let key = TrustRecordKey::from_record(&record);
        let mut records = self
            .records
            .write()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        if records.contains_key(&key) {
            return Err(RepositoryError::RecordAlreadyExists(format!(
                "Record already exists: {}#{}#{}#{}",
                record.authority_id(),
                record.action(),
                record.resource(),
                record.entity_id()
            )));
        }
        records.insert(key, record);
        Ok(())
    }

    async fn update(&self, record: TrustRecord) -> Result<(), RepositoryError> {
        let key = TrustRecordKey::from_record(&record);
        let mut records = self
            .records
            .write()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        if !records.contains_key(&key) {
            return Err(RepositoryError::RecordNotFound(format!(
                "Record not found: {}#{}#{}#{}",
                record.authority_id(),
                record.action(),
                record.resource(),
                record.entity_id()
            )));
        }
        records.insert(key, record);
        Ok(())
    }

    async fn delete(&self, query: TrustRecordQuery) -> Result<(), RepositoryError> {
        let key = TrustRecordKey::from_query(&query);
        let mut records = self
            .records
            .write()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        if records.remove(&key).is_none() {
            return Err(RepositoryError::RecordNotFound(format!(
                "Record not found: {}#{}#{}#{}",
                query.authority_id, query.action, query.resource, query.entity_id
            )));
        }
        Ok(())
    }

    async fn list(&self) -> Result<TrustRecordList, RepositoryError> {
        let records = self
            .records
            .read()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        let records_vec: Vec<TrustRecord> = records.values().cloned().collect();
        Ok(TrustRecordList::new(records_vec))
    }

    async fn read(&self, query: TrustRecordQuery) -> Result<TrustRecord, RepositoryError> {
        let records = self
            .records
            .read()
            .map_err(|_| RepositoryError::LockPoisoned)?;
        let result = records
            .values()
            .find(|&record| Self::matches_query(record, &query))
            .cloned();

        result.ok_or_else(|| {
            RepositoryError::RecordNotFound(format!(
                "Record not found: {}#{}#{}#{}",
                query.authority_id, query.action, query.resource, query.entity_id
            ))
        })
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use super::*;

    fn create_test_record(
        entity: &str,
        authority: &str,
        action: &str,
        resource: &str,
        recognized: bool,
        verified: bool,
        record_type: &str,
    ) -> TrustRecord {
        TrustRecordBuilder::new()
            .entity_id(EntityId::new(entity))
            .authority_id(AuthorityId::new(authority))
            .action(Action::new(action))
            .resource(Resource::new(resource))
            .recognized(recognized)
            .authorized(verified)
            .record_type(RecordType::from_str(record_type).unwrap())
            .build()
            .unwrap()
    }

    #[tokio::test]
    async fn test_find_by_query_filters_records() {
        let storage = LocalStorage::with_records(vec![
            create_test_record(
                "entity-1",
                "authority-1",
                "action-1",
                "resource-1",
                true,
                true,
                "authorization",
            ),
            create_test_record(
                "entity-2",
                "authority-2",
                "action-2",
                "resource-2",
                false,
                false,
                "recognition",
            ),
        ])
        .unwrap();

        let query = TrustRecordQuery::new(
            EntityId::new("entity-1"),
            AuthorityId::new("authority-1"),
            Action::new("action-1"),
            Resource::new("resource-1"),
        );

        let result = storage.find_by_query(query).await.unwrap();
        assert!(result.is_some());
        let record = result.unwrap();
        assert_eq!(record.action().as_str(), "action-1");
        assert_eq!(record.resource().as_str(), "resource-1");
    }
}