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