1use crate::generated::common::ActorType;
4use sha2::{Digest, Sha256};
5
6#[derive(Debug, thiserror::Error, PartialEq, Eq)]
7pub enum ActorIdParseError {
8 #[error("expected tf:actor:<type>:<path>, got {0:?}")]
9 MalformedScheme(String),
10 #[error("expected scheme 'tf:actor:', got 'tf:{0}:'")]
11 WrongKind(String),
12 #[error("unknown actor type: {0}")]
13 UnknownType(String),
14 #[error("actor id path is empty")]
15 EmptyPath,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ParsedActorId {
20 pub actor_type: ActorType,
21 pub path: String,
22 pub raw: String,
23}
24
25pub const ACTOR_TYPE_STRS: &[&str] = &[
26 "human",
27 "agent",
28 "device",
29 "service",
30 "site",
31 "organization",
32 "relay",
33 "plugin",
34 "process",
35 "tool",
36 "model-provider",
37 "policy-engine",
38 "proof-anchor",
39 "emergency-authority",
40];
41
42pub fn parse_actor_id(s: &str) -> Result<ParsedActorId, ActorIdParseError> {
43 let parts = split_scheme(s).ok_or_else(|| ActorIdParseError::MalformedScheme(s.to_owned()))?;
44 if parts.kind != "actor" {
45 return Err(ActorIdParseError::WrongKind(parts.kind.to_owned()));
46 }
47 let actor_type = parse_actor_type(parts.type_segment)
48 .ok_or_else(|| ActorIdParseError::UnknownType(parts.type_segment.to_owned()))?;
49 if parts.path.is_empty() {
50 return Err(ActorIdParseError::EmptyPath);
51 }
52 Ok(ParsedActorId {
53 actor_type,
54 path: parts.path.to_owned(),
55 raw: s.to_owned(),
56 })
57}
58
59pub fn format_actor_id(actor_type: &ActorType, path: &str) -> Result<String, ActorIdParseError> {
60 if path.is_empty() {
61 return Err(ActorIdParseError::EmptyPath);
62 }
63 Ok(format!(
64 "tf:actor:{}:{}",
65 actor_type_to_str(actor_type),
66 path
67 ))
68}
69
70pub fn actor_id_equals(a: &str, b: &str) -> bool {
71 match (parse_actor_id(a), parse_actor_id(b)) {
72 (Ok(pa), Ok(pb)) => pa.actor_type == pb.actor_type && pa.path == pb.path,
73 _ => false,
74 }
75}
76
77pub fn derive_peer_actor(ident_pub: &[u8]) -> Result<String, ActorIdParseError> {
82 if ident_pub.len() != 32 {
83 return Err(ActorIdParseError::EmptyPath);
84 }
85 let digest = Sha256::digest(ident_pub);
86 let thumbprint = digest[..8]
87 .iter()
88 .map(|b| format!("{:02x}", b))
89 .collect::<String>();
90 Ok(format!("tf:actor:process:key/{}", thumbprint))
91}
92
93pub(crate) struct SchemeParts<'a> {
94 pub kind: &'a str,
95 pub type_segment: &'a str,
96 pub path: &'a str,
97}
98
99pub(crate) fn split_scheme(s: &str) -> Option<SchemeParts<'_>> {
100 let rest = s.strip_prefix("tf:")?;
101 let first = rest.find(':')?;
102 let kind = &rest[..first];
103 let remainder = &rest[first + 1..];
104 let second = remainder.find(':')?;
105 let type_segment = &remainder[..second];
106 let path = &remainder[second + 1..];
107 Some(SchemeParts {
108 kind,
109 type_segment,
110 path,
111 })
112}
113
114pub(crate) fn parse_actor_type(s: &str) -> Option<ActorType> {
115 Some(match s {
116 "human" => ActorType::Human,
117 "agent" => ActorType::Agent,
118 "device" => ActorType::Device,
119 "service" => ActorType::Service,
120 "site" => ActorType::Site,
121 "organization" => ActorType::Organization,
122 "relay" => ActorType::Relay,
123 "plugin" => ActorType::Plugin,
124 "process" => ActorType::Process,
125 "tool" => ActorType::Tool,
126 "model-provider" => ActorType::ModelProvider,
127 "policy-engine" => ActorType::PolicyEngine,
128 "proof-anchor" => ActorType::ProofAnchor,
129 "emergency-authority" => ActorType::EmergencyAuthority,
130 _ => return None,
131 })
132}
133
134pub(crate) fn actor_type_to_str(t: &ActorType) -> &'static str {
135 match t {
136 ActorType::Human => "human",
137 ActorType::Agent => "agent",
138 ActorType::Device => "device",
139 ActorType::Service => "service",
140 ActorType::Site => "site",
141 ActorType::Organization => "organization",
142 ActorType::Relay => "relay",
143 ActorType::Plugin => "plugin",
144 ActorType::Process => "process",
145 ActorType::Tool => "tool",
146 ActorType::ModelProvider => "model-provider",
147 ActorType::PolicyEngine => "policy-engine",
148 ActorType::ProofAnchor => "proof-anchor",
149 ActorType::EmergencyAuthority => "emergency-authority",
150 }
151}