rho_core/providers/
mod.rs1use 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}