1use std::borrow::Cow;
25use std::collections::HashMap;
26use std::fmt;
27
28use crate::common::{doc_anchor_for_rule, ErrorInfo, Phase, ValueType};
29
30pub mod implementations;
31pub mod registry;
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum ActionKind {
35 Action,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39pub enum ActionValueType {
40 Event,
41 Number,
42 Series,
43 Bool,
44 String,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub enum ActionOutcome {
49 Attempted,
50 Completed,
53 Rejected,
54 Cancelled,
55 Failed,
56 Skipped,
58}
59
60#[derive(Debug, Clone, PartialEq)]
61pub enum ActionValue {
62 Event(ActionOutcome),
63 Number(f64),
64 Series(Vec<f64>),
65 Bool(bool),
66 String(String),
67}
68
69impl ActionValue {
70 pub fn value_type(&self) -> ActionValueType {
71 match self {
72 ActionValue::Event(_) => ActionValueType::Event,
73 ActionValue::Number(_) => ActionValueType::Number,
74 ActionValue::Series(_) => ActionValueType::Series,
75 ActionValue::Bool(_) => ActionValueType::Bool,
76 ActionValue::String(_) => ActionValueType::String,
77 }
78 }
79
80 pub fn as_event(&self) -> Option<&ActionOutcome> {
81 match self {
82 ActionValue::Event(e) => Some(e),
83 _ => None,
84 }
85 }
86
87 pub fn as_series(&self) -> Option<&Vec<f64>> {
88 match self {
89 ActionValue::Series(series) => Some(series),
90 _ => None,
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq)]
96pub enum ParameterType {
97 Int,
98 Number,
99 Bool,
100 String,
101 Enum,
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub enum ParameterValue {
106 Int(i64),
107 Number(f64),
108 Bool(bool),
109 String(String),
110 Enum(String),
111}
112
113impl ParameterValue {
114 pub fn value_type(&self) -> ParameterType {
115 match self {
116 ParameterValue::Int(_) => ParameterType::Int,
117 ParameterValue::Number(_) => ParameterType::Number,
118 ParameterValue::Bool(_) => ParameterType::Bool,
119 ParameterValue::String(_) => ParameterType::String,
120 ParameterValue::Enum(_) => ParameterType::Enum,
121 }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq)]
126pub enum Cardinality {
127 Single,
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub struct InputSpec {
132 pub name: String,
133 pub value_type: ActionValueType,
134 pub required: bool,
135 pub cardinality: Cardinality,
136}
137
138#[derive(Debug, Clone, PartialEq)]
139pub struct OutputSpec {
140 pub name: String,
141 pub value_type: ActionValueType,
142}
143
144#[derive(Debug, Clone, PartialEq)]
145pub struct ParameterSpec {
146 pub name: String,
147 pub value_type: ParameterType,
148 pub default: Option<ParameterValue>,
149 pub required: bool,
150 pub bounds: Option<String>,
151}
152
153#[derive(Debug, Clone, PartialEq)]
154pub struct ActionWriteSpec {
155 pub name: String,
156 pub value_type: ValueType,
157 pub from_input: String,
158}
159
160#[derive(Debug, Clone, PartialEq)]
161pub struct IntentFieldSpec {
162 pub name: String,
163 pub value_type: ValueType,
164 pub from_input: Option<String>,
165 pub from_param: Option<String>,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub struct IntentMirrorWriteSpec {
170 pub name: String,
171 pub value_type: ValueType,
172 pub from_field: String,
173}
174
175#[derive(Debug, Clone, PartialEq)]
176pub struct IntentSpec {
177 pub name: String,
178 pub fields: Vec<IntentFieldSpec>,
179 pub mirror_writes: Vec<IntentMirrorWriteSpec>,
180}
181
182#[derive(Debug, Clone, PartialEq, Default)]
183pub struct ActionEffects {
184 pub writes: Vec<ActionWriteSpec>,
185 pub intents: Vec<IntentSpec>,
186}
187
188#[derive(Debug, Clone, PartialEq)]
189pub struct ExecutionSpec {
190 pub deterministic: bool,
191 pub retryable: bool,
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub struct StateSpec {
196 pub allowed: bool,
197}
198
199#[derive(Debug, Clone, PartialEq)]
200pub struct ActionPrimitiveManifest {
201 pub id: String,
202 pub version: String,
203 pub kind: ActionKind,
204 pub inputs: Vec<InputSpec>,
205 pub outputs: Vec<OutputSpec>,
206 pub parameters: Vec<ParameterSpec>,
207 pub effects: ActionEffects,
208 pub execution: ExecutionSpec,
209 pub state: StateSpec,
210 pub side_effects: bool,
211}
212
213#[derive(Debug, Clone, Default)]
214pub struct ActionState {
215 pub data: HashMap<String, ActionValue>,
216}
217
218#[derive(Debug, Clone, PartialEq)]
219#[non_exhaustive]
220pub enum ActionValidationError {
221 InvalidId {
222 id: String,
223 },
224 InvalidVersion {
225 version: String,
226 },
227 WrongKind {
228 expected: ActionKind,
229 got: ActionKind,
230 },
231 SideEffectsRequired,
232 NonDeterministicExecution,
233 RetryNotAllowed,
234 StateNotAllowed,
235 DuplicateId(String),
236 DuplicateInput {
237 name: String,
238 first_index: usize,
239 second_index: usize,
240 },
241 EventInputRequired,
242 DuplicateWriteName {
243 name: String,
244 first_index: usize,
245 second_index: usize,
246 },
247 InvalidWriteType {
248 name: String,
249 got: ValueType,
250 },
251 InvalidInputType {
252 input: String,
253 expected: ActionValueType,
254 got: ActionValueType,
255 },
256 OutputNotOutcome {
257 name: String,
258 index: usize,
259 },
260 InvalidOutputType {
261 output: String,
262 expected: ActionValueType,
263 got: ActionValueType,
264 },
265 UndeclaredOutput {
266 primitive: String,
267 output: String,
268 },
269 InvalidParameterType {
270 parameter: String,
271 expected: ParameterType,
272 got: ParameterType,
273 },
274 UnboundWriteKeyReference {
275 name: String,
276 referenced_param: String,
277 },
278 WriteKeyReferenceNotString {
279 name: String,
280 referenced_param: String,
281 },
282 WriteFromInputNotFound {
283 write_name: String,
284 from_input: String,
285 },
286 WriteFromInputTypeMismatch {
287 write_name: String,
288 from_input: String,
289 expected: ValueType,
290 found: ActionValueType,
291 },
292 DuplicateIntentName {
293 name: String,
294 first_index: usize,
295 second_index: usize,
296 },
297 DuplicateIntentFieldName {
298 intent_name: String,
299 field_name: String,
300 first_index: usize,
301 second_index: usize,
302 },
303 IntentFieldMissingSource {
304 intent_name: String,
305 field_name: String,
306 },
307 IntentFieldMultipleSources {
308 intent_name: String,
309 field_name: String,
310 },
311 IntentFieldFromInputNotFound {
312 intent_name: String,
313 field_name: String,
314 from_input: String,
315 },
316 IntentFieldFromInputTypeMismatch {
317 intent_name: String,
318 field_name: String,
319 from_input: String,
320 expected: ValueType,
321 found: ActionValueType,
322 },
323 IntentFieldFromParamNotFound {
324 intent_name: String,
325 field_name: String,
326 from_param: String,
327 },
328 IntentFieldFromParamTypeMismatch {
329 intent_name: String,
330 field_name: String,
331 from_param: String,
332 expected: ValueType,
333 found: ParameterType,
334 },
335 MirrorWriteFromFieldNotFound {
336 intent_name: String,
337 write_name: String,
338 from_field: String,
339 },
340 MirrorWriteTypeMismatch {
341 intent_name: String,
342 write_name: String,
343 from_field: String,
344 expected: ValueType,
345 found: ValueType,
346 },
347}
348
349impl ErrorInfo for ActionValidationError {
350 fn rule_id(&self) -> &'static str {
351 match self {
352 Self::InvalidId { .. } => "ACT-1",
353 Self::InvalidVersion { .. } => "ACT-2",
354 Self::WrongKind { .. } => "ACT-3",
355 Self::EventInputRequired => "ACT-4",
356 Self::DuplicateInput { .. } => "ACT-5",
357 Self::InvalidInputType { .. } => "ACT-6",
358 Self::UndeclaredOutput { .. } => "ACT-7",
359 Self::OutputNotOutcome { .. } => "ACT-8",
360 Self::InvalidOutputType { .. } => "ACT-9",
361 Self::StateNotAllowed => "ACT-10",
362 Self::SideEffectsRequired => "ACT-11",
363 Self::DuplicateWriteName { .. } => "ACT-14",
364 Self::InvalidWriteType { .. } => "ACT-15",
365 Self::RetryNotAllowed => "ACT-16",
366 Self::NonDeterministicExecution => "ACT-17",
367 Self::DuplicateId(_) => "ACT-18",
368 Self::InvalidParameterType { .. } => "ACT-19",
369 Self::UnboundWriteKeyReference { .. } => "ACT-20",
370 Self::WriteKeyReferenceNotString { .. } => "ACT-21",
371 Self::WriteFromInputNotFound { .. } => "ACT-22",
372 Self::WriteFromInputTypeMismatch { .. } => "ACT-23",
373 Self::DuplicateIntentName { .. } => "ACT-24",
374 Self::DuplicateIntentFieldName { .. } => "ACT-25",
375 Self::IntentFieldMissingSource { .. } => "ACT-26",
376 Self::IntentFieldMultipleSources { .. } => "ACT-27",
377 Self::IntentFieldFromInputNotFound { .. } => "ACT-28",
378 Self::IntentFieldFromInputTypeMismatch { .. } => "ACT-29",
379 Self::IntentFieldFromParamNotFound { .. } => "ACT-30",
380 Self::IntentFieldFromParamTypeMismatch { .. } => "ACT-31",
381 Self::MirrorWriteFromFieldNotFound { .. } => "ACT-32",
382 Self::MirrorWriteTypeMismatch { .. } => "ACT-33",
383 }
384 }
385
386 fn phase(&self) -> Phase {
387 Phase::Registration
388 }
389
390 fn doc_anchor(&self) -> &'static str {
391 doc_anchor_for_rule(self.rule_id())
392 }
393
394 fn summary(&self) -> Cow<'static, str> {
395 match self {
396 Self::InvalidId { id } => Cow::Owned(format!("Invalid action ID: '{}'", id)),
397 Self::InvalidVersion { version } => {
398 Cow::Owned(format!("Invalid version: '{}'", version))
399 }
400 Self::WrongKind { expected, got } => Cow::Owned(format!(
401 "Wrong kind: expected {:?}, got {:?}",
402 expected, got
403 )),
404 Self::SideEffectsRequired => Cow::Borrowed("Actions must declare side effects"),
405 Self::NonDeterministicExecution => {
406 Cow::Borrowed("Action execution must be deterministic")
407 }
408 Self::RetryNotAllowed => Cow::Borrowed("Action retryable must be false"),
409 Self::StateNotAllowed => Cow::Borrowed("Action state is not allowed"),
410 Self::DuplicateId(_) => Cow::Borrowed("Duplicate action ID: already registered"),
411 Self::DuplicateInput { name, .. } => {
412 Cow::Owned(format!("Duplicate input name: '{}'", name))
413 }
414 Self::EventInputRequired => Cow::Borrowed("Action requires at least one event input"),
415 Self::DuplicateWriteName { name, .. } => {
416 Cow::Owned(format!("Duplicate write name: '{}'", name))
417 }
418 Self::InvalidWriteType { name, got } => {
419 Cow::Owned(format!("Write '{}' has invalid type {:?}", name, got))
420 }
421 Self::InvalidInputType {
422 input,
423 expected,
424 got,
425 } => Cow::Owned(format!(
426 "Input '{}' has invalid type: expected {:?}, got {:?}",
427 input, expected, got
428 )),
429 Self::OutputNotOutcome { name, .. } => Cow::Owned(format!(
430 "Action output must be named 'outcome', got '{}'",
431 name
432 )),
433 Self::InvalidOutputType {
434 output,
435 expected,
436 got,
437 } => Cow::Owned(format!(
438 "Output '{}' has invalid type: expected {:?}, got {:?}",
439 output, expected, got
440 )),
441 Self::UndeclaredOutput { primitive, output } => Cow::Owned(format!(
442 "Undeclared output '{}' on primitive '{}'",
443 output, primitive
444 )),
445 Self::InvalidParameterType {
446 parameter,
447 expected,
448 got,
449 } => Cow::Owned(format!(
450 "Parameter '{}' has invalid type: expected {:?}, got {:?}",
451 parameter, expected, got
452 )),
453 Self::UnboundWriteKeyReference {
454 name,
455 referenced_param,
456 } => Cow::Owned(format!(
457 "Write key '{}' references undefined parameter '{}'",
458 name, referenced_param
459 )),
460 Self::WriteKeyReferenceNotString {
461 name,
462 referenced_param,
463 } => Cow::Owned(format!(
464 "Write key '{}' references parameter '{}' which is not String type",
465 name, referenced_param
466 )),
467 Self::WriteFromInputNotFound {
468 write_name,
469 from_input,
470 } => Cow::Owned(format!(
471 "Write '{}' references undeclared input '{}'",
472 write_name, from_input
473 )),
474 Self::WriteFromInputTypeMismatch {
475 write_name,
476 from_input,
477 expected,
478 found,
479 } => Cow::Owned(format!(
480 "Write '{}' type {:?} does not match input '{}' type {:?}",
481 write_name, expected, from_input, found
482 )),
483 Self::DuplicateIntentName { name, .. } => {
484 Cow::Owned(format!("Duplicate intent name: '{}'", name))
485 }
486 Self::DuplicateIntentFieldName {
487 intent_name,
488 field_name,
489 ..
490 } => Cow::Owned(format!(
491 "Intent '{}' has duplicate field name '{}'",
492 intent_name, field_name
493 )),
494 Self::IntentFieldMissingSource {
495 intent_name,
496 field_name,
497 } => Cow::Owned(format!(
498 "Intent '{}' field '{}' must set exactly one source (from_input or from_param)",
499 intent_name, field_name
500 )),
501 Self::IntentFieldMultipleSources {
502 intent_name,
503 field_name,
504 } => Cow::Owned(format!(
505 "Intent '{}' field '{}' sets both from_input and from_param",
506 intent_name, field_name
507 )),
508 Self::IntentFieldFromInputNotFound {
509 intent_name,
510 field_name,
511 from_input,
512 } => Cow::Owned(format!(
513 "Intent '{}' field '{}' references undeclared input '{}'",
514 intent_name, field_name, from_input
515 )),
516 Self::IntentFieldFromInputTypeMismatch {
517 intent_name,
518 field_name,
519 from_input,
520 expected,
521 found,
522 } => Cow::Owned(format!(
523 "Intent '{}' field '{}' expects {:?} but input '{}' is {:?}",
524 intent_name, field_name, expected, from_input, found
525 )),
526 Self::IntentFieldFromParamNotFound {
527 intent_name,
528 field_name,
529 from_param,
530 } => Cow::Owned(format!(
531 "Intent '{}' field '{}' references undeclared parameter '{}'",
532 intent_name, field_name, from_param
533 )),
534 Self::IntentFieldFromParamTypeMismatch {
535 intent_name,
536 field_name,
537 from_param,
538 expected,
539 found,
540 } => Cow::Owned(format!(
541 "Intent '{}' field '{}' expects {:?} but parameter '{}' is {:?}",
542 intent_name, field_name, expected, from_param, found
543 )),
544 Self::MirrorWriteFromFieldNotFound {
545 intent_name,
546 write_name,
547 from_field,
548 } => Cow::Owned(format!(
549 "Intent '{}' mirror write '{}' references undeclared field '{}'",
550 intent_name, write_name, from_field
551 )),
552 Self::MirrorWriteTypeMismatch {
553 intent_name,
554 write_name,
555 from_field,
556 expected,
557 found,
558 } => Cow::Owned(format!(
559 "Intent '{}' mirror write '{}' type {:?} does not match field '{}' type {:?}",
560 intent_name, write_name, expected, from_field, found
561 )),
562 }
563 }
564
565 fn path(&self) -> Option<Cow<'static, str>> {
566 match self {
567 Self::InvalidId { .. } => Some(Cow::Borrowed("$.id")),
568 Self::InvalidVersion { .. } => Some(Cow::Borrowed("$.version")),
569 Self::DuplicateId(_) => Some(Cow::Borrowed("$.id")),
570 Self::WrongKind { .. } => Some(Cow::Borrowed("$.kind")),
571 Self::EventInputRequired => Some(Cow::Borrowed("$.inputs")),
572 Self::DuplicateInput { second_index, .. } => {
573 Some(Cow::Owned(format!("$.inputs[{}].name", second_index)))
574 }
575 Self::InvalidInputType { .. } => Some(Cow::Borrowed("$.inputs[].type")),
576 Self::UndeclaredOutput { .. } => Some(Cow::Borrowed("$.outputs")),
577 Self::OutputNotOutcome { index, .. } => {
578 Some(Cow::Owned(format!("$.outputs[{}].name", index)))
579 }
580 Self::InvalidOutputType { .. } => Some(Cow::Borrowed("$.outputs[0].type")),
581 Self::StateNotAllowed => Some(Cow::Borrowed("$.state.allowed")),
582 Self::SideEffectsRequired => Some(Cow::Borrowed("$.side_effects")),
583 Self::DuplicateWriteName { second_index, .. } => Some(Cow::Owned(format!(
584 "$.effects.writes[{}].name",
585 second_index
586 ))),
587 Self::InvalidWriteType { .. } => Some(Cow::Borrowed("$.effects.writes[].type")),
588 Self::RetryNotAllowed => Some(Cow::Borrowed("$.execution.retryable")),
589 Self::NonDeterministicExecution => Some(Cow::Borrowed("$.execution.deterministic")),
590 Self::InvalidParameterType { .. } => Some(Cow::Borrowed("$.parameters[].default")),
591 Self::UnboundWriteKeyReference { .. } => Some(Cow::Borrowed("$.effects.writes[].name")),
592 Self::WriteKeyReferenceNotString { .. } => {
593 Some(Cow::Borrowed("$.effects.writes[].name"))
594 }
595 Self::WriteFromInputNotFound { .. } => {
596 Some(Cow::Borrowed("$.effects.writes[].from_input"))
597 }
598 Self::WriteFromInputTypeMismatch { .. } => {
599 Some(Cow::Borrowed("$.effects.writes[].from_input"))
600 }
601 Self::DuplicateIntentName { second_index, .. } => Some(Cow::Owned(format!(
602 "$.effects.intents[{}].name",
603 second_index
604 ))),
605 Self::DuplicateIntentFieldName {
606 intent_name,
607 second_index,
608 ..
609 } => Some(Cow::Owned(format!(
610 "$.effects.intents[?(@.name==\"{}\")].fields[{}].name",
611 intent_name, second_index
612 ))),
613 Self::IntentFieldMissingSource { intent_name, .. }
614 | Self::IntentFieldMultipleSources { intent_name, .. } => Some(Cow::Owned(format!(
615 "$.effects.intents[?(@.name==\"{}\")].fields[]",
616 intent_name
617 ))),
618 Self::IntentFieldFromInputNotFound { intent_name, .. }
619 | Self::IntentFieldFromInputTypeMismatch { intent_name, .. } => {
620 Some(Cow::Owned(format!(
621 "$.effects.intents[?(@.name==\"{}\")].fields[].from_input",
622 intent_name
623 )))
624 }
625 Self::IntentFieldFromParamNotFound { intent_name, .. }
626 | Self::IntentFieldFromParamTypeMismatch { intent_name, .. } => {
627 Some(Cow::Owned(format!(
628 "$.effects.intents[?(@.name==\"{}\")].fields[].from_param",
629 intent_name
630 )))
631 }
632 Self::MirrorWriteFromFieldNotFound { intent_name, .. }
633 | Self::MirrorWriteTypeMismatch { intent_name, .. } => Some(Cow::Owned(format!(
634 "$.effects.intents[?(@.name==\"{}\")].mirror_writes[]",
635 intent_name
636 ))),
637 }
638 }
639
640 fn fix(&self) -> Option<Cow<'static, str>> {
641 match self {
642 Self::InvalidId { .. } => Some(Cow::Borrowed(
643 "ID must start with lowercase letter and contain only lowercase letters, digits, and underscores",
644 )),
645 Self::DuplicateId(_) => Some(Cow::Borrowed("Choose a unique ID not already registered")),
646 Self::InvalidVersion { .. } => Some(Cow::Borrowed(
647 "Version must be valid semver (e.g., '1.0.0')",
648 )),
649 Self::WrongKind { .. } => Some(Cow::Borrowed("Set kind: action")),
650 Self::EventInputRequired => Some(Cow::Borrowed("Add at least one event input")),
651 Self::DuplicateInput { name, .. } => Some(Cow::Owned(format!(
652 "Rename input '{}' to a unique value",
653 name
654 ))),
655 Self::InvalidInputType { .. } => Some(Cow::Borrowed(
656 "Use a valid input type: event, number, series, bool, or string",
657 )),
658 Self::UndeclaredOutput { .. } => Some(Cow::Borrowed("Declare a single outcome output")),
659 Self::OutputNotOutcome { .. } => Some(Cow::Borrowed("Rename output to 'outcome'")),
660 Self::InvalidOutputType { .. } => Some(Cow::Borrowed("Output type must be event")),
661 Self::StateNotAllowed => Some(Cow::Borrowed("Set state.allowed: false")),
662 Self::SideEffectsRequired => Some(Cow::Borrowed("Set side_effects: true")),
663 Self::DuplicateWriteName { name, .. } => Some(Cow::Owned(format!(
664 "Rename write '{}' to a unique value",
665 name
666 ))),
667 Self::InvalidWriteType { .. } => Some(Cow::Borrowed(
668 "Write types must be Number, Series, Bool, or String",
669 )),
670 Self::RetryNotAllowed => Some(Cow::Borrowed("Set execution.retryable: false")),
671 Self::NonDeterministicExecution => {
672 Some(Cow::Borrowed("Set execution.deterministic: true"))
673 }
674 Self::InvalidParameterType { .. } => Some(Cow::Borrowed(
675 "Change parameter default value to match the declared parameter type",
676 )),
677 Self::UnboundWriteKeyReference {
678 referenced_param, ..
679 } => Some(Cow::Owned(format!(
680 "Add parameter '{}' to the action manifest",
681 referenced_param
682 ))),
683 Self::WriteKeyReferenceNotString {
684 referenced_param, ..
685 } => Some(Cow::Owned(format!(
686 "Change parameter '{}' type to String",
687 referenced_param
688 ))),
689 Self::WriteFromInputNotFound { from_input, .. } => Some(Cow::Owned(format!(
690 "Declare input '{}' in the action manifest inputs",
691 from_input
692 ))),
693 Self::WriteFromInputTypeMismatch {
694 from_input,
695 expected,
696 ..
697 } => Some(Cow::Owned(format!(
698 "Change input '{}' type to match write type {:?}, or use a scalar-typed input",
699 from_input, expected
700 ))),
701 Self::DuplicateIntentName { name, .. } => Some(Cow::Owned(format!(
702 "Rename intent '{}' to a unique value",
703 name
704 ))),
705 Self::DuplicateIntentFieldName { field_name, .. } => Some(Cow::Owned(format!(
706 "Rename intent field '{}' to a unique value within its intent",
707 field_name
708 ))),
709 Self::IntentFieldMissingSource { .. } => Some(Cow::Borrowed(
710 "Set exactly one source on each intent field: from_input or from_param",
711 )),
712 Self::IntentFieldMultipleSources { .. } => Some(Cow::Borrowed(
713 "Set only one source on each intent field: from_input or from_param",
714 )),
715 Self::IntentFieldFromInputNotFound { from_input, .. } => Some(Cow::Owned(format!(
716 "Declare input '{}' in the action manifest inputs",
717 from_input
718 ))),
719 Self::IntentFieldFromInputTypeMismatch {
720 from_input,
721 expected,
722 ..
723 } => Some(Cow::Owned(format!(
724 "Change input '{}' type to match intent field type {:?}",
725 from_input, expected
726 ))),
727 Self::IntentFieldFromParamNotFound { from_param, .. } => Some(Cow::Owned(format!(
728 "Declare parameter '{}' in the action manifest parameters",
729 from_param
730 ))),
731 Self::IntentFieldFromParamTypeMismatch {
732 from_param,
733 expected,
734 ..
735 } => Some(Cow::Owned(format!(
736 "Change parameter '{}' type to match intent field type {:?}",
737 from_param, expected
738 ))),
739 Self::MirrorWriteFromFieldNotFound { from_field, .. } => Some(Cow::Owned(format!(
740 "Declare intent field '{}' before referencing it from mirror_writes",
741 from_field
742 ))),
743 Self::MirrorWriteTypeMismatch {
744 from_field,
745 expected,
746 ..
747 } => Some(Cow::Owned(format!(
748 "Change mirror write type to match field '{}' type {:?}",
749 from_field, expected
750 ))),
751 }
752 }
753}
754
755impl fmt::Display for ActionValidationError {
756 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
757 write!(f, "{} ({})", self.summary(), self.rule_id())
758 }
759}
760
761impl std::error::Error for ActionValidationError {}
762
763pub trait ActionPrimitive {
771 fn manifest(&self) -> &ActionPrimitiveManifest;
772
773 fn execute(
774 &self,
775 inputs: &HashMap<String, ActionValue>,
776 parameters: &HashMap<String, ParameterValue>,
777 ) -> HashMap<String, ActionValue>;
778}
779
780pub use implementations::{
781 AckAction, AnnotateAction, ContextSetBoolAction, ContextSetNumberAction,
782 ContextSetSeriesAction, ContextSetStringAction,
783};
784pub use registry::ActionRegistry;
785
786#[cfg(test)]
787mod tests;