rho-cli 0.1.24

Rho CLI tools for encrypted agent collaboration, dataset publishing, controlled runs, and result release workflows
Documentation
use std::path::PathBuf;

use crate::{RhoResult, validate_relative_safe_path};

pub mod github;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdentityLocator {
    pub provider: String,
    pub handle: String,
}

pub trait IdentityProvider {
    fn provider(&self) -> &'static str;
    fn validate_handle(&self, handle: &str) -> RhoResult<()>;

    fn identity_id(&self, handle: &str) -> RhoResult<String> {
        self.validate_handle(handle)?;
        Ok(format!("rho://id/{}/{}", self.provider(), handle))
    }

    fn handle_from_identity_id(&self, identity_id: &str) -> RhoResult<String> {
        let locator = parse_identity_id(identity_id)?;
        if locator.provider != self.provider() {
            return Err(format!(
                "unsupported identity provider for {}: {}",
                self.provider(),
                identity_id
            )
            .into());
        }
        self.validate_handle(&locator.handle)?;
        Ok(locator.handle)
    }
}

pub fn parse_identity_id(identity_id: &str) -> RhoResult<IdentityLocator> {
    let Some(rest) = identity_id.strip_prefix("rho://id/") else {
        return Err(format!("identity id must start with rho://id/: {identity_id}").into());
    };
    let Some((provider, handle)) = rest.split_once('/') else {
        return Err(format!("identity id must include provider and handle: {identity_id}").into());
    };
    if provider.is_empty() || handle.is_empty() || handle.contains('/') {
        return Err(format!("invalid identity id: {identity_id}").into());
    }
    validate_relative_safe_path(provider)?;
    validate_relative_safe_path(handle)?;
    Ok(IdentityLocator {
        provider: provider.to_string(),
        handle: handle.to_string(),
    })
}

pub fn identity_inbox_relative_path(identity_id: &str) -> RhoResult<PathBuf> {
    let locator = parse_identity_id(identity_id)?;
    Ok(PathBuf::from("id")
        .join(locator.provider)
        .join(locator.handle))
}