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,
32 AllowIfPreviousFailed,
34 AllowIfNotRunning,
36 Disallow,
38 TerminateIfRunning,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum FlowTimeoutPolicy {
45 Default,
47 Fail,
49 Cancel,
51 Handler,
53}
54
55#[derive(Clone)]
56pub struct StartFlowOptions {
90 pub(crate) timeout: Option<Duration>,
91 pub(crate) timeout_policy: FlowTimeoutPolicy,
92 pub(crate) timeout_handler_options: Option<FlowTimeoutHandlerOptions>,
93 pub(crate) start_delay: Option<Duration>,
94 pub(crate) id_reuse_policy: IdReusePolicy,
95 pub(crate) retry_policy: Option<RetryPolicy>,
96 pub(crate) config_override: Option<FlowConfig>,
97 pub(crate) ignore_already_started: bool,
98 pub(crate) request_id: Option<String>,
99 pub(crate) attributes: Vec<InitialAttribute>,
100}
101
102#[derive(Clone)]
103pub(crate) struct InitialAttribute {
104 pub(crate) key: String,
105 pub(crate) attribute_name: String,
106 pub(crate) value: Arc<dyn ErasedValue>,
107 pub(crate) write_settings: AttributeWriteSettings,
108}
109
110impl InitialAttribute {
111 pub(crate) fn for_attribute<T: Value>(attribute: &Attribute<T>, value: T) -> Self {
112 Self {
113 key: attribute.name().to_string(),
114 attribute_name: attribute.name().to_string(),
115 value: Arc::new(TypedValue(value)),
116 write_settings: attribute.write_settings(),
117 }
118 }
119
120 pub(crate) fn for_attribute_map<T: Value>(
121 attribute: &AttributeMap<T>,
122 instance: &str,
123 value: T,
124 ) -> Self {
125 Self {
126 key: physical_name(attribute.name(), instance),
127 attribute_name: attribute.name().to_string(),
128 value: Arc::new(TypedValue(value)),
129 write_settings: attribute.write_settings(),
130 }
131 }
132
133 pub(crate) fn encode_attribute_write(
139 &self,
140 definition: &PersistenceDefinition,
141 ) -> SdkResult<AttributeWrite> {
142 require_registered_hard_delete_setting(
143 &self.attribute_name,
144 definition.is_hard_delete_on_null,
145 self.write_settings.is_hard_delete_on_null,
146 )
147 .map_err(invalid)?;
148 let write = self
149 .write_settings
150 .clone()
151 .attribute_write(self.key.clone(), self.value.encode()?);
152 validate_hard_delete_attribute_write(&write).map_err(invalid)?;
153 Ok(write)
154 }
155}
156
157impl StartFlowOptions {
158 pub fn new() -> Self {
160 Self {
161 timeout: None,
162 timeout_policy: FlowTimeoutPolicy::Default,
163 timeout_handler_options: None,
164 start_delay: None,
165 id_reuse_policy: IdReusePolicy::Default,
166 retry_policy: None,
167 config_override: None,
168 ignore_already_started: false,
169 request_id: None,
170 attributes: Vec::new(),
171 }
172 }
173
174 pub fn timeout(mut self, value: Duration) -> Self {
176 self.timeout = Some(value);
177 self
178 }
179
180 pub fn timeout_policy(mut self, value: FlowTimeoutPolicy) -> Self {
184 self.timeout_policy = value;
185 self
186 }
187
188 pub fn timeout_handler_options(mut self, value: FlowTimeoutHandlerOptions) -> Self {
193 self.timeout_handler_options = Some(value);
194 self
195 }
196
197 pub fn start_delay(mut self, value: Duration) -> Self {
203 self.start_delay = Some(value);
204 self
205 }
206
207 pub fn id_reuse_policy(mut self, value: IdReusePolicy) -> Self {
209 self.id_reuse_policy = value;
210 self
211 }
212
213 pub fn retry_policy(mut self, value: RetryPolicy) -> Self {
219 self.retry_policy = Some(value);
220 self
221 }
222
223 pub fn initial_attribute<T: Value>(mut self, attribute: &Attribute<T>, value: T) -> Self {
225 self.attributes
226 .push(InitialAttribute::for_attribute(attribute, value));
227 self
228 }
229
230 pub fn initial_attribute_map<T: Value>(
233 mut self,
234 attribute: &AttributeMap<T>,
235 instance: &str,
236 value: T,
237 ) -> Self {
238 self.attributes.push(InitialAttribute::for_attribute_map(
239 attribute, instance, value,
240 ));
241 self
242 }
243
244 pub fn config_override(mut self, value: FlowConfig) -> Self {
246 self.config_override = Some(value);
247 self
248 }
249
250 pub fn ignore_already_started(mut self, value: bool) -> Self {
257 self.ignore_already_started = value;
258 self
259 }
260
261 pub fn request_id(mut self, value: impl Into<String>) -> Self {
272 self.request_id = Some(value.into());
273 self
274 }
275}
276
277impl Default for StartFlowOptions {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::StartFlowOptions;
286 use crate::persistence::{PersistenceDefinition, PersistenceKind};
287 use crate::{Attribute, AttributeMap, SdkError};
288
289 #[test]
290 fn initial_attributes_retain_sync_configuration() {
291 let plain = Attribute::<String>::new("plain");
292 let synced = Attribute::<String>::new("synced").sync_to_attribute_store();
293 let synced_map = AttributeMap::<String>::new("map").sync_to_attribute_store();
294 let options = StartFlowOptions::new()
295 .initial_attribute(&plain, "plain".to_string())
296 .initial_attribute(&synced, "synced".to_string())
297 .initial_attribute_map(&synced_map, "tenant-1", "mapped".to_string());
298
299 assert!(options.attributes[0].write_settings.sync_config.is_none());
300 assert_eq!(
301 options.attributes[1]
302 .write_settings
303 .sync_config
304 .as_ref()
305 .map(|config| config.enabled),
306 Some(true)
307 );
308 assert_eq!(
309 options.attributes[2]
310 .write_settings
311 .sync_config
312 .as_ref()
313 .map(|config| config.enabled),
314 Some(true)
315 );
316 }
317
318 #[derive(serde::Deserialize, serde::Serialize)]
319 struct NumericNewtype(u64);
320
321 #[test]
322 fn initial_hard_delete_attributes_set_the_flag_and_validate_values() {
323 let secret = Attribute::<String>::new("secret").hard_delete_on_null();
324 let secrets = AttributeMap::<Option<String>>::new("secrets").hard_delete_on_null();
325 let numeric = Attribute::<NumericNewtype>::new("numeric").hard_delete_on_null();
326 let plain_secret = Attribute::<String>::new("secret");
327 let options = StartFlowOptions::new()
328 .initial_attribute(&secret, "key-1".to_string())
329 .initial_attribute_map(&secrets, "tenant-1", None)
330 .initial_attribute(&numeric, NumericNewtype(7))
331 .initial_attribute(&plain_secret, "leak".to_string());
332 let secret_definition = PersistenceDefinition {
333 is_hard_delete_on_null: true,
334 ..PersistenceDefinition::new("secret", PersistenceKind::Attribute)
335 };
336 let secrets_definition = PersistenceDefinition {
337 is_hard_delete_on_null: true,
338 ..PersistenceDefinition::new("secrets", PersistenceKind::AttributeMap)
339 };
340 let numeric_definition = PersistenceDefinition {
341 is_hard_delete_on_null: true,
342 ..PersistenceDefinition::new("numeric", PersistenceKind::Attribute)
343 };
344
345 let write = options.attributes[0]
346 .encode_attribute_write(&secret_definition)
347 .expect("string write");
348 assert!(write.is_hard_delete_on_null);
349 assert!(write.index_config.is_none() && write.sync_config.is_none());
350 let deletion = options.attributes[1]
351 .encode_attribute_write(&secrets_definition)
352 .expect("null write");
353 assert!(deletion.is_hard_delete_on_null);
354 assert_eq!(deletion.key, "secrets/tenant-1");
355 for (result, expected) in [
356 (
357 options.attributes[2].encode_attribute_write(&numeric_definition),
358 "numeric and Boolean values are rejected",
359 ),
360 (
361 options.attributes[3].encode_attribute_write(&secret_definition),
362 "does not match its registered definition",
363 ),
364 (
365 options.attributes[0].encode_attribute_write(&PersistenceDefinition::new(
366 "secret",
367 PersistenceKind::Attribute,
368 )),
369 "does not match its registered definition",
370 ),
371 ] {
372 let Err(SdkError::InvalidArgument { message }) = result else {
373 panic!("expected InvalidArgument");
374 };
375 assert!(message.contains(expected), "{message}");
376 }
377 }
378}