harn_vm/triggers/dispatcher/
uri.rs1use crate::triggers::TriggerHandlerSpec;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub enum DispatchUriError {
5 Empty,
6 MissingTarget { scheme: String },
7 UnknownScheme(String),
8}
9
10impl std::fmt::Display for DispatchUriError {
11 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 match self {
13 Self::Empty => f.write_str("handler URI cannot be empty"),
14 Self::MissingTarget { scheme } => {
15 write!(f, "{scheme} handler URI target cannot be empty")
16 }
17 Self::UnknownScheme(scheme) => write!(f, "unsupported handler URI scheme '{scheme}'"),
18 }
19 }
20}
21
22impl std::error::Error for DispatchUriError {}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum DispatchUri {
26 Local {
27 raw: String,
28 },
29 A2a {
30 target: String,
31 allow_cleartext: bool,
32 },
33 Worker {
34 queue: String,
35 },
36 Persona {
37 name: String,
38 },
39}
40
41impl DispatchUri {
42 pub fn parse(raw: &str) -> Result<Self, DispatchUriError> {
43 let raw = raw.trim();
44 if raw.is_empty() {
45 return Err(DispatchUriError::Empty);
46 }
47 if let Some(target) = raw.strip_prefix("a2a://") {
48 if target.is_empty() {
49 return Err(DispatchUriError::MissingTarget {
50 scheme: "a2a".to_string(),
51 });
52 }
53 return Ok(Self::A2a {
54 target: target.to_string(),
55 allow_cleartext: false,
56 });
57 }
58 if let Some(queue) = raw.strip_prefix("worker://") {
59 if queue.is_empty() {
60 return Err(DispatchUriError::MissingTarget {
61 scheme: "worker".to_string(),
62 });
63 }
64 return Ok(Self::Worker {
65 queue: queue.to_string(),
66 });
67 }
68 if let Some(name) = raw.strip_prefix("persona://") {
69 if name.is_empty() {
70 return Err(DispatchUriError::MissingTarget {
71 scheme: "persona".to_string(),
72 });
73 }
74 return Ok(Self::Persona {
75 name: name.to_string(),
76 });
77 }
78 if let Some((scheme, _)) = raw.split_once("://") {
79 return Err(DispatchUriError::UnknownScheme(scheme.to_string()));
80 }
81 Ok(Self::Local {
82 raw: raw.to_string(),
83 })
84 }
85
86 pub fn kind(&self) -> &'static str {
87 match self {
88 Self::Local { .. } => "local",
89 Self::A2a { .. } => "a2a",
90 Self::Worker { .. } => "worker",
91 Self::Persona { .. } => "persona",
92 }
93 }
94
95 pub fn target_uri(&self) -> String {
96 match self {
97 Self::Local { raw } => raw.clone(),
98 Self::A2a { target, .. } => format!("a2a://{target}"),
99 Self::Worker { queue } => format!("worker://{queue}"),
100 Self::Persona { name } => format!("persona://{name}"),
101 }
102 }
103
104 pub fn trust_boundary(&self) -> &'static str {
105 match self {
106 Self::Local { .. } => "local_process",
107 Self::A2a { .. } => "federated_a2a",
108 Self::Worker { .. } => "event_log_worker_queue",
109 Self::Persona { .. } => "persona_runtime",
110 }
111 }
112
113 pub fn execution_location(&self) -> &'static str {
114 match self {
115 Self::Local { .. } => "in_process",
116 Self::A2a { .. } => "remote",
117 Self::Worker { .. } => "queued",
118 Self::Persona { .. } => "managed_persona",
119 }
120 }
121
122 pub fn remote_identity(&self) -> Option<String> {
123 match self {
124 Self::A2a { target, .. } => Some(crate::a2a::target_agent_label(target)),
125 _ => None,
126 }
127 }
128
129 pub fn dispatch_boundary_metadata(
130 &self,
131 ) -> std::collections::BTreeMap<String, serde_json::Value> {
132 let mut metadata = std::collections::BTreeMap::new();
133 metadata.insert(
134 "trust_boundary".to_string(),
135 serde_json::json!(self.trust_boundary()),
136 );
137 metadata.insert(
138 "execution_location".to_string(),
139 serde_json::json!(self.execution_location()),
140 );
141 if let Some(remote_identity) = self.remote_identity() {
142 metadata.insert(
143 "remote_identity".to_string(),
144 serde_json::json!(remote_identity),
145 );
146 }
147 metadata
148 }
149}
150
151impl From<&TriggerHandlerSpec> for DispatchUri {
152 fn from(value: &TriggerHandlerSpec) -> Self {
153 match value {
154 TriggerHandlerSpec::Local { raw, .. } => Self::Local { raw: raw.clone() },
155 TriggerHandlerSpec::A2a {
156 target,
157 allow_cleartext,
158 } => Self::A2a {
159 target: target.clone(),
160 allow_cleartext: *allow_cleartext,
161 },
162 TriggerHandlerSpec::Worker { queue } => Self::Worker {
163 queue: queue.clone(),
164 },
165 TriggerHandlerSpec::Persona { binding } => Self::Persona {
166 name: binding.name.clone(),
167 },
168 }
169 }
170}