Skip to main content

lash_remote_protocol/protocol/
triggers.rs

1#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
2pub struct RemoteTriggerOccurrenceRequest {
3    pub protocol_version: u32,
4    pub source_type: String,
5    pub source_key: String,
6    #[serde(default)]
7    pub payload: serde_json::Value,
8    pub idempotency_key: String,
9    #[serde(default, skip_serializing_if = "Option::is_none")]
10    pub source: Option<serde_json::Value>,
11}
12
13impl RemoteTriggerOccurrenceRequest {
14    pub fn new(
15        source_type: impl Into<String>,
16        source_key: impl Into<String>,
17        payload: serde_json::Value,
18        idempotency_key: impl Into<String>,
19    ) -> Self {
20        Self {
21            protocol_version: REMOTE_PROTOCOL_VERSION,
22            source_type: source_type.into(),
23            source_key: source_key.into(),
24            payload,
25            idempotency_key: idempotency_key.into(),
26            source: None,
27        }
28    }
29
30    pub fn with_source(mut self, source: serde_json::Value) -> Self {
31        self.source = Some(source);
32        self
33    }
34
35    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
36        ensure_protocol_version(self.protocol_version)?;
37        require_non_empty(
38            "RemoteTriggerOccurrenceRequest",
39            "source_type",
40            &self.source_type,
41        )?;
42        require_non_empty(
43            "RemoteTriggerOccurrenceRequest",
44            "source_key",
45            &self.source_key,
46        )?;
47        require_non_empty(
48            "RemoteTriggerOccurrenceRequest",
49            "idempotency_key",
50            &self.idempotency_key,
51        )
52    }
53}
54
55#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
56pub struct RemoteTriggerOccurrenceRecord {
57    pub occurrence_id: String,
58    pub source_type: String,
59    pub source_key: String,
60    #[serde(default)]
61    pub payload: serde_json::Value,
62    pub idempotency_key: String,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub source: Option<serde_json::Value>,
65    pub occurred_at_ms: u64,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
69pub struct RemoteTriggerEmitReport {
70    pub protocol_version: u32,
71    #[serde(default, skip_serializing_if = "String::is_empty")]
72    pub occurrence_id: String,
73    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub started_process_ids: Vec<String>,
75}
76
77impl RemoteTriggerEmitReport {
78    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
79        ensure_protocol_version(self.protocol_version)
80    }
81}
82
83#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
84pub struct RemoteTriggerSubscriptionFilter {
85    pub protocol_version: u32,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub session_id: Option<String>,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub handle: Option<String>,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub name: Option<String>,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub source_type: Option<String>,
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub source_key: Option<String>,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub target: Option<RemoteProcessDefinitionIdentity>,
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub enabled: Option<bool>,
100}
101
102impl Default for RemoteTriggerSubscriptionFilter {
103    fn default() -> Self {
104        Self {
105            protocol_version: REMOTE_PROTOCOL_VERSION,
106            session_id: None,
107            handle: None,
108            name: None,
109            source_type: None,
110            source_key: None,
111            target: None,
112            enabled: None,
113        }
114    }
115}
116
117impl RemoteTriggerSubscriptionFilter {
118    pub fn for_session(session_id: impl Into<String>) -> Self {
119        Self {
120            protocol_version: REMOTE_PROTOCOL_VERSION,
121            session_id: Some(session_id.into()),
122            ..Self::default()
123        }
124    }
125
126    pub fn for_source_type(source_type: impl Into<String>) -> Self {
127        Self {
128            protocol_version: REMOTE_PROTOCOL_VERSION,
129            source_type: Some(source_type.into()),
130            ..Self::default()
131        }
132    }
133
134    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
135        ensure_protocol_version(self.protocol_version)
136    }
137}
138
139#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
140pub struct RemoteTriggerRegistration {
141    pub handle: String,
142    pub source_key: String,
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub name: Option<String>,
145    pub source_type: String,
146    #[serde(default)]
147    pub source: serde_json::Value,
148    pub target: RemoteTriggerTargetSummary,
149    #[serde(default = "default_true")]
150    pub enabled: bool,
151}
152
153#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
154pub struct RemoteTriggerTargetSummary {
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub label: Option<String>,
157    pub identity: RemoteProcessIdentity,
158    pub input: RemoteProcessInput,
159    #[serde(default)]
160    pub inputs: RemoteTriggerInputTemplate,
161}
162
163fn default_true() -> bool {
164    true
165}
166
167#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
168#[serde(transparent)]
169pub struct RemoteTriggerInputTemplate {
170    pub entries: BTreeMap<String, RemoteTriggerInputBinding>,
171}
172
173impl RemoteTriggerInputTemplate {
174    pub fn new(entries: BTreeMap<String, RemoteTriggerInputBinding>) -> Self {
175        Self { entries }
176    }
177
178    pub fn validate(&self, type_name: &'static str) -> Result<(), RemoteProtocolError> {
179        for name in self.entries.keys() {
180            require_non_empty(type_name, "input_template key", name)?;
181        }
182        Ok(())
183    }
184}
185
186#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
187#[serde(tag = "kind", rename_all = "snake_case")]
188pub enum RemoteTriggerInputBinding {
189    Event,
190    Fixed { value: serde_json::Value },
191}
192
193#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
194pub struct RemoteTriggerSubscriptionDraft {
195    pub protocol_version: u32,
196    pub registrant: RemoteProcessOriginator,
197    pub env_ref: RemoteProcessExecutionEnvRef,
198    #[serde(default, skip_serializing_if = "Option::is_none")]
199    pub wake_target: Option<RemoteSessionScope>,
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub name: Option<String>,
202    pub source_type: String,
203    pub source_key: String,
204    #[serde(default)]
205    pub source: serde_json::Value,
206    #[serde(default)]
207    pub payload_schema: serde_json::Value,
208    pub target: RemoteProcessInput,
209    pub target_identity: RemoteProcessIdentity,
210    #[serde(default, skip_serializing_if = "Vec::is_empty")]
211    pub event_types: Vec<RemoteProcessEventType>,
212    #[serde(default)]
213    pub input_template: RemoteTriggerInputTemplate,
214    #[serde(default, skip_serializing_if = "Option::is_none")]
215    pub target_label: Option<String>,
216}
217
218impl RemoteTriggerSubscriptionDraft {
219    pub fn for_process(
220        registrant: RemoteProcessOriginator,
221        env_ref: RemoteProcessExecutionEnvRef,
222        source_type: impl Into<String>,
223        source_key: impl Into<String>,
224        target: RemoteProcessInput,
225        target_identity: RemoteProcessIdentity,
226    ) -> Self {
227        let target_label = target_identity.label.clone();
228        Self {
229            protocol_version: REMOTE_PROTOCOL_VERSION,
230            registrant,
231            env_ref,
232            wake_target: None,
233            name: None,
234            source_type: source_type.into(),
235            source_key: source_key.into(),
236            source: serde_json::Value::Object(serde_json::Map::new()),
237            payload_schema: serde_json::Value::Object(serde_json::Map::new()),
238            target,
239            target_identity,
240            event_types: Vec::new(),
241            input_template: RemoteTriggerInputTemplate::default(),
242            target_label,
243        }
244    }
245
246    pub fn with_name(mut self, name: impl Into<String>) -> Self {
247        self.name = Some(name.into());
248        self
249    }
250
251    pub fn with_source(mut self, source: serde_json::Value) -> Self {
252        self.source = source;
253        self
254    }
255
256    pub fn with_payload_schema(mut self, payload_schema: serde_json::Value) -> Self {
257        self.payload_schema = payload_schema;
258        self
259    }
260
261    pub fn with_wake_target(mut self, wake_target: RemoteSessionScope) -> Self {
262        self.wake_target = Some(wake_target);
263        self
264    }
265
266    pub fn with_event_types(
267        mut self,
268        event_types: impl IntoIterator<Item = RemoteProcessEventType>,
269    ) -> Self {
270        self.event_types = event_types.into_iter().collect();
271        self
272    }
273
274    pub fn with_input_template(mut self, input_template: RemoteTriggerInputTemplate) -> Self {
275        self.input_template = input_template;
276        self
277    }
278
279    pub fn with_target_label(mut self, target_label: impl Into<String>) -> Self {
280        self.target_label = Some(target_label.into());
281        self
282    }
283
284    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
285        ensure_protocol_version(self.protocol_version)?;
286        self.registrant.validate("RemoteTriggerSubscriptionDraft")?;
287        self.env_ref.validate("RemoteTriggerSubscriptionDraft")?;
288        if let Some(wake_target) = &self.wake_target {
289            wake_target.validate("RemoteTriggerSubscriptionDraft")?;
290        }
291        require_non_empty(
292            "RemoteTriggerSubscriptionDraft",
293            "source_type",
294            &self.source_type,
295        )?;
296        require_non_empty(
297            "RemoteTriggerSubscriptionDraft",
298            "source_key",
299            &self.source_key,
300        )?;
301        self.target.validate("RemoteTriggerSubscriptionDraft")?;
302        self.target_identity
303            .validate("RemoteTriggerSubscriptionDraft")?;
304        for event_type in &self.event_types {
305            event_type.validate("RemoteTriggerSubscriptionDraft")?;
306        }
307        validate_remote_trigger_target_label(
308            "RemoteTriggerSubscriptionDraft",
309            self.target_label.as_deref(),
310            self.target_identity.label.as_deref(),
311        )?;
312        self.input_template
313            .validate("RemoteTriggerSubscriptionDraft")
314    }
315}
316
317#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
318pub struct RemoteTriggerSubscriptionRecord {
319    pub subscription_id: String,
320    pub registrant: RemoteProcessOriginator,
321    pub env_ref: RemoteProcessExecutionEnvRef,
322    #[serde(default, skip_serializing_if = "Option::is_none")]
323    pub wake_target: Option<RemoteSessionScope>,
324    pub handle: String,
325    #[serde(default, skip_serializing_if = "Option::is_none")]
326    pub name: Option<String>,
327    pub source_type: String,
328    pub source_key: String,
329    #[serde(default)]
330    pub source: serde_json::Value,
331    #[serde(default)]
332    pub payload_schema: serde_json::Value,
333    pub target: RemoteProcessInput,
334    pub target_identity: RemoteProcessIdentity,
335    #[serde(default, skip_serializing_if = "Vec::is_empty")]
336    pub event_types: Vec<RemoteProcessEventType>,
337    #[serde(default)]
338    pub input_template: RemoteTriggerInputTemplate,
339    #[serde(default, skip_serializing_if = "Option::is_none")]
340    pub target_label: Option<String>,
341    #[serde(default = "default_true")]
342    pub enabled: bool,
343    pub created_at_ms: u64,
344    pub updated_at_ms: u64,
345}
346
347impl RemoteTriggerSubscriptionRecord {
348    pub fn validate(&self, type_name: &'static str) -> Result<(), RemoteProtocolError> {
349        require_non_empty(type_name, "subscription_id", &self.subscription_id)?;
350        self.registrant.validate(type_name)?;
351        self.env_ref.validate(type_name)?;
352        if let Some(wake_target) = &self.wake_target {
353            wake_target.validate(type_name)?;
354        }
355        require_non_empty(type_name, "handle", &self.handle)?;
356        require_non_empty(type_name, "source_type", &self.source_type)?;
357        require_non_empty(type_name, "source_key", &self.source_key)?;
358        self.target.validate(type_name)?;
359        self.target_identity.validate(type_name)?;
360        for event_type in &self.event_types {
361            event_type.validate(type_name)?;
362        }
363        validate_remote_trigger_target_label(
364            type_name,
365            self.target_label.as_deref(),
366            self.target_identity.label.as_deref(),
367        )?;
368        self.input_template.validate(type_name)
369    }
370}
371
372fn validate_remote_trigger_target_label(
373    type_name: &'static str,
374    target_label: Option<&str>,
375    identity_label: Option<&str>,
376) -> Result<(), RemoteProtocolError> {
377    match (target_label, identity_label) {
378        (Some(target_label), Some(identity_label)) if target_label != identity_label => {
379            Err(RemoteProtocolError::InvalidEnvelope {
380                type_name,
381                message:
382                    "target_label must match target_identity.label when both are present"
383                        .to_string(),
384            })
385        }
386        _ => Ok(()),
387    }
388}
389
390#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
391pub struct RemoteTriggerRegisterSubscriptionRequest {
392    pub protocol_version: u32,
393    pub draft: RemoteTriggerSubscriptionDraft,
394}
395
396impl RemoteTriggerRegisterSubscriptionRequest {
397    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
398        ensure_protocol_version(self.protocol_version)?;
399        if self.draft.protocol_version != self.protocol_version {
400            return Err(RemoteProtocolError::MismatchedNestedProtocolVersion {
401                parent: "RemoteTriggerRegisterSubscriptionRequest",
402                child: "draft",
403                parent_version: self.protocol_version,
404                child_version: self.draft.protocol_version,
405            });
406        }
407        self.draft.validate()
408    }
409}
410
411#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
412pub struct RemoteTriggerRegisterSubscriptionResult {
413    pub protocol_version: u32,
414    pub record: RemoteTriggerSubscriptionRecord,
415}
416
417impl RemoteTriggerRegisterSubscriptionResult {
418    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
419        ensure_protocol_version(self.protocol_version)?;
420        self.record
421            .validate("RemoteTriggerRegisterSubscriptionResult")
422    }
423}
424
425#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
426pub struct RemoteTriggerListSubscriptionsResponse {
427    pub protocol_version: u32,
428    #[serde(default)]
429    pub subscriptions: Vec<RemoteTriggerSubscriptionRecord>,
430}
431
432impl RemoteTriggerListSubscriptionsResponse {
433    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
434        ensure_protocol_version(self.protocol_version)?;
435        for record in &self.subscriptions {
436            record.validate("RemoteTriggerListSubscriptionsResponse")?;
437        }
438        Ok(())
439    }
440}
441
442#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
443pub struct RemoteTriggerCancelSubscriptionRequest {
444    pub protocol_version: u32,
445    pub session_id: String,
446    pub handle: String,
447}
448
449impl RemoteTriggerCancelSubscriptionRequest {
450    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
451        ensure_protocol_version(self.protocol_version)?;
452        require_non_empty(
453            "RemoteTriggerCancelSubscriptionRequest",
454            "session_id",
455            &self.session_id,
456        )?;
457        require_non_empty(
458            "RemoteTriggerCancelSubscriptionRequest",
459            "handle",
460            &self.handle,
461        )
462    }
463}
464
465#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
466pub struct RemoteTriggerCancelSubscriptionResult {
467    pub protocol_version: u32,
468    pub session_id: String,
469    pub handle: String,
470    pub cancelled: bool,
471}
472
473impl RemoteTriggerCancelSubscriptionResult {
474    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
475        ensure_protocol_version(self.protocol_version)?;
476        require_non_empty(
477            "RemoteTriggerCancelSubscriptionResult",
478            "session_id",
479            &self.session_id,
480        )?;
481        require_non_empty(
482            "RemoteTriggerCancelSubscriptionResult",
483            "handle",
484            &self.handle,
485        )
486    }
487}