1use std::time::Duration;
10
11use std::sync::Arc;
12
13use dex_protocol::dex::AttributeWrite;
14
15use crate::attribute::{
16 AttributeWriteSettings, require_registered_hard_delete_setting,
17 validate_hard_delete_attribute_write,
18};
19use crate::client::invalid;
20use crate::persistence::PersistenceDefinition;
21use crate::registry::physical_name;
22use crate::step::{ErasedValue, TypedValue};
23use crate::{
24 Attribute, AttributeMap, FlowConfig, FlowTimeoutHandlerOptions, RetryPolicy, SdkResult, Value,
25};
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum IdReusePolicy {
30 Default,
33 AllowIfPreviousFailed,
38 AllowIfNotRunning,
40 Disallow,
42 TerminateIfRunning,
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum FlowTimeoutPolicy {
49 Default,
51 Fail,
53 Cancel,
55 Handler,
57}
58
59#[derive(Clone)]
60pub struct StartFlowOptions {
94 pub(crate) timeout: Option<Duration>,
95 pub(crate) timeout_policy: FlowTimeoutPolicy,
96 pub(crate) timeout_handler_options: Option<FlowTimeoutHandlerOptions>,
97 pub(crate) start_delay: Option<Duration>,
98 pub(crate) id_reuse_policy: IdReusePolicy,
99 pub(crate) retry_policy: Option<RetryPolicy>,
100 pub(crate) config_override: Option<FlowConfig>,
101 pub(crate) ignore_already_started: bool,
102 pub(crate) request_id: Option<String>,
103 pub(crate) attributes: Vec<InitialAttribute>,
104}
105
106#[derive(Clone)]
107pub(crate) struct InitialAttribute {
108 pub(crate) key: String,
109 pub(crate) attribute_name: String,
110 pub(crate) value: Arc<dyn ErasedValue>,
111 pub(crate) write_settings: AttributeWriteSettings,
112}
113
114impl InitialAttribute {
115 pub(crate) fn for_attribute<T: Value>(attribute: &Attribute<T>, value: T) -> Self {
116 Self {
117 key: attribute.name().to_string(),
118 attribute_name: attribute.name().to_string(),
119 value: Arc::new(TypedValue(value)),
120 write_settings: attribute.write_settings(),
121 }
122 }
123
124 pub(crate) fn for_attribute_map<T: Value>(
125 attribute: &AttributeMap<T>,
126 instance: &str,
127 value: T,
128 ) -> Self {
129 Self {
130 key: physical_name(attribute.name(), instance),
131 attribute_name: attribute.name().to_string(),
132 value: Arc::new(TypedValue(value)),
133 write_settings: attribute.write_settings(),
134 }
135 }
136
137 pub(crate) fn encode_attribute_write(
143 &self,
144 definition: &PersistenceDefinition,
145 ) -> SdkResult<AttributeWrite> {
146 require_registered_hard_delete_setting(
147 &self.attribute_name,
148 definition.is_hard_delete_on_null,
149 self.write_settings.is_hard_delete_on_null,
150 )
151 .map_err(invalid)?;
152 let write = self
153 .write_settings
154 .clone()
155 .attribute_write(self.key.clone(), self.value.encode()?);
156 validate_hard_delete_attribute_write(&write).map_err(invalid)?;
157 Ok(write)
158 }
159}
160
161impl StartFlowOptions {
162 pub fn new() -> Self {
164 Self {
165 timeout: None,
166 timeout_policy: FlowTimeoutPolicy::Default,
167 timeout_handler_options: None,
168 start_delay: None,
169 id_reuse_policy: IdReusePolicy::Default,
170 retry_policy: None,
171 config_override: None,
172 ignore_already_started: false,
173 request_id: None,
174 attributes: Vec::new(),
175 }
176 }
177
178 pub fn timeout(mut self, value: Duration) -> Self {
180 self.timeout = Some(value);
181 self
182 }
183
184 pub fn timeout_policy(mut self, value: FlowTimeoutPolicy) -> Self {
188 self.timeout_policy = value;
189 self
190 }
191
192 pub fn timeout_handler_options(mut self, value: FlowTimeoutHandlerOptions) -> Self {
197 self.timeout_handler_options = Some(value);
198 self
199 }
200
201 pub fn start_delay(mut self, value: Duration) -> Self {
207 self.start_delay = Some(value);
208 self
209 }
210
211 pub fn id_reuse_policy(mut self, value: IdReusePolicy) -> Self {
213 self.id_reuse_policy = value;
214 self
215 }
216
217 pub fn retry_policy(mut self, value: RetryPolicy) -> Self {
223 self.retry_policy = Some(value);
224 self
225 }
226
227 pub fn initial_attribute<T: Value>(mut self, attribute: &Attribute<T>, value: T) -> Self {
229 self.attributes
230 .push(InitialAttribute::for_attribute(attribute, value));
231 self
232 }
233
234 pub fn initial_attribute_map<T: Value>(
237 mut self,
238 attribute: &AttributeMap<T>,
239 instance: &str,
240 value: T,
241 ) -> Self {
242 self.attributes.push(InitialAttribute::for_attribute_map(
243 attribute, instance, value,
244 ));
245 self
246 }
247
248 pub fn config_override(mut self, value: FlowConfig) -> Self {
250 self.config_override = Some(value);
251 self
252 }
253
254 pub fn ignore_already_started(mut self, value: bool) -> Self {
261 self.ignore_already_started = value;
262 self
263 }
264
265 pub fn request_id(mut self, value: impl Into<String>) -> Self {
277 self.request_id = Some(value.into());
278 self
279 }
280}
281
282impl Default for StartFlowOptions {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::StartFlowOptions;
291 use crate::persistence::{PersistenceDefinition, PersistenceKind};
292 use crate::{Attribute, AttributeMap, SdkError};
293
294 #[test]
295 fn initial_attributes_retain_sync_configuration() {
296 let plain = Attribute::<String>::new("plain");
297 let synced = Attribute::<String>::new("synced").sync_to_attribute_store();
298 let synced_map = AttributeMap::<String>::new("map").sync_to_attribute_store();
299 let options = StartFlowOptions::new()
300 .initial_attribute(&plain, "plain".to_string())
301 .initial_attribute(&synced, "synced".to_string())
302 .initial_attribute_map(&synced_map, "tenant-1", "mapped".to_string());
303
304 assert!(options.attributes[0].write_settings.sync_config.is_none());
305 assert_eq!(
306 options.attributes[1]
307 .write_settings
308 .sync_config
309 .as_ref()
310 .map(|config| config.enabled),
311 Some(true)
312 );
313 assert_eq!(
314 options.attributes[2]
315 .write_settings
316 .sync_config
317 .as_ref()
318 .map(|config| config.enabled),
319 Some(true)
320 );
321 }
322
323 #[derive(serde::Deserialize, serde::Serialize)]
324 struct NumericNewtype(u64);
325
326 #[test]
327 fn initial_hard_delete_attributes_set_the_flag_and_validate_values() {
328 let secret = Attribute::<String>::new("secret").hard_delete_on_null();
329 let secrets = AttributeMap::<Option<String>>::new("secrets").hard_delete_on_null();
330 let numeric = Attribute::<NumericNewtype>::new("numeric").hard_delete_on_null();
331 let plain_secret = Attribute::<String>::new("secret");
332 let options = StartFlowOptions::new()
333 .initial_attribute(&secret, "key-1".to_string())
334 .initial_attribute_map(&secrets, "tenant-1", None)
335 .initial_attribute(&numeric, NumericNewtype(7))
336 .initial_attribute(&plain_secret, "leak".to_string());
337 let secret_definition = PersistenceDefinition {
338 is_hard_delete_on_null: true,
339 ..PersistenceDefinition::new("secret", PersistenceKind::Attribute)
340 };
341 let secrets_definition = PersistenceDefinition {
342 is_hard_delete_on_null: true,
343 ..PersistenceDefinition::new("secrets", PersistenceKind::AttributeMap)
344 };
345 let numeric_definition = PersistenceDefinition {
346 is_hard_delete_on_null: true,
347 ..PersistenceDefinition::new("numeric", PersistenceKind::Attribute)
348 };
349
350 let write = options.attributes[0]
351 .encode_attribute_write(&secret_definition)
352 .expect("string write");
353 assert!(write.is_hard_delete_on_null);
354 assert!(write.index_config.is_none() && write.sync_config.is_none());
355 let deletion = options.attributes[1]
356 .encode_attribute_write(&secrets_definition)
357 .expect("null write");
358 assert!(deletion.is_hard_delete_on_null);
359 assert_eq!(deletion.key, "secrets/tenant-1");
360 for (result, expected) in [
361 (
362 options.attributes[2].encode_attribute_write(&numeric_definition),
363 "numeric and Boolean values are rejected",
364 ),
365 (
366 options.attributes[3].encode_attribute_write(&secret_definition),
367 "does not match its registered definition",
368 ),
369 (
370 options.attributes[0].encode_attribute_write(&PersistenceDefinition::new(
371 "secret",
372 PersistenceKind::Attribute,
373 )),
374 "does not match its registered definition",
375 ),
376 ] {
377 let Err(SdkError::InvalidArgument { message }) = result else {
378 panic!("expected InvalidArgument");
379 };
380 assert!(message.contains(expected), "{message}");
381 }
382 }
383}