opencloudmesh 0.2.1

Implementation of the OpenCloudMesh protocol
Documentation
// SPDX-FileCopyrightText: 2026 Matthias Kraus <info@opengeomesh.org>
//
// SPDX-License-Identifier: LGPL-3.0-or-later

mod sent;
pub use sent::SentShareRepo;
pub use sent::InMemorySentShareRepo;

use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
    fmt::Debug
};

use ocm_types::{common::OcmAddress, error::Error, share::{NewShare, SendingServer}};
use serde::Serialize;

use super::protocols::MultiProtocol;

pub type ReceivedShare = (NewShare, MultiProtocol);

/// Implement this trait to persist received shares.
///
pub trait ReceivedShareRepo: Debug + Clone + Send + Sync {
    /// Store a new share with the necessary details to access it.
    /// URIs in the [MultiProtocol] are expected to be
    /// [resolved](super::protocols::Protocol::resolve_client_properties) to be relative to the
    /// SendingServer or absolute, to avoid Discovery lookups when accessing the share.
    fn insert(
        &self,
        sending_server: SendingServer,
        share: ReceivedShare,
    ) -> impl std::future::Future<Output = Result<Option<ReceivedShare>, ShareRepoError>>
    + std::marker::Send
    + std::marker::Sized;
    /// Get a share by [SendingServer] and provider_id
    ///
    /// As the ProviderId is only unique per [SendingServer], it is not sufficient to identify a
    /// share by itself.
    fn get(
        &self,
        sending_server: SendingServer,
        provider_id: &str,
    ) -> impl std::future::Future<Output = Result<ReceivedShare, ShareRepoError>>
    + std::marker::Send
    + std::marker::Sized;
    /// Get all shares sent by the given sender.
    fn get_by_sender(
        &self,
        sender: &OcmAddress,
    ) -> impl std::future::Future<Output = Result<Vec<ReceivedShare>, ShareRepoError>>
    + std::marker::Send
    + std::marker::Sized;
    /// Get all shares sent to the given recipient.
    fn get_by_recipient(
        &self,
        recipient: &OcmAddress,
    ) -> impl std::future::Future<Output = Result<Vec<ReceivedShare>, ShareRepoError>>
    + std::marker::Send
    + std::marker::Sized;
    /// Delete a share.
    fn remove(
        &self,
        sending_server: SendingServer,
        provider_id: &str,
    ) -> impl std::future::Future<Output = Result<ReceivedShare, ShareRepoError>>
    + std::marker::Send
    + std::marker::Sized;
}

#[derive(Debug, Clone, Serialize)]
#[serde(into = "ocm_types::error::Error")]
// TODO extend
/// Errors when storing and retrieving shares.
pub enum ShareRepoError {
    NotFound,
}

impl ShareRepoError {
    /// Translate this error into an appropriate HTTP StatusCode
    pub fn status_code(&self) -> http::StatusCode {
        match self {
            ShareRepoError::NotFound => http::StatusCode::NOT_FOUND,
        }
    }
}

impl From<ShareRepoError> for Error {
    fn from(value: ShareRepoError) -> Self {
        match value {
            ShareRepoError::NotFound => Self {
                message: "SHARE_NOT_FOUND".to_string(),
                validation_errors: vec![],
            },
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
// FIXME move to ocm_types or drop
pub struct ProviderId(String);

impl From<ProviderId> for String{
    fn from(value: ProviderId) -> Self {
        value.0
    }
}

impl From<String> for ProviderId {
    fn from(value: String) -> Self {
        Self(value)
    }
}

impl AsRef<str> for ProviderId {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// Store received shares in memory. This is intended for testing only.
#[derive(Debug, Clone, Default)]
pub struct InMemoryShareRepo {
    map: Arc<Mutex<HashMap<(SendingServer, ProviderId), ReceivedShare>>>,
}

impl ReceivedShareRepo for InMemoryShareRepo {
    async fn insert(&self, sending_server: SendingServer, share: ReceivedShare) -> Result<Option<ReceivedShare>, ShareRepoError> {
        Ok(self
            .map
            .lock()
            .unwrap()
            .insert((sending_server, ProviderId(share.0.provider_id.clone())), share))
    }

    async fn get(&self, sending_server: SendingServer, provider_id: &str) -> Result<ReceivedShare, ShareRepoError> {
        Ok(self
            .map
            .lock()
            .unwrap()
            .get(&(sending_server, ProviderId(provider_id.to_owned())))
            .cloned()
            .ok_or(ShareRepoError::NotFound))?
    }

    async fn get_by_sender(&self, sender: &OcmAddress) -> Result<Vec<ReceivedShare>, ShareRepoError> {
        Ok(self
            .map
            .lock()
            .unwrap()
            .values()
            .filter(|share| &share.0.sender == sender)
            .cloned()
            .collect())
    }

    async fn get_by_recipient(&self, recipient: &OcmAddress) -> Result<Vec<ReceivedShare>, ShareRepoError> {
        Ok(self
            .map
            .lock()
            .unwrap()
            .values()
            .filter(|share| &share.0.share_with == recipient)
            .cloned()
            .collect())
    }

    async fn remove(&self, sending_server: SendingServer, provider_id: &str) -> Result<ReceivedShare, ShareRepoError> {
        Ok(self
            .map
            .lock()
            .unwrap()
            .remove(&(sending_server, ProviderId(provider_id.to_owned())))
            .ok_or(ShareRepoError::NotFound))?
    }
}