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>, }
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 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 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 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 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 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 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 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}