Skip to main content

rho_core/providers/
mod.rs

1use std::path::PathBuf;
2
3use crate::{RhoResult, validate_relative_safe_path};
4
5pub mod github;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct IdentityLocator {
9    pub provider: String,
10    pub handle: String,
11}
12
13pub trait IdentityProvider {
14    fn provider(&self) -> &'static str;
15    fn validate_handle(&self, handle: &str) -> RhoResult<()>;
16
17    fn identity_id(&self, handle: &str) -> RhoResult<String> {
18        self.validate_handle(handle)?;
19        Ok(format!("rho://id/{}/{}", self.provider(), handle))
20    }
21
22    fn handle_from_identity_id(&self, identity_id: &str) -> RhoResult<String> {
23        let locator = parse_identity_id(identity_id)?;
24        if locator.provider != self.provider() {
25            return Err(format!(
26                "unsupported identity provider for {}: {}",
27                self.provider(),
28                identity_id
29            )
30            .into());
31        }
32        self.validate_handle(&locator.handle)?;
33        Ok(locator.handle)
34    }
35}
36
37pub fn parse_identity_id(identity_id: &str) -> RhoResult<IdentityLocator> {
38    let Some(rest) = identity_id.strip_prefix("rho://id/") else {
39        return Err(format!("identity id must start with rho://id/: {identity_id}").into());
40    };
41    let Some((provider, handle)) = rest.split_once('/') else {
42        return Err(format!("identity id must include provider and handle: {identity_id}").into());
43    };
44    if provider.is_empty() || handle.is_empty() || handle.contains('/') {
45        return Err(format!("invalid identity id: {identity_id}").into());
46    }
47    validate_relative_safe_path(provider)?;
48    validate_relative_safe_path(handle)?;
49    Ok(IdentityLocator {
50        provider: provider.to_string(),
51        handle: handle.to_string(),
52    })
53}
54
55pub fn identity_inbox_relative_path(identity_id: &str) -> RhoResult<PathBuf> {
56    let locator = parse_identity_id(identity_id)?;
57    Ok(PathBuf::from("id")
58        .join(locator.provider)
59        .join(locator.handle))
60}