1use std::any::TypeId;
10use std::collections::{HashMap, HashSet};
11use std::sync::Arc;
12
13use crate::persistence::{PersistenceDefinition, PersistenceKind};
14use crate::rpc::RegisteredRpc;
15use crate::step::RegisteredStep;
16use crate::step_options::ErasedStepOptions;
17use crate::{Context, Flow, HandlerResult, SdkError, SdkResult, StepDecision, Wait};
18use dex_protocol::dex::Value as ProtoValue;
19
20#[derive(Clone, Default)]
21pub struct Registry {
39 inner: Arc<RegistryInner>,
40}
41
42#[derive(Clone, Default)]
43struct RegistryInner {
44 flows: HashMap<&'static str, RegisteredFlow>,
45 attribute_indexes: HashMap<String, i32>,
46}
47
48#[derive(Clone)]
49pub(crate) struct RegisteredFlow {
50 pub(crate) name: &'static str,
51 pub(crate) type_id: TypeId,
52 pub(crate) steps: HashMap<&'static str, RegisteredStep>,
53 pub(crate) start_step: Option<RegisteredStep>,
54 pub(crate) rpcs: HashMap<&'static str, RegisteredRpc>,
55 pub(crate) persistence: HashMap<String, PersistenceDefinition>,
56 pub(crate) handler: Arc<dyn ErasedFlow>,
57}
58
59impl Registry {
60 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn register<SomeFlow: Flow>(mut self, flow: SomeFlow) -> SdkResult<Self> {
72 let flow = Arc::new(flow);
73 let name = require_static_name(flow.flow_type(), "Flow type")?;
74 let registered = assemble_flow(name, Arc::clone(&flow))?;
75 let inner = Arc::make_mut(&mut self.inner);
76 let mut additions = Vec::new();
77 for definition in registered.persistence.values() {
78 let Some(index) = &definition.index else {
79 continue;
80 };
81 if definition.kind == PersistenceKind::AttributeMap
82 && index.key().is_none_or(str::is_empty)
83 {
84 return Err(definition_error(format!(
85 "Flow {name} indexed AttributeMap {} requires an index key",
86 definition.name
87 )));
88 }
89 let key = index.key().unwrap_or(&definition.name).to_string();
90 let index_type = index.proto_value();
91 if inner
92 .attribute_indexes
93 .get(&key)
94 .is_some_and(|existing| *existing != index_type)
95 {
96 return Err(definition_error(format!(
97 "Attribute index {key} has conflicting types"
98 )));
99 }
100 additions.push((key, index_type));
101 }
102 if inner.flows.insert(name, registered).is_some() {
103 return Err(definition_error(format!("duplicate Flow {name}")));
104 }
105 inner.attribute_indexes.extend(additions);
106 Ok(self)
107 }
108
109 pub(crate) fn flow(&self, name: &str) -> SdkResult<&RegisteredFlow> {
110 self.inner
111 .flows
112 .get(name)
113 .ok_or_else(|| SdkError::FlowDefinition {
114 message: format!("Flow is not registered: {name}"),
115 })
116 }
117
118 pub(crate) fn rpc(&self, name: &str) -> SdkResult<&RegisteredRpc> {
119 let mut matched = None;
120 for flow in self.inner.flows.values() {
121 let Some(rpc) = flow.rpcs.get(name) else {
122 continue;
123 };
124 if matched.is_some() {
125 return Err(SdkError::FlowDefinition {
126 message: format!("RPC name is ambiguous across registered Flows: {name}"),
127 });
128 }
129 matched = Some(rpc);
130 }
131 matched.ok_or_else(|| SdkError::FlowDefinition {
132 message: format!("RPC is not registered: {name}"),
133 })
134 }
135
136 pub(crate) fn attribute_indexes(&self) -> &HashMap<String, i32> {
137 &self.inner.attribute_indexes
138 }
139}
140
141fn assemble_flow<SomeFlow: Flow>(
142 name: &'static str,
143 flow: Arc<SomeFlow>,
144) -> SdkResult<RegisteredFlow> {
145 let timeout_handler = flow.timeout_handler();
146 let mut steps = HashMap::new();
147 let mut start_step = None;
148 for step in flow.steps().into_definitions() {
149 require_static_name(step.name, "Step type")?;
150 if step.starting {
151 if start_step.is_some() {
152 return Err(definition_error(format!(
153 "Flow {name} must not have multiple start Steps"
154 )));
155 }
156 start_step = Some(step.clone());
157 }
158 if steps.insert(step.name, step).is_some() {
159 return Err(definition_error(format!(
160 "Flow {name} has a duplicate Step"
161 )));
162 }
163 }
164
165 let persistence = assemble_persistence(name, flow.persistence().definitions())?;
166 let mut rpcs = HashMap::new();
167 for rpc in flow.rpcs().bind(Arc::clone(&flow)) {
168 require_static_name(rpc.name, "RPC name")?;
169 validate_rpc(name, &rpc, &persistence)?;
170 if rpcs.insert(rpc.name, rpc).is_some() {
171 return Err(definition_error(format!("Flow {name} has a duplicate RPC")));
172 }
173 }
174 Ok(RegisteredFlow {
175 name,
176 type_id: TypeId::of::<SomeFlow>(),
177 steps,
178 start_step,
179 rpcs,
180 persistence,
181 handler: Arc::new(TypedFlow {
182 flow,
183 timeout_handler,
184 }),
185 })
186}
187
188pub(crate) trait ErasedFlow: Send + Sync {
189 fn has_timeout_handler(&self) -> bool;
190 fn handle_timeout(&self, context: &mut Context) -> HandlerResult<StepDecision>;
191 fn wait_for(
192 &self,
193 step_type: &str,
194 context: &mut Context,
195 input: &ProtoValue,
196 ) -> HandlerResult<Wait>;
197 fn execute(
198 &self,
199 step_type: &str,
200 context: &mut Context,
201 input: &ProtoValue,
202 ) -> HandlerResult<StepDecision>;
203 fn step_options(&self, step_type: &str) -> HandlerResult<ErasedStepOptions>;
204}
205
206struct TypedFlow<SomeFlow> {
207 flow: Arc<SomeFlow>,
208 timeout_handler: Option<crate::FlowTimeoutHandler<SomeFlow>>,
209}
210
211impl<SomeFlow: Flow> ErasedFlow for TypedFlow<SomeFlow> {
212 fn has_timeout_handler(&self) -> bool {
213 self.timeout_handler.is_some()
214 }
215
216 fn handle_timeout(&self, context: &mut Context) -> HandlerResult<StepDecision> {
217 let handler = self.timeout_handler.ok_or_else(|| {
218 crate::HandlerError::new("dex_sdk::HandlerError", "Flow has no timeout handler")
219 })?;
220 handler(self.flow.as_ref(), context)
221 }
222 fn wait_for(
223 &self,
224 step_type: &str,
225 context: &mut Context,
226 input: &ProtoValue,
227 ) -> HandlerResult<Wait> {
228 self.flow
229 .steps()
230 .find(step_type)
231 .ok_or_else(|| {
232 crate::HandlerError::new(
233 "dex_sdk::HandlerError",
234 format!("Step is not registered: {step_type}"),
235 )
236 })?
237 .wait_for(context, input)
238 }
239
240 fn execute(
241 &self,
242 step_type: &str,
243 context: &mut Context,
244 input: &ProtoValue,
245 ) -> HandlerResult<StepDecision> {
246 self.flow
247 .steps()
248 .find(step_type)
249 .ok_or_else(|| {
250 crate::HandlerError::new(
251 "dex_sdk::HandlerError",
252 format!("Step is not registered: {step_type}"),
253 )
254 })?
255 .execute(context, input)
256 }
257
258 fn step_options(&self, step_type: &str) -> HandlerResult<ErasedStepOptions> {
259 Ok(self
260 .flow
261 .steps()
262 .find(step_type)
263 .ok_or_else(|| {
264 crate::HandlerError::new(
265 "dex_sdk::HandlerError",
266 format!("Step is not registered: {step_type}"),
267 )
268 })?
269 .options())
270 }
271}
272
273fn assemble_persistence(
274 flow_name: &str,
275 definitions: &[PersistenceDefinition],
276) -> SdkResult<HashMap<String, PersistenceDefinition>> {
277 let mut persistence = HashMap::new();
278 for definition in definitions {
279 if definition.name.is_empty() {
280 return Err(definition_error(format!(
281 "Flow {flow_name} has an empty persistence definition name"
282 )));
283 }
284 if persistence
285 .insert(definition.name.clone(), definition.clone())
286 .is_some()
287 {
288 return Err(definition_error(format!(
289 "Flow {flow_name} has duplicate persistence definition {}",
290 definition.name
291 )));
292 }
293 }
294 Ok(persistence)
295}
296
297fn validate_rpc(
298 flow_name: &str,
299 rpc: &RegisteredRpc,
300 persistence: &HashMap<String, PersistenceDefinition>,
301) -> SdkResult<()> {
302 if rpc.timeout.is_some_and(|timeout| timeout.is_zero()) {
303 return Err(definition_error(format!(
304 "Flow {flow_name} RPC {} timeout must be positive",
305 rpc.name
306 )));
307 }
308 let mut locks = HashSet::new();
309 for lock in &rpc.locks {
310 let physical_name = lock.physical_name();
311 let logical_name = physical_name.split('/').next().unwrap_or(&physical_name);
312 let definition = persistence.get(logical_name).ok_or_else(|| {
313 definition_error(format!(
314 "Flow {flow_name} RPC {} locks an unregistered Attribute",
315 rpc.name
316 ))
317 })?;
318 if !matches!(
319 definition.kind,
320 PersistenceKind::Attribute | PersistenceKind::AttributeMap
321 ) {
322 return Err(definition_error(format!(
323 "Flow {flow_name} RPC {} locks a non-Attribute",
324 rpc.name
325 )));
326 }
327 if !locks.insert(physical_name) {
328 return Err(definition_error(format!(
329 "Flow {flow_name} RPC {} has a duplicate lock",
330 rpc.name
331 )));
332 }
333 }
334 Ok(())
335}
336
337fn require_static_name(name: &'static str, kind: &str) -> SdkResult<&'static str> {
338 if name.is_empty() {
339 Err(definition_error(format!("{kind} is required")))
340 } else {
341 Ok(name)
342 }
343}
344
345fn definition_error(message: impl Into<String>) -> SdkError {
346 SdkError::FlowDefinition {
347 message: message.into(),
348 }
349}
350
351pub(crate) fn physical_name(name: &str, instance: &str) -> String {
352 if instance.is_empty() {
353 panic!("persistence instance is required");
354 }
355 let mut encoded = String::new();
356 for byte in instance.bytes() {
357 if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~') {
358 encoded.push(char::from(byte));
359 } else {
360 encoded.push_str(&format!("%{byte:02X}"));
361 }
362 }
363 format!("{name}/{encoded}")
364}
365
366pub(crate) fn decode_instance(physical_name: &str, prefix: &str) -> Result<String, String> {
367 let encoded = physical_name
368 .strip_prefix(prefix)
369 .ok_or_else(|| format!("map key {physical_name} does not start with {prefix}"))?;
370 let bytes = encoded.as_bytes();
371 let mut decoded = Vec::with_capacity(bytes.len());
372 let mut index = 0;
373 while index < bytes.len() {
374 if bytes[index] == b'%' {
375 if index + 2 >= bytes.len() {
376 return Err(format!("invalid percent encoding in {physical_name}"));
377 }
378 let high = hex_value(bytes[index + 1])?;
379 let low = hex_value(bytes[index + 2])?;
380 decoded.push(high * 16 + low);
381 index += 3;
382 } else {
383 decoded.push(bytes[index]);
384 index += 1;
385 }
386 }
387 String::from_utf8(decoded).map_err(|error| error.to_string())
388}
389
390fn hex_value(byte: u8) -> Result<u8, String> {
391 match byte {
392 b'0'..=b'9' => Ok(byte - b'0'),
393 b'a'..=b'f' => Ok(byte - b'a' + 10),
394 b'A'..=b'F' => Ok(byte - b'A' + 10),
395 _ => Err(format!(
396 "invalid percent-encoding digit {}",
397 char::from(byte)
398 )),
399 }
400}