txtx_addon_kit/types/
commands.rs

1use serde::{
2    ser::{SerializeMap, SerializeStruct},
3    Serialize, Serializer,
4};
5use std::{
6    collections::HashMap,
7    future::{self, Future},
8    hash::Hash,
9    pin::Pin,
10};
11use uuid::Uuid;
12
13use hcl_edit::{expr::Expression, structure::Block, Span};
14use indexmap::IndexMap;
15
16use crate::{
17    constants::{SIGNED_MESSAGE_BYTES, SIGNED_TRANSACTION_BYTES},
18    helpers::hcl::{
19        collect_constructs_references_from_expression, visit_optional_untyped_attribute,
20    },
21};
22use crate::{helpers::hcl::get_object_expression_key, types::stores::ValueStore};
23
24use super::{
25    cloud_interface::CloudServiceContext,
26    diagnostics::Diagnostic,
27    frontend::{
28        ActionItemRequest, ActionItemRequestType, ActionItemRequestUpdate, ActionItemResponse,
29        ActionItemResponseType, ActionItemStatus, Actions, BlockEvent, ProvideInputRequest,
30        ProvidedInputResponse, ReviewedInputResponse,
31    },
32    signers::{
33        consolidate_nested_execution_result, consolidate_signer_activate_future_result,
34        consolidate_signer_future_result, return_synchronous, PrepareSignedNestedExecutionResult,
35        SignerActionsFutureResult, SignerInstance, SignerSignFutureResult, SignersState,
36    },
37    stores::ValueMap,
38    types::{ObjectDefinition, ObjectProperty, RunbookSupervisionContext, Type, Value},
39    ConstructDid, Did, EvaluatableInput, PackageId, WithEvaluatableInputs,
40};
41
42#[derive(Clone, Debug)]
43pub struct CommandExecutionResult {
44    pub outputs: HashMap<String, Value>, // todo: change value to be Result<Value, Diagnostic>
45}
46
47impl Serialize for CommandExecutionResult {
48    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: Serializer,
51    {
52        let mut map = serializer.serialize_map(Some(self.outputs.len()))?;
53        for (k, v) in self.outputs.iter() {
54            map.serialize_entry(&k, &v)?;
55        }
56        map.end()
57    }
58}
59impl CommandExecutionResult {
60    pub fn new() -> Self {
61        Self { outputs: HashMap::new() }
62    }
63
64    pub fn from<S: ToString, T: IntoIterator<Item = (S, Value)>>(default: T) -> Self {
65        let mut outputs = HashMap::new();
66        for (key, value) in default {
67            outputs.insert(key.to_string(), value);
68        }
69        Self { outputs }
70    }
71
72    pub fn append(&mut self, other: &mut CommandExecutionResult) {
73        for (key, value) in other.outputs.drain() {
74            self.outputs.insert(key, value);
75        }
76    }
77
78    pub fn from_value_store(store: &ValueStore) -> Self {
79        let mut outputs = HashMap::new();
80        for (key, value) in store.iter() {
81            outputs.insert(key.clone(), value.clone());
82        }
83        Self { outputs }
84    }
85
86    pub fn insert(&mut self, key: &str, value: Value) {
87        self.outputs.insert(key.into(), value);
88    }
89
90    /// Applies each of the keys/values of `other` onto `self`
91    pub fn apply(&mut self, other: &CommandExecutionResult) {
92        for (key, value) in other.outputs.iter() {
93            self.outputs.insert(key.clone(), value.clone());
94        }
95    }
96}
97
98#[derive(Clone, Debug)]
99pub struct DependencyExecutionResultCache {
100    cache: HashMap<ConstructDid, Result<CommandExecutionResult, Diagnostic>>,
101}
102impl DependencyExecutionResultCache {
103    pub fn new() -> Self {
104        Self { cache: HashMap::new() }
105    }
106
107    pub fn get(
108        &self,
109        construct_did: &ConstructDid,
110    ) -> Option<&Result<CommandExecutionResult, Diagnostic>> {
111        self.cache.get(construct_did)
112    }
113
114    pub fn insert(
115        &mut self,
116        construct_did: ConstructDid,
117        result: Result<CommandExecutionResult, Diagnostic>,
118    ) {
119        self.cache.insert(construct_did, result);
120    }
121
122    /// If `self` does not contain `construct_did`, insert `construct_did` with `other_result`.
123    /// If `self` contains `construct_did`, apply `other_result` onto the existing value by only inserting
124    /// each of the keys of `other_result` into `self`'s results at `construct_did`.
125    pub fn merge(
126        &mut self,
127        construct_did: &ConstructDid,
128        other_result: &CommandExecutionResult,
129    ) -> Result<(), Diagnostic> {
130        match self.cache.get_mut(&construct_did) {
131            Some(Ok(result)) => {
132                result.apply(&other_result);
133            }
134            Some(Err(e)) => return Err(e.clone()),
135            None => {
136                self.cache.insert(construct_did.clone(), Ok(other_result.clone()));
137            }
138        }
139        Ok(())
140    }
141}
142
143#[derive(Clone, Debug)]
144pub struct CommandInputsEvaluationResult {
145    pub inputs: ValueStore,
146    pub unevaluated_inputs: UnevaluatedInputsMap,
147}
148
149impl CommandInputsEvaluationResult {
150    pub fn get_if_not_unevaluated<'a, ReturnType>(
151        &'a self,
152        key: &str,
153        getter: impl Fn(&'a ValueStore, &str) -> Result<ReturnType, Diagnostic>,
154    ) -> Result<ReturnType, Diagnostic> {
155        self.unevaluated_inputs.check_for_diagnostic(key)?;
156        getter(&self.inputs, key)
157    }
158}
159
160#[derive(Clone, Debug)]
161pub struct UnevaluatedInputsMap {
162    pub map: IndexMap<String, Option<Diagnostic>>,
163}
164impl UnevaluatedInputsMap {
165    pub fn new() -> Self {
166        Self { map: IndexMap::new() }
167    }
168
169    pub fn insert(&mut self, key: String, value: Option<Diagnostic>) {
170        self.map.insert(key, value);
171    }
172    pub fn contains_key(&self, key: &str) -> bool {
173        self.map.contains_key(key)
174    }
175
176    pub fn check_for_diagnostic(&self, key: &str) -> Result<(), Diagnostic> {
177        match self.map.get(key) {
178            Some(diag) => match diag {
179                Some(diag) => Err(diag.clone()),
180                None => {
181                    Err(Diagnostic::error_from_string(format!("input '{}' was not evaluated", key)))
182                }
183            },
184            _ => Ok(()),
185        }
186    }
187    pub fn merge(&mut self, other: &UnevaluatedInputsMap) {
188        for (key, value) in other.map.iter() {
189            self.map.insert(key.clone(), value.clone());
190        }
191    }
192}
193
194impl Serialize for CommandInputsEvaluationResult {
195    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
196    where
197        S: Serializer,
198    {
199        let mut map = serializer.serialize_map(Some(self.inputs.len()))?;
200        for (k, v) in self.inputs.iter() {
201            map.serialize_entry(&k, &v)?;
202        }
203        map.end()
204    }
205}
206
207impl CommandInputsEvaluationResult {
208    pub fn new(name: &str, defaults: &ValueMap) -> Self {
209        Self {
210            inputs: ValueStore::new(&format!("{name}_inputs"), &Did::zero())
211                .with_defaults(defaults),
212            unevaluated_inputs: UnevaluatedInputsMap::new(),
213        }
214    }
215
216    pub fn insert(&mut self, key: &str, value: Value) {
217        self.inputs.insert(key, value);
218    }
219}
220
221#[derive(Clone, Debug, Eq, PartialEq, Hash)]
222pub struct CommandInput {
223    pub name: String,
224    pub documentation: String,
225    pub typing: Type,
226    pub optional: bool,
227    pub tainting: bool,
228    pub check_required: bool,
229    pub check_performed: bool,
230    pub sensitive: bool,
231    pub internal: bool,
232}
233impl EvaluatableInput for CommandInput {
234    fn optional(&self) -> bool {
235        self.optional
236    }
237    fn typing(&self) -> &Type {
238        &self.typing
239    }
240    fn name(&self) -> String {
241        self.name.clone()
242    }
243}
244
245impl CommandInput {
246    pub fn as_object(&self) -> Option<&ObjectDefinition> {
247        self.typing.as_object()
248    }
249    pub fn as_array(&self) -> Option<&Box<Type>> {
250        self.typing.as_array()
251    }
252    pub fn as_action(&self) -> Option<&String> {
253        self.typing.as_action()
254    }
255    pub fn as_map(&self) -> Option<&ObjectDefinition> {
256        self.typing.as_map()
257    }
258    pub fn check_value(&self, value: &Value) -> Result<(), Diagnostic> {
259        self.typing.check_value(value).map_err(|e| {
260            Diagnostic::error_from_string(format!("error in input '{}': {}", self.name, e.message))
261        })
262    }
263}
264
265impl Serialize for CommandInput {
266    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
267    where
268        S: Serializer,
269    {
270        let mut ser = serializer.serialize_struct("CommandInput", 4)?;
271        ser.serialize_field("name", &self.name)?;
272        ser.serialize_field("documentation", &self.documentation)?;
273        ser.serialize_field("typing", &self.typing)?;
274        ser.serialize_field("optional", &self.optional)?;
275        ser.end()
276    }
277}
278
279#[derive(Clone, PartialEq, Eq, Debug)]
280pub struct CommandOutput {
281    pub name: String,
282    pub documentation: String,
283    pub typing: Type,
284}
285
286impl Serialize for CommandOutput {
287    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
288    where
289        S: Serializer,
290    {
291        let mut ser = serializer.serialize_struct("CommandOutput", 4)?;
292        ser.serialize_field("name", &self.name)?;
293        ser.serialize_field("documentation", &self.documentation)?;
294        ser.serialize_field("typing", &self.typing)?;
295        ser.end()
296    }
297}
298
299#[derive(Clone, Debug, PartialEq, Eq, Hash)]
300pub enum CommandId {
301    Action(String),
302}
303
304impl CommandId {
305    pub fn to_string(&self) -> String {
306        match &self {
307            &CommandId::Action(id) => format!("action::{id}"),
308        }
309    }
310
311    pub fn action_name(&self) -> String {
312        match &self {
313            &CommandId::Action(id) => format!("{id}"),
314        }
315    }
316}
317
318#[derive(Clone, PartialEq, Eq, Debug)]
319pub enum PreCommandSpecification {
320    Atomic(CommandSpecification),
321    Composite(CompositeCommandSpecification),
322}
323
324impl PreCommandSpecification {
325    pub fn expect_atomic_specification(&self) -> &CommandSpecification {
326        match &self {
327            PreCommandSpecification::Atomic(spec) => spec,
328            _ => unreachable!(),
329        }
330    }
331}
332
333#[derive(Clone, PartialEq, Eq, Debug)]
334pub struct CommandSpecification {
335    pub name: String,
336    pub matcher: String,
337    pub documentation: String,
338    pub accepts_arbitrary_inputs: bool,
339    pub create_output_for_each_input: bool,
340    pub create_critical_output: Option<String>,
341    pub update_addon_defaults: bool,
342    pub implements_signing_capability: bool,
343    pub implements_background_task_capability: bool,
344    pub example: String,
345    pub default_inputs: Vec<CommandInput>,
346    pub inputs: Vec<CommandInput>,
347    pub outputs: Vec<CommandOutput>,
348    pub inputs_post_processing_closure: InputsPostProcessingClosure,
349    pub check_instantiability: InstantiabilityChecker,
350    pub check_executability: CommandCheckExecutabilityClosure,
351    pub prepare_nested_execution: CommandPrepareNestedExecution,
352    pub run_execution: CommandExecutionClosure,
353    pub check_signed_executability: CommandCheckSignedExecutabilityClosure,
354    pub prepare_signed_nested_execution: CommandSignedPrepareNestedExecution,
355    pub run_signed_execution: CommandSignedExecutionClosure,
356    pub build_background_task: CommandBackgroundTaskExecutionClosure,
357    pub implements_cloud_service: bool,
358    pub aggregate_nested_execution_results: CommandAggregateNestedExecutionResults,
359}
360
361#[derive(Clone, PartialEq, Eq, Debug)]
362pub struct CompositeCommandSpecification {
363    pub name: String,
364    pub matcher: String,
365    pub documentation: String,
366    pub parts: Vec<PreCommandSpecification>,
367    pub default_inputs: Vec<CommandInput>,
368    pub router: CommandRouter,
369    pub example: String,
370}
371
372impl CommandSpecification {
373    pub fn default_inputs() -> Vec<CommandInput> {
374        vec![
375            CommandInput {
376                name: "description".into(),
377                documentation: "Allows you to describe and comment steps of your runbook".into(),
378                typing: Type::string(),
379                optional: true,
380                tainting: true,
381                internal: false,
382                check_performed: false,
383                check_required: false,
384                sensitive: false,
385            },
386            CommandInput {
387                name: "labels".into(),
388                documentation: "Allows you to label steps of your runbook".into(),
389                typing: Type::array(Type::string()),
390                optional: true,
391                tainting: true,
392                internal: false,
393                check_performed: false,
394                check_required: false,
395                sensitive: false,
396            },
397            CommandInput {
398                name: "environments".into(),
399                documentation: "Only enable command for given environments (default: all)".into(),
400                typing: Type::array(Type::string()),
401                optional: true,
402                tainting: true,
403                internal: false,
404                check_performed: false,
405                check_required: false,
406                sensitive: false,
407            },
408            CommandInput {
409                name: "sensitive".into(),
410                documentation: "Never include value in logs".into(),
411                typing: Type::array(Type::string()),
412                optional: true,
413                tainting: true,
414                internal: true,
415                check_performed: false,
416                check_required: false,
417                sensitive: false,
418            },
419            CommandInput {
420                name: "group".into(),
421                documentation: "Name used for grouping commands together".into(),
422                typing: Type::array(Type::string()),
423                optional: true,
424                tainting: true,
425                internal: true,
426                check_performed: false,
427                check_required: false,
428                sensitive: false,
429            },
430            CommandInput {
431                name: "depends_on".into(),
432                documentation: "Name used for grouping commands together".into(),
433                typing: Type::array(Type::string()),
434                optional: true,
435                tainting: false,
436                internal: false,
437                check_performed: false,
438                check_required: false,
439                sensitive: false,
440            },
441        ]
442    }
443}
444
445impl Serialize for CommandSpecification {
446    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
447    where
448        S: Serializer,
449    {
450        let mut ser = serializer.serialize_struct("CommandSpecification", 6)?;
451        ser.serialize_field("id", &self.matcher)?;
452        ser.serialize_field("name", &self.name)?;
453        ser.serialize_field("documentation", &self.documentation)?;
454        ser.serialize_field("inputs", &self.inputs)?;
455        ser.serialize_field("outputs", &self.outputs)?;
456        ser.serialize_field("example", &self.example)?;
457        ser.end()
458    }
459}
460
461impl Serialize for CompositeCommandSpecification {
462    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
463    where
464        S: Serializer,
465    {
466        // todo
467        let mut ser = serializer.serialize_struct("CompositeCommandSpecification", 2)?;
468        ser.serialize_field("name", &self.name)?;
469        ser.serialize_field("documentation", &self.documentation)?;
470        ser.end()
471    }
472}
473
474pub type InstantiabilityChecker = fn(&CommandSpecification, Vec<Type>) -> Result<Type, Diagnostic>;
475
476pub type InputsPostProcessingClosure =
477    fn(&CommandSpecification, CommandInputsEvaluationResult) -> InputsPostProcessingFutureResult;
478pub type InputsPostProcessingFutureResult = Result<InputsPostProcessingFuture, Diagnostic>;
479pub type InputsPostProcessingFuture =
480    Pin<Box<dyn Future<Output = Result<CommandInputsEvaluationResult, Diagnostic>> + Send>>;
481
482pub type CommandExecutionFutureResult = Result<CommandExecutionFuture, Diagnostic>;
483pub type CommandExecutionFuture =
484    Pin<Box<dyn Future<Output = Result<CommandExecutionResult, Diagnostic>> + Send>>;
485
486pub type CommandExecutionClosure = Box<
487    fn(
488        &ConstructDid,
489        &CommandSpecification,
490        &ValueStore,
491        &channel::Sender<BlockEvent>,
492    ) -> CommandExecutionFutureResult,
493>;
494
495pub type CommandBackgroundTaskExecutionClosure = Box<
496    fn(
497        &ConstructDid,
498        &CommandSpecification,
499        &ValueStore,
500        &ValueStore,
501        &channel::Sender<BlockEvent>,
502        &Uuid,
503        &RunbookSupervisionContext,
504        &Option<CloudServiceContext>,
505    ) -> CommandExecutionFutureResult,
506>;
507
508pub type CommandAggregateNestedExecutionResults = fn(
509    &ConstructDid,
510    &Vec<(ConstructDid, ValueStore)>,
511    &Vec<CommandExecutionResult>,
512) -> Result<CommandExecutionResult, Diagnostic>;
513
514pub type CommandSignedExecutionClosure = Box<
515    fn(
516        &ConstructDid,
517        &CommandSpecification,
518        &ValueStore,
519        &channel::Sender<BlockEvent>,
520        &HashMap<ConstructDid, SignerInstance>,
521        SignersState,
522    ) -> SignerSignFutureResult,
523>;
524
525type CommandRouter =
526    fn(&String, &String, &Vec<PreCommandSpecification>) -> Result<Vec<String>, Diagnostic>;
527
528pub type CommandCheckExecutabilityClosure = fn(
529    &ConstructDid,
530    &str,
531    &CommandSpecification,
532    &ValueStore,
533    &RunbookSupervisionContext,
534) -> Result<Actions, Diagnostic>;
535
536pub type CommandCheckSignedExecutabilityClosure = fn(
537    &ConstructDid,
538    &str,
539    &CommandSpecification,
540    &ValueStore,
541    &RunbookSupervisionContext,
542    &HashMap<ConstructDid, SignerInstance>,
543    SignersState,
544) -> SignerActionsFutureResult;
545
546pub type CommandSignedPrepareNestedExecution = fn(
547    &ConstructDid,
548    &str,
549    &ValueStore,
550    &HashMap<ConstructDid, SignerInstance>,
551    SignersState,
552) -> PrepareSignedNestedExecutionResult;
553
554pub type CommandPrepareNestedExecution =
555    fn(&ConstructDid, &str, &ValueStore) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic>;
556
557pub fn return_synchronous_result(
558    res: Result<CommandExecutionResult, Diagnostic>,
559) -> CommandExecutionFutureResult {
560    Ok(Box::pin(future::ready(res)))
561}
562
563pub fn return_synchronous_ok(res: CommandExecutionResult) -> CommandExecutionFutureResult {
564    return_synchronous_result(Ok(res))
565}
566
567pub fn return_synchronous_err(diag: Diagnostic) -> CommandExecutionFutureResult {
568    return_synchronous_result(Err(diag))
569}
570
571pub trait CompositeCommandImplementation {
572    fn router(
573        _first_input_body: &String,
574        _command_instance_name: &String,
575        _parts: &Vec<PreCommandSpecification>,
576    ) -> Result<Vec<String>, Diagnostic>;
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
580pub enum CommandInstanceType {
581    Variable,
582    Output,
583    Action(String),
584    Prompt,
585    Module,
586    Addon,
587}
588
589impl CommandInstanceType {
590    pub fn to_ident(&self) -> &str {
591        match self {
592            CommandInstanceType::Variable => "variable",
593            CommandInstanceType::Output => "output",
594            CommandInstanceType::Action(_) => "action",
595            CommandInstanceType::Prompt => "prompt",
596            CommandInstanceType::Module => "module",
597            CommandInstanceType::Addon => "addon",
598        }
599    }
600}
601
602#[derive(Debug, Clone)]
603pub struct CommandInstance {
604    pub specification: CommandSpecification,
605    pub name: String,
606    pub block: Block,
607    pub package_id: PackageId,
608    pub namespace: String,
609    pub typing: CommandInstanceType,
610}
611pub enum CommandExecutionStatus {
612    Complete(Result<CommandExecutionResult, Diagnostic>),
613    NeedsAsyncRequest,
614}
615
616impl Serialize for CommandInstance {
617    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
618    where
619        S: Serializer,
620    {
621        let mut ser = serializer.serialize_struct("CommandInstance", 6)?;
622        ser.serialize_field("specification", &self.specification)?;
623        ser.serialize_field("name", &self.name)?;
624        ser.serialize_field("packageUuid", &self.package_id.did())?;
625        ser.serialize_field("namespace", &self.namespace)?;
626        ser.serialize_field("typing", &self.typing)?;
627        ser.end()
628    }
629}
630
631impl WithEvaluatableInputs for CommandInstance {
632    fn name(&self) -> String {
633        self.name.clone()
634    }
635    fn block(&self) -> &Block {
636        &self.block
637    }
638    /// Checks the `CommandInstance` HCL Block for an attribute named `input.name`
639    fn get_expression_from_input(&self, input_name: &str) -> Option<Expression> {
640        visit_optional_untyped_attribute(&input_name, &self.block)
641    }
642
643    fn get_blocks_for_map(
644        &self,
645        input_name: &str,
646        input_typing: &Type,
647        input_optional: bool,
648    ) -> Result<Option<Vec<Block>>, Vec<Diagnostic>> {
649        let mut entries = vec![];
650
651        match &input_typing {
652            Type::Map(_) => {
653                for block in self.block.body.get_blocks(&input_name) {
654                    entries.push(block.clone());
655                }
656            }
657            _ => {
658                unreachable!()
659            }
660        };
661        if entries.is_empty() {
662            if !input_optional {
663                return Err(vec![Diagnostic::error_from_string(format!(
664                    "command '{}' (type '{}') is missing value for object '{}'",
665                    self.name, self.specification.matcher, input_name
666                ))]);
667            } else {
668                return Ok(None);
669            }
670        }
671        Ok(Some(entries))
672    }
673
674    fn get_expression_from_block(
675        &self,
676        block: &Block,
677        prop: &ObjectProperty,
678    ) -> Option<Expression> {
679        visit_optional_untyped_attribute(&prop.name, &block)
680    }
681
682    fn get_expression_from_object(
683        &self,
684        input_name: &str,
685        input_typing: &Type,
686    ) -> Result<Option<Expression>, Vec<Diagnostic>> {
687        match &input_typing {
688            Type::Object(_) => Ok(visit_optional_untyped_attribute(&input_name, &self.block)),
689            _ => Err(vec![Diagnostic::error_from_string(format!(
690                "command '{}' (type '{}') expected object for input '{}'",
691                self.name, self.specification.matcher, input_name
692            ))]),
693        }
694    }
695
696    fn get_expression_from_object_property(
697        &self,
698        input_name: &str,
699        prop: &ObjectProperty,
700    ) -> Option<Expression> {
701        let expr = visit_optional_untyped_attribute(&input_name, &self.block);
702        match expr {
703            Some(expr) => {
704                let object_expr = expr.as_object().unwrap();
705                let expr_res = get_object_expression_key(object_expr, &prop.name);
706                match expr_res {
707                    Some(expression) => Some(expression.expr().clone()),
708                    None => None,
709                }
710            }
711            None => None,
712        }
713    }
714    fn spec_inputs(&self) -> Vec<impl EvaluatableInput> {
715        self.specification.inputs.iter().map(|x| x.clone()).collect()
716    }
717}
718
719impl CommandInstance {
720    pub async fn post_process_inputs_evaluations(
721        &self,
722        inputs_evaluation: CommandInputsEvaluationResult,
723    ) -> Result<CommandInputsEvaluationResult, Diagnostic> {
724        let spec = &self.specification;
725        let future = (self.specification.inputs_post_processing_closure)(spec, inputs_evaluation)?;
726        let res = future.await?;
727        Ok(res)
728    }
729
730    pub fn get_group(&self) -> String {
731        let Some(group) = self.block.body.get_attribute("group") else {
732            return format!("{} Review", self.specification.name.to_string());
733        };
734        group.value.to_string()
735    }
736
737    pub fn check_executability(
738        &mut self,
739        construct_did: &ConstructDid,
740        nested_evaluation_values: &ValueStore,
741        evaluated_inputs: &mut CommandInputsEvaluationResult,
742        _signer_instances: &mut HashMap<ConstructDid, SignerInstance>,
743        action_item_response: &Option<&Vec<ActionItemResponse>>,
744        supervision_context: &RunbookSupervisionContext,
745    ) -> Result<Actions, Diagnostic> {
746        let mut values = ValueStore::new(
747            &format!("{}_inputs", self.specification.matcher),
748            &construct_did.value(),
749        )
750        .with_defaults(&evaluated_inputs.inputs.defaults)
751        .append_inputs(&nested_evaluation_values.inputs);
752
753        let mut consolidated_actions = Actions::none();
754        match action_item_response {
755            Some(responses) => {
756                responses.into_iter().for_each(|ActionItemResponse { action_item_id, payload }| {
757                    match payload {
758                        ActionItemResponseType::ReviewInput(ReviewedInputResponse {
759                            input_name,
760                            value_checked,
761                            ..
762                        }) => {
763                            for input in self.specification.inputs.iter_mut() {
764                                if &input.name == input_name {
765                                    input.check_performed = value_checked.clone();
766                                    break;
767                                }
768                            }
769                        }
770                        ActionItemResponseType::ProvideInput(ProvidedInputResponse {
771                            input_name,
772                            updated_value,
773                        }) => {
774                            evaluated_inputs.inputs.insert(&input_name, updated_value.clone());
775
776                            let action_item_update =
777                                ActionItemRequestUpdate::from_id(&action_item_id)
778                                    .set_type(ActionItemRequestType::ProvideInput(
779                                        ProvideInputRequest {
780                                            default_value: Some(updated_value.clone()),
781                                            input_name: input_name.clone(),
782                                            typing: updated_value.get_type(),
783                                        },
784                                    ))
785                                    .set_status(ActionItemStatus::Success(None));
786                            consolidated_actions.push_action_item_update(action_item_update);
787
788                            for input in self.specification.inputs.iter_mut() {
789                                if &input.name == input_name {
790                                    input.check_performed = true;
791                                    break;
792                                }
793                            }
794                        }
795                        ActionItemResponseType::ProvideSignedTransaction(response) => {
796                            match &response.signed_transaction_bytes {
797                                Some(bytes) => values
798                                    .insert(SIGNED_TRANSACTION_BYTES, Value::string(bytes.clone())),
799                                None => values.insert(SIGNED_TRANSACTION_BYTES, Value::null()),
800                            }
801                        }
802                        ActionItemResponseType::ProvideSignedMessage(response) => {
803                            values.insert(
804                                SIGNED_MESSAGE_BYTES,
805                                Value::string(response.signed_message_bytes.clone()),
806                            );
807                        }
808                        _ => {}
809                    }
810                })
811            }
812            None => {}
813        }
814        let values = values
815            .with_inputs(&evaluated_inputs.inputs.inputs)
816            .check(&self.name, &self.specification.inputs)?;
817
818        let spec = &self.specification;
819        if spec.matcher != "output" {
820            let mut actions = (spec.check_executability)(
821                &construct_did,
822                &self.name,
823                &spec,
824                &values,
825                &supervision_context,
826            )?;
827            consolidated_actions.append(&mut actions);
828        }
829        Ok(consolidated_actions)
830    }
831
832    pub async fn perform_execution(
833        &self,
834        construct_did: &ConstructDid,
835        nested_evaluation_values: &ValueStore,
836        evaluated_inputs: &CommandInputsEvaluationResult,
837        action_item_requests: &mut Vec<&mut ActionItemRequest>,
838        _action_item_responses: &Option<&Vec<ActionItemResponse>>,
839        progress_tx: &channel::Sender<BlockEvent>,
840    ) -> Result<CommandExecutionResult, Diagnostic> {
841        let values = ValueStore::new(&self.name, &construct_did.value())
842            .with_defaults(&evaluated_inputs.inputs.defaults)
843            .with_inputs(&evaluated_inputs.inputs.inputs)
844            .append_inputs(&nested_evaluation_values.inputs);
845
846        let spec = &self.specification;
847        let res = (spec.run_execution)(&construct_did, &self.specification, &values, progress_tx)?
848            .await
849            .map_err(|e| e.set_span_range(self.block.span()));
850
851        for request in action_item_requests.iter_mut() {
852            let (status, success) = match &res {
853                Ok(_) => (ActionItemStatus::Success(None), true),
854                Err(diag) => (ActionItemStatus::Error(diag.clone()), false),
855            };
856            match request.action_type {
857                ActionItemRequestType::ReviewInput(_) => {
858                    request.action_status = status.clone();
859                }
860                ActionItemRequestType::ProvideInput(_) => {
861                    request.action_status = status.clone();
862                }
863                ActionItemRequestType::ProvidePublicKey(_) => {
864                    if success {
865                        request.action_status = status.clone();
866                    }
867                }
868                ActionItemRequestType::ProvideSignedTransaction(_) => {
869                    if success {
870                        request.action_status = status.clone();
871                    }
872                }
873                ActionItemRequestType::SendTransaction(_) => {
874                    if success {
875                        request.action_status = status.clone();
876                    }
877                }
878                ActionItemRequestType::ProvideSignedMessage(_) => {
879                    if success {
880                        request.action_status = status.clone();
881                    }
882                }
883                _ => unreachable!(),
884            }
885        }
886        res
887    }
888
889    pub async fn prepare_signed_nested_execution(
890        &self,
891        construct_did: &ConstructDid,
892        evaluated_inputs: &CommandInputsEvaluationResult,
893        signers: SignersState,
894        signer_instances: &HashMap<ConstructDid, SignerInstance>,
895    ) -> Result<(SignersState, Vec<(ConstructDid, ValueStore)>), (SignersState, Diagnostic)> {
896        let values = ValueStore::new(&self.name, &construct_did.value())
897            .with_defaults(&evaluated_inputs.inputs.defaults)
898            .with_inputs(&evaluated_inputs.inputs.inputs);
899
900        let spec = &self.specification;
901        let future = (spec.prepare_signed_nested_execution)(
902            &construct_did,
903            &self.name,
904            &values,
905            signer_instances,
906            signers,
907        );
908        return consolidate_nested_execution_result(future, self.block.span()).await;
909    }
910
911    pub fn prepare_nested_execution(
912        &self,
913        construct_did: &ConstructDid,
914        evaluated_inputs: &CommandInputsEvaluationResult,
915    ) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic> {
916        let values = ValueStore::new(&self.name, &construct_did.value())
917            .with_defaults(&evaluated_inputs.inputs.defaults)
918            .with_inputs(&evaluated_inputs.inputs.inputs);
919
920        let spec = &self.specification;
921
922        (spec.prepare_nested_execution)(&construct_did, &self.name, &values)
923    }
924
925    pub async fn check_signed_executability(
926        &mut self,
927        construct_did: &ConstructDid,
928        nested_evaluation_values: &ValueStore,
929        evaluated_inputs: &CommandInputsEvaluationResult,
930        signers: SignersState,
931        signer_instances: &mut HashMap<ConstructDid, SignerInstance>,
932        action_item_response: &Option<&Vec<ActionItemResponse>>,
933        action_item_requests: &Option<&Vec<&mut ActionItemRequest>>,
934        supervision_context: &RunbookSupervisionContext,
935    ) -> Result<(SignersState, Actions), (SignersState, Diagnostic)> {
936        let values = ValueStore::new(&self.name, &construct_did.value())
937            .with_defaults(&evaluated_inputs.inputs.defaults)
938            .with_inputs(&evaluated_inputs.inputs.inputs)
939            .append_inputs(&nested_evaluation_values.inputs);
940
941        // TODO
942        let mut consolidated_actions = Actions::none();
943        match action_item_response {
944            Some(responses) => {
945                responses.into_iter().for_each(|ActionItemResponse { action_item_id, payload }| {
946                    match payload {
947                        ActionItemResponseType::ReviewInput(update) => {
948                            // This is a shortcut and should be mutated somewhere else
949                            for input in self.specification.inputs.iter_mut() {
950                                if input.name == update.input_name {
951                                    input.check_performed = true;
952                                    break;
953                                }
954                            }
955                        }
956                        ActionItemResponseType::ProvideInput(update) => {
957                            let action_item_update =
958                                ActionItemRequestUpdate::from_id(&action_item_id)
959                                    .set_type(ActionItemRequestType::ProvideInput(
960                                        ProvideInputRequest {
961                                            default_value: Some(update.updated_value.clone()),
962                                            input_name: update.input_name.clone(),
963                                            typing: update.updated_value.get_type(),
964                                        },
965                                    ))
966                                    .set_status(ActionItemStatus::Success(None));
967                            consolidated_actions.push_action_item_update(action_item_update);
968                        }
969                        ActionItemResponseType::ProvideSignedTransaction(_) => {
970                            let action_item_update =
971                                ActionItemRequestUpdate::from_id(&action_item_id)
972                                    .set_status(ActionItemStatus::Success(None));
973                            consolidated_actions.push_action_item_update(action_item_update);
974                        }
975                        ActionItemResponseType::SendTransaction(_) => {
976                            let action_item_update =
977                                ActionItemRequestUpdate::from_id(&action_item_id)
978                                    .set_status(ActionItemStatus::Success(None));
979                            consolidated_actions.push_action_item_update(action_item_update);
980                        }
981                        ActionItemResponseType::ProvideSignedMessage(_response) => {
982                            let action_item_update =
983                                ActionItemRequestUpdate::from_id(&action_item_id)
984                                    .set_status(ActionItemStatus::Success(None));
985                            consolidated_actions.push_action_item_update(action_item_update);
986                        }
987                        _ => {}
988                    }
989                })
990            }
991            None => {}
992        }
993
994        let spec = &self.specification;
995        let future = (spec.check_signed_executability)(
996            &construct_did,
997            &self.name,
998            &self.specification,
999            &values,
1000            &supervision_context,
1001            signer_instances,
1002            signers,
1003        );
1004        let (signer_state, mut actions) =
1005            consolidate_signer_future_result(future, self.block.span()).await?;
1006        consolidated_actions.append(&mut actions);
1007        consolidated_actions.filter_existing_action_items(action_item_requests);
1008        Ok((signer_state, consolidated_actions))
1009    }
1010
1011    pub async fn perform_signed_execution(
1012        &self,
1013        construct_did: &ConstructDid,
1014        nested_evaluation_values: &ValueStore,
1015        evaluated_inputs: &CommandInputsEvaluationResult,
1016        signers: SignersState,
1017        signer_instances: &HashMap<ConstructDid, SignerInstance>,
1018        action_item_requests: &mut Vec<&mut ActionItemRequest>,
1019        _action_item_responses: &Option<&Vec<ActionItemResponse>>,
1020        progress_tx: &channel::Sender<BlockEvent>,
1021    ) -> Result<(SignersState, CommandExecutionResult), (SignersState, Diagnostic)> {
1022        let values = ValueStore::new(&self.name, &construct_did.value())
1023            .with_defaults(&evaluated_inputs.inputs.defaults)
1024            .with_inputs(&evaluated_inputs.inputs.inputs)
1025            .append_inputs(&nested_evaluation_values.inputs);
1026
1027        let spec = &self.specification;
1028        let future = (spec.run_signed_execution)(
1029            &construct_did,
1030            &self.specification,
1031            &values,
1032            progress_tx,
1033            signer_instances,
1034            signers,
1035        );
1036        let res = consolidate_signer_activate_future_result(future, self.block.span()).await?;
1037
1038        for request in action_item_requests.iter_mut() {
1039            let (status, success) = match &res {
1040                Ok(_) => (ActionItemStatus::Success(None), true),
1041                Err((_, diag)) => (ActionItemStatus::Error(diag.clone()), false),
1042            };
1043            match request.action_type {
1044                ActionItemRequestType::ReviewInput(_) => {
1045                    request.action_status = status.clone();
1046                }
1047                ActionItemRequestType::ProvidePublicKey(_) => {
1048                    if success {
1049                        request.action_status = status.clone();
1050                    }
1051                }
1052                ActionItemRequestType::ProvideSignedTransaction(_) => {
1053                    if success {
1054                        request.action_status = status.clone();
1055                    }
1056                }
1057                ActionItemRequestType::SendTransaction(_) => {
1058                    if success {
1059                        request.action_status = status.clone();
1060                    }
1061                }
1062                ActionItemRequestType::ProvideSignedMessage(_) => {
1063                    if success {
1064                        request.action_status = status.clone();
1065                    }
1066                }
1067                _ => {}
1068            }
1069        }
1070        res
1071    }
1072
1073    pub fn build_background_task(
1074        &self,
1075        construct_did: &ConstructDid,
1076        nested_evaluation_values: &ValueStore,
1077        evaluated_inputs: &CommandInputsEvaluationResult,
1078        execution_result: &CommandExecutionResult,
1079        progress_tx: &channel::Sender<BlockEvent>,
1080        background_tasks_uuid: &Uuid,
1081        supervision_context: &RunbookSupervisionContext,
1082        cloud_svc_context: &CloudServiceContext,
1083    ) -> CommandExecutionFutureResult {
1084        let values = ValueStore::new(&self.name, &construct_did.value())
1085            .with_defaults(&evaluated_inputs.inputs.defaults)
1086            .with_inputs(&evaluated_inputs.inputs.inputs)
1087            .with_inputs_from_map(&execution_result.outputs)
1088            .append_inputs(&nested_evaluation_values.inputs);
1089        let outputs = ValueStore::new(&self.name, &construct_did.value())
1090            .with_inputs_from_map(&execution_result.outputs);
1091
1092        let spec = &self.specification;
1093        let res = (spec.build_background_task)(
1094            &construct_did,
1095            &self.specification,
1096            &values,
1097            &outputs,
1098            progress_tx,
1099            background_tasks_uuid,
1100            supervision_context,
1101            &if spec.implements_cloud_service { Some(cloud_svc_context.clone()) } else { None },
1102        );
1103        res
1104    }
1105
1106    pub fn aggregate_nested_execution_results(
1107        &self,
1108        construct_did: &ConstructDid,
1109        nested_values: &Vec<(ConstructDid, ValueStore)>,
1110        commands_execution_results: &HashMap<ConstructDid, CommandExecutionResult>,
1111    ) -> Result<CommandExecutionResult, Diagnostic> {
1112        let mut nested_results = vec![];
1113        for (nested_construct_did, _) in nested_values {
1114            let nested_result = commands_execution_results
1115                .get(nested_construct_did)
1116                .cloned()
1117                .unwrap_or_else(|| {
1118                    return CommandExecutionResult::new();
1119                });
1120            nested_results.push(nested_result);
1121        }
1122
1123        (self.specification.aggregate_nested_execution_results)(
1124            &construct_did,
1125            &nested_values,
1126            &nested_results,
1127        )
1128    }
1129}
1130
1131impl ConstructInstance for CommandInstance {
1132    fn block(&self) -> &Block {
1133        &self.block
1134    }
1135    fn inputs(&self) -> Vec<&impl EvaluatableInput> {
1136        self.specification.inputs.iter().chain(&self.specification.default_inputs).collect()
1137    }
1138    fn accepts_arbitrary_inputs(&self) -> bool {
1139        self.specification.accepts_arbitrary_inputs
1140    }
1141}
1142
1143pub trait ConstructInstance {
1144    /// The HCL block of the construct
1145    fn block(&self) -> &Block;
1146    fn inputs(&self) -> Vec<&impl EvaluatableInput>;
1147    fn accepts_arbitrary_inputs(&self) -> bool {
1148        false
1149    }
1150
1151    fn get_expressions_referencing_commands_from_inputs(
1152        &self,
1153    ) -> Vec<(Option<&impl EvaluatableInput>, Expression)> {
1154        let mut expressions = vec![];
1155        for input in self.inputs() {
1156            input.typing().get_expressions_referencing_constructs(
1157                &self.block(),
1158                input,
1159                &mut expressions,
1160            );
1161        }
1162        if self.accepts_arbitrary_inputs() {
1163            for attribute in self.block().body.attributes() {
1164                let mut references = vec![];
1165                collect_constructs_references_from_expression(
1166                    &attribute.value,
1167                    None,
1168                    &mut references,
1169                );
1170                expressions.append(&mut references);
1171            }
1172        }
1173        expressions
1174    }
1175}
1176
1177pub trait CommandImplementation {
1178    fn post_process_evaluated_inputs(
1179        _ctx: &CommandSpecification,
1180        inputs: CommandInputsEvaluationResult,
1181    ) -> InputsPostProcessingFutureResult {
1182        let future = async move { Ok(inputs) };
1183        Ok(Box::pin(future))
1184    }
1185
1186    fn check_instantiability(
1187        _ctx: &CommandSpecification,
1188        _args: Vec<Type>,
1189    ) -> Result<Type, Diagnostic>;
1190
1191    fn check_executability(
1192        _construct_id: &ConstructDid,
1193        _instance_name: &str,
1194        _spec: &CommandSpecification,
1195        _values: &ValueStore,
1196        _supervision_context: &RunbookSupervisionContext,
1197    ) -> Result<Actions, Diagnostic> {
1198        unimplemented!()
1199    }
1200
1201    fn run_execution(
1202        _construct_id: &ConstructDid,
1203        _spec: &CommandSpecification,
1204        _values: &ValueStore,
1205        _progress_tx: &channel::Sender<BlockEvent>,
1206    ) -> CommandExecutionFutureResult {
1207        unimplemented!()
1208    }
1209
1210    fn check_signed_executability(
1211        _construct_id: &ConstructDid,
1212        _instance_name: &str,
1213        _spec: &CommandSpecification,
1214        _values: &ValueStore,
1215        _supervision_context: &RunbookSupervisionContext,
1216        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
1217        _signers_state: SignersState,
1218    ) -> SignerActionsFutureResult {
1219        unimplemented!()
1220    }
1221
1222    fn prepare_signed_nested_execution(
1223        construct_did: &ConstructDid,
1224        instance_name: &str,
1225        _values: &ValueStore,
1226        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
1227        signers_state: SignersState,
1228    ) -> PrepareSignedNestedExecutionResult {
1229        let signer_state = signers_state
1230            .get_first_signer()
1231            .expect(&format!("no signers provided for action '{}'", instance_name));
1232        return_synchronous((
1233            signers_state,
1234            signer_state,
1235            vec![(
1236                construct_did.clone(),
1237                ValueStore::new(&construct_did.to_string(), &construct_did.0),
1238            )],
1239        ))
1240    }
1241
1242    fn prepare_nested_execution(
1243        construct_did: &ConstructDid,
1244        _instance_name: &str,
1245        _values: &ValueStore,
1246    ) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic> {
1247        Ok(vec![(
1248            construct_did.clone(),
1249            ValueStore::new(&construct_did.to_string(), &construct_did.0),
1250        )])
1251    }
1252
1253    fn aggregate_nested_execution_results(
1254        _construct_did: &ConstructDid,
1255        _values: &Vec<(ConstructDid, ValueStore)>,
1256        nested_results: &Vec<CommandExecutionResult>,
1257    ) -> Result<CommandExecutionResult, Diagnostic> {
1258        let mut result = CommandExecutionResult::new();
1259        for nested_result in nested_results {
1260            result.outputs.extend(nested_result.outputs.clone());
1261        }
1262        Ok(result)
1263    }
1264
1265    fn run_signed_execution(
1266        _construct_id: &ConstructDid,
1267        _spec: &CommandSpecification,
1268        _values: &ValueStore,
1269        _progress_tx: &channel::Sender<BlockEvent>,
1270        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
1271        _signers_state: SignersState,
1272    ) -> SignerSignFutureResult {
1273        unimplemented!()
1274    }
1275
1276    fn build_background_task(
1277        _construct_did: &ConstructDid,
1278        _spec: &CommandSpecification,
1279        _values: &ValueStore,
1280        _outputs: &ValueStore,
1281        _progress_tx: &channel::Sender<BlockEvent>,
1282        _background_tasks_uuid: &Uuid,
1283        _supervision_context: &RunbookSupervisionContext,
1284        _cloud_service_context: &Option<CloudServiceContext>,
1285    ) -> CommandExecutionFutureResult {
1286        unimplemented!()
1287    }
1288}
1289
1290pub fn add_ctx_to_diag(
1291    command_type: String,
1292    matcher: String,
1293    command_instance_name: String,
1294    namespace: String,
1295) -> impl Fn(&Diagnostic) -> Diagnostic {
1296    let diag_with_command_ctx = move |diag: &Diagnostic| -> Diagnostic {
1297        let mut diag = diag.clone();
1298        diag.message = format!(
1299            "'{}:{}' {} '{}': {}",
1300            namespace, matcher, command_type, command_instance_name, diag.message
1301        );
1302        diag
1303    };
1304    return diag_with_command_ctx;
1305}
1306pub fn add_ctx_to_embedded_runbook_diag(
1307    embedded_runbook_instance_name: String,
1308) -> impl Fn(&Diagnostic) -> Diagnostic {
1309    let diag_with_command_ctx = move |diag: &Diagnostic| -> Diagnostic {
1310        let mut diag = diag.clone();
1311        diag.message =
1312            format!("embedded runbook '{}': {}", embedded_runbook_instance_name, diag.message);
1313        diag
1314    };
1315    return diag_with_command_ctx;
1316}