1#[cfg(feature = "schemars08")]
12use crate::spec::JsonSchemaEngineSpec;
13use crate::{
14 errors::ConvertGenericError,
15 spec::{EngineSpec, GenericSpec},
16};
17use derive_where::derive_where;
18use newtype_uuid::{TypedUuid, TypedUuidKind, TypedUuidTag};
19use serde::{Deserialize, Deserializer, Serialize, de::IgnoredAny};
20use std::{borrow::Cow, fmt, time::Duration};
21
22pub enum ExecutionUuidKind {}
24
25impl TypedUuidKind for ExecutionUuidKind {
26 #[inline]
27 fn tag() -> TypedUuidTag {
28 const TAG: TypedUuidTag = TypedUuidTag::new("execution_id");
29 TAG
30 }
31
32 #[inline]
33 fn alias() -> Option<&'static str> {
34 Some("ExecutionUuid")
37 }
38}
39
40#[cfg(feature = "schemars08")]
41impl schemars::JsonSchema for ExecutionUuidKind {
42 fn schema_name() -> String {
43 "ExecutionUuidKind".to_owned()
44 }
45
46 fn json_schema(
47 _: &mut schemars::r#gen::SchemaGenerator,
48 ) -> schemars::schema::Schema {
49 use crate::schema::{EVENTS_MODULE, rust_type_for_events};
50
51 schemars::schema::SchemaObject {
57 instance_type: Some(schemars::schema::InstanceType::String.into()),
58 format: Some("uuid".to_owned()),
59 extensions: [(
60 "x-rust-type".to_owned(),
61 rust_type_for_events(&format!(
62 "{EVENTS_MODULE}::ExecutionUuidKind"
63 )),
64 )]
65 .into_iter()
66 .collect(),
67 ..Default::default()
68 }
69 .into()
70 }
71}
72
73pub type ExecutionUuid = TypedUuid<ExecutionUuidKind>;
78
79#[derive_where(Clone, Debug, PartialEq, Eq)]
80pub enum Event<S: EngineSpec> {
81 Step(StepEvent<S>),
82 Progress(ProgressEvent<S>),
83}
84
85impl<S: EngineSpec> Event<S> {
86 pub fn from_generic(
91 value: Event<GenericSpec>,
92 ) -> Result<Self, ConvertGenericError> {
93 let ret = match value {
94 Event::Step(event) => Event::Step(StepEvent::from_generic(event)?),
95 Event::Progress(event) => {
96 Event::Progress(ProgressEvent::from_generic(event)?)
97 }
98 };
99 Ok(ret)
100 }
101
102 pub fn into_generic(self) -> Event<GenericSpec> {
113 match self {
114 Event::Step(event) => Event::Step(event.into_generic()),
115 Event::Progress(event) => Event::Progress(event.into_generic()),
116 }
117 }
118}
119
120#[derive(Deserialize, Serialize)]
121#[derive_where(Clone, Debug, Eq, PartialEq)]
122#[serde(bound = "", rename_all = "snake_case")]
123pub struct StepEvent<S: EngineSpec> {
124 pub spec: String,
140
141 pub execution_id: ExecutionUuid,
143
144 pub event_index: usize,
146
147 pub total_elapsed: Duration,
149
150 #[serde(rename = "data")]
152 pub kind: StepEventKind<S>,
153}
154
155#[cfg(feature = "schemars08")]
156impl<S: JsonSchemaEngineSpec> schemars::JsonSchema for StepEvent<S> {
157 fn schema_name() -> String {
158 format!("StepEventFor{}", S::schema_name())
159 }
160
161 fn json_schema(
162 generator: &mut schemars::r#gen::SchemaGenerator,
163 ) -> schemars::schema::Schema {
164 use crate::schema::with_description;
165 use schemars::schema::{ObjectValidation, SchemaObject};
166
167 let mut obj = ObjectValidation::default();
168 obj.properties.insert(
169 "spec".to_owned(),
170 with_description(
171 generator.subschema_for::<String>(),
172 "The specification that this event belongs to.",
173 ),
174 );
175 obj.properties.insert(
176 "execution_id".to_owned(),
177 with_description(
178 generator.subschema_for::<ExecutionUuid>(),
179 "The execution ID.",
180 ),
181 );
182 obj.properties.insert(
183 "event_index".to_owned(),
184 with_description(
185 generator.subschema_for::<usize>(),
186 "A monotonically increasing index for this \
187 `StepEvent`.",
188 ),
189 );
190 obj.properties.insert(
191 "total_elapsed".to_owned(),
192 with_description(
193 generator.subschema_for::<Duration>(),
194 "Total time elapsed since the start of execution.",
195 ),
196 );
197 obj.properties.insert(
198 "data".to_owned(),
199 with_description(
200 generator.subschema_for::<StepEventKind<S>>(),
201 "The kind of event this is.",
202 ),
203 );
204 obj.required =
205 ["spec", "execution_id", "event_index", "total_elapsed", "data"]
206 .into_iter()
207 .map(String::from)
208 .collect();
209
210 let mut extensions = serde_json::Map::new();
211 if let Some(info) = S::rust_type_info() {
212 extensions.insert(
213 "x-rust-type".to_owned(),
214 crate::schema::rust_type_for_generic(&info, "StepEvent"),
215 );
216 }
217
218 SchemaObject {
219 instance_type: Some(schemars::schema::InstanceType::Object.into()),
220 object: Some(Box::new(obj)),
221 extensions: extensions.into_iter().collect(),
222 ..Default::default()
223 }
224 .into()
225 }
226}
227
228impl<S: EngineSpec> StepEvent<S> {
229 pub fn progress_event(&self) -> Option<ProgressEvent<S>> {
235 match &self.kind {
236 StepEventKind::ExecutionStarted { first_step, .. } => {
237 Some(ProgressEvent {
238 spec: self.spec.clone(),
239 execution_id: self.execution_id,
240 total_elapsed: self.total_elapsed,
241 kind: ProgressEventKind::WaitingForProgress {
242 step: first_step.clone(),
243 attempt: 1,
244 step_elapsed: Duration::ZERO,
245 attempt_elapsed: Duration::ZERO,
246 },
247 })
248 }
249 StepEventKind::ProgressReset {
250 step,
251 attempt,
252 step_elapsed,
253 attempt_elapsed,
254 ..
255 } => Some(ProgressEvent {
256 spec: self.spec.clone(),
257 execution_id: self.execution_id,
258 total_elapsed: self.total_elapsed,
259 kind: ProgressEventKind::WaitingForProgress {
260 step: step.clone(),
261 attempt: *attempt,
262 step_elapsed: *step_elapsed,
263 attempt_elapsed: *attempt_elapsed,
264 },
265 }),
266 StepEventKind::AttemptRetry {
267 step,
268 next_attempt,
269 step_elapsed,
270 ..
271 } => Some(ProgressEvent {
272 spec: self.spec.clone(),
273 execution_id: self.execution_id,
274 total_elapsed: self.total_elapsed,
275 kind: ProgressEventKind::WaitingForProgress {
276 step: step.clone(),
277 attempt: *next_attempt,
278 step_elapsed: *step_elapsed,
279 attempt_elapsed: Duration::ZERO,
281 },
282 }),
283 StepEventKind::StepCompleted { next_step, .. } => {
284 Some(ProgressEvent {
285 spec: self.spec.clone(),
286 execution_id: self.execution_id,
287 total_elapsed: self.total_elapsed,
288 kind: ProgressEventKind::WaitingForProgress {
289 step: next_step.clone(),
290 attempt: 1,
291 step_elapsed: Duration::ZERO,
293 attempt_elapsed: Duration::ZERO,
294 },
295 })
296 }
297 StepEventKind::Nested {
298 step,
299 attempt,
300 step_elapsed,
301 attempt_elapsed,
302 event,
303 ..
304 } => event.progress_event().map(|progress_event| ProgressEvent {
305 spec: self.spec.clone(),
306 execution_id: self.execution_id,
307 total_elapsed: self.total_elapsed,
308 kind: ProgressEventKind::Nested {
309 step: step.clone(),
310 attempt: *attempt,
311 event: Box::new(progress_event),
312 step_elapsed: *step_elapsed,
313 attempt_elapsed: *attempt_elapsed,
314 },
315 }),
316 StepEventKind::NoStepsDefined
317 | StepEventKind::ExecutionCompleted { .. }
318 | StepEventKind::ExecutionFailed { .. }
319 | StepEventKind::ExecutionAborted { .. }
320 | StepEventKind::Unknown => None,
321 }
322 }
323
324 pub fn leaf_execution_id(&self) -> ExecutionUuid {
327 match &self.kind {
328 StepEventKind::Nested { event, .. } => event.leaf_execution_id(),
329 _ => self.execution_id,
330 }
331 }
332
333 pub fn leaf_event_index(&self) -> usize {
336 match &self.kind {
337 StepEventKind::Nested { event, .. } => event.leaf_event_index(),
338 _ => self.event_index,
339 }
340 }
341
342 pub fn from_generic(
347 value: StepEvent<GenericSpec>,
348 ) -> Result<Self, ConvertGenericError> {
349 Ok(StepEvent {
350 spec: value.spec,
351 execution_id: value.execution_id,
352 event_index: value.event_index,
353 total_elapsed: value.total_elapsed,
354 kind: StepEventKind::from_generic(value.kind)
355 .map_err(|error| error.parent("kind"))?,
356 })
357 }
358
359 pub fn into_generic(self) -> StepEvent<GenericSpec> {
370 StepEvent {
371 spec: self.spec,
372 execution_id: self.execution_id,
373 event_index: self.event_index,
374 total_elapsed: self.total_elapsed,
375 kind: self.kind.into_generic(),
376 }
377 }
378}
379
380#[derive(Deserialize, Serialize)]
381#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
382#[derive_where(Clone, Debug, Eq, PartialEq)]
383#[serde(bound = "", rename_all = "snake_case", tag = "kind")]
384#[cfg_attr(
385 feature = "schemars08",
386 schemars(
387 rename = "StepEventKindFor{S}",
388 bound = "S: JsonSchemaEngineSpec",
389 )
390)]
391pub enum StepEventKind<S: EngineSpec> {
392 NoStepsDefined,
397
398 ExecutionStarted {
403 steps: Vec<StepInfo<S>>,
405
406 components: Vec<StepComponentSummary<S>>,
408
409 first_step: StepInfoWithMetadata<S>,
411 },
412
413 ProgressReset {
416 step: StepInfoWithMetadata<S>,
418
419 attempt: usize,
421
422 metadata: S::ProgressMetadata,
424
425 step_elapsed: Duration,
428
429 attempt_elapsed: Duration,
431
432 message: Cow<'static, str>,
434 },
435
436 AttemptRetry {
438 step: StepInfoWithMetadata<S>,
440
441 next_attempt: usize,
443
444 step_elapsed: Duration,
447
448 attempt_elapsed: Duration,
450
451 message: Cow<'static, str>,
453 },
454
455 StepCompleted {
457 step: StepInfoWithMetadata<S>,
459
460 attempt: usize,
462
463 outcome: StepOutcome<S>,
465
466 next_step: StepInfoWithMetadata<S>,
468
469 step_elapsed: Duration,
472
473 attempt_elapsed: Duration,
475 },
476
477 ExecutionCompleted {
482 last_step: StepInfoWithMetadata<S>,
484
485 last_attempt: usize,
487
488 last_outcome: StepOutcome<S>,
490
491 step_elapsed: Duration,
494
495 attempt_elapsed: Duration,
497 },
498
499 ExecutionFailed {
504 failed_step: StepInfoWithMetadata<S>,
506
507 total_attempts: usize,
509
510 step_elapsed: Duration,
513
514 attempt_elapsed: Duration,
516
517 message: String,
519
520 causes: Vec<String>,
522 },
523
524 ExecutionAborted {
529 aborted_step: StepInfoWithMetadata<S>,
532
533 attempt: usize,
535
536 step_elapsed: Duration,
539
540 attempt_elapsed: Duration,
542
543 message: String,
545 },
546
547 Nested {
549 step: StepInfoWithMetadata<S>,
551
552 attempt: usize,
554
555 event: Box<StepEvent<GenericSpec>>,
557
558 step_elapsed: Duration,
561
562 attempt_elapsed: Duration,
564 },
565
566 #[serde(other, deserialize_with = "deserialize_ignore_any")]
568 Unknown,
569}
570
571fn deserialize_ignore_any<'de, D: Deserializer<'de>, T: Default>(
572 deserializer: D,
573) -> Result<T, D::Error> {
574 IgnoredAny::deserialize(deserializer).map(|_| T::default())
575}
576
577impl<S: EngineSpec> StepEventKind<S> {
578 pub fn is_terminal(&self) -> StepEventIsTerminal {
585 match self {
586 StepEventKind::NoStepsDefined
587 | StepEventKind::ExecutionCompleted { .. } => {
588 StepEventIsTerminal::Terminal { success: true }
589 }
590 StepEventKind::ExecutionFailed { .. }
591 | StepEventKind::ExecutionAborted { .. } => {
592 StepEventIsTerminal::Terminal { success: false }
593 }
594 StepEventKind::ExecutionStarted { .. }
595 | StepEventKind::ProgressReset { .. }
596 | StepEventKind::AttemptRetry { .. }
597 | StepEventKind::StepCompleted { .. }
598 | StepEventKind::Nested { .. }
599 | StepEventKind::Unknown => StepEventIsTerminal::NonTerminal,
600 }
601 }
602
603 pub fn priority(&self) -> StepEventPriority {
607 match self {
608 StepEventKind::NoStepsDefined
609 | StepEventKind::ExecutionStarted { .. }
610 | StepEventKind::StepCompleted { .. }
611 | StepEventKind::ExecutionCompleted { .. }
612 | StepEventKind::ExecutionFailed { .. }
613 | StepEventKind::ExecutionAborted { .. } => StepEventPriority::High,
614 StepEventKind::ProgressReset { .. }
615 | StepEventKind::AttemptRetry { .. }
616 | StepEventKind::Unknown => StepEventPriority::Low,
617 StepEventKind::Nested { event, .. } => event.kind.priority(),
618 }
619 }
620
621 pub fn from_generic(
626 value: StepEventKind<GenericSpec>,
627 ) -> Result<Self, ConvertGenericError> {
628 let ret = match value {
629 StepEventKind::NoStepsDefined => StepEventKind::NoStepsDefined,
630 StepEventKind::ExecutionStarted {
631 steps,
632 components,
633 first_step,
634 } => StepEventKind::ExecutionStarted {
635 steps: steps
636 .into_iter()
637 .enumerate()
638 .map(|(index, step)| {
639 StepInfo::from_generic(step)
640 .map_err(|error| error.parent_array("steps", index))
641 })
642 .collect::<Result<Vec<_>, _>>()?,
643 components: components
644 .into_iter()
645 .enumerate()
646 .map(|(index, component)| {
647 StepComponentSummary::from_generic(component).map_err(
648 |error| error.parent_array("components", index),
649 )
650 })
651 .collect::<Result<Vec<_>, _>>()?,
652 first_step: StepInfoWithMetadata::from_generic(first_step)
653 .map_err(|error| error.parent("first_step"))?,
654 },
655 StepEventKind::ProgressReset {
656 step,
657 attempt,
658 metadata,
659 step_elapsed,
660 attempt_elapsed,
661 message,
662 } => StepEventKind::ProgressReset {
663 step: StepInfoWithMetadata::from_generic(step)
664 .map_err(|error| error.parent("step"))?,
665 attempt,
666 metadata: serde_json::from_value(metadata).map_err(
667 |error| ConvertGenericError::new("metadata", error),
668 )?,
669 step_elapsed,
670 attempt_elapsed,
671 message,
672 },
673 StepEventKind::AttemptRetry {
674 step,
675 next_attempt,
676 step_elapsed,
677 attempt_elapsed,
678 message,
679 } => StepEventKind::AttemptRetry {
680 step: StepInfoWithMetadata::from_generic(step)
681 .map_err(|error| error.parent("step"))?,
682 next_attempt,
683 step_elapsed,
684 attempt_elapsed,
685 message,
686 },
687 StepEventKind::StepCompleted {
688 step,
689 attempt,
690 outcome,
691 next_step,
692 step_elapsed,
693 attempt_elapsed,
694 } => StepEventKind::StepCompleted {
695 step: StepInfoWithMetadata::from_generic(step)
696 .map_err(|error| error.parent("step"))?,
697 attempt,
698 outcome: StepOutcome::from_generic(outcome)
699 .map_err(|error| error.parent("outcome"))?,
700 next_step: StepInfoWithMetadata::from_generic(next_step)
701 .map_err(|error| error.parent("next_step"))?,
702 step_elapsed,
703 attempt_elapsed,
704 },
705 StepEventKind::ExecutionCompleted {
706 last_step,
707 last_attempt,
708 last_outcome,
709 step_elapsed,
710 attempt_elapsed,
711 } => StepEventKind::ExecutionCompleted {
712 last_step: StepInfoWithMetadata::from_generic(last_step)
713 .map_err(|error| error.parent("last_step"))?,
714 last_attempt,
715 last_outcome: StepOutcome::from_generic(last_outcome)
716 .map_err(|error| error.parent("last_outcome"))?,
717 step_elapsed,
718 attempt_elapsed,
719 },
720 StepEventKind::ExecutionFailed {
721 failed_step,
722 total_attempts,
723 step_elapsed,
724 attempt_elapsed,
725 message,
726 causes,
727 } => StepEventKind::ExecutionFailed {
728 failed_step: StepInfoWithMetadata::from_generic(failed_step)
729 .map_err(|error| error.parent("failed_step"))?,
730 total_attempts,
731 step_elapsed,
732 attempt_elapsed,
733 message,
734 causes,
735 },
736 StepEventKind::ExecutionAborted {
737 aborted_step,
738 attempt,
739 step_elapsed,
740 attempt_elapsed,
741 message,
742 } => StepEventKind::ExecutionAborted {
743 aborted_step: StepInfoWithMetadata::from_generic(aborted_step)
744 .map_err(|error| error.parent("aborted_step"))?,
745 attempt,
746 step_elapsed,
747 attempt_elapsed,
748 message,
749 },
750 StepEventKind::Nested {
751 step,
752 attempt,
753 event,
754 step_elapsed,
755 attempt_elapsed,
756 } => StepEventKind::Nested {
757 step: StepInfoWithMetadata::from_generic(step)
758 .map_err(|error| error.parent("step"))?,
759 attempt,
760 event,
761 step_elapsed,
762 attempt_elapsed,
763 },
764 StepEventKind::Unknown => StepEventKind::Unknown,
765 };
766 Ok(ret)
767 }
768
769 pub fn into_generic(self) -> StepEventKind<GenericSpec> {
780 match self {
781 StepEventKind::NoStepsDefined => StepEventKind::NoStepsDefined,
782 StepEventKind::ExecutionStarted {
783 steps,
784 components,
785 first_step,
786 } => StepEventKind::ExecutionStarted {
787 steps: steps
788 .into_iter()
789 .map(|step| step.into_generic())
790 .collect(),
791 components: components
792 .into_iter()
793 .map(|component| component.into_generic())
794 .collect(),
795 first_step: first_step.into_generic(),
796 },
797 StepEventKind::ProgressReset {
798 step,
799 attempt,
800 metadata,
801 step_elapsed,
802 attempt_elapsed,
803 message,
804 } => StepEventKind::ProgressReset {
805 step: step.into_generic(),
806 attempt,
807 metadata: serde_json::to_value(metadata)
808 .unwrap_or(serde_json::Value::Null),
809 step_elapsed,
810 attempt_elapsed,
811 message,
812 },
813 StepEventKind::AttemptRetry {
814 step,
815 next_attempt,
816 step_elapsed,
817 attempt_elapsed,
818 message,
819 } => StepEventKind::AttemptRetry {
820 step: step.into_generic(),
821 next_attempt,
822 step_elapsed,
823 attempt_elapsed,
824 message,
825 },
826 StepEventKind::StepCompleted {
827 step,
828 attempt,
829 outcome,
830 next_step,
831 step_elapsed,
832 attempt_elapsed,
833 } => StepEventKind::StepCompleted {
834 step: step.into_generic(),
835 attempt,
836 outcome: outcome.into_generic(),
837 next_step: next_step.into_generic(),
838 step_elapsed,
839 attempt_elapsed,
840 },
841 StepEventKind::ExecutionCompleted {
842 last_step,
843 last_attempt,
844 last_outcome,
845 step_elapsed,
846 attempt_elapsed,
847 } => StepEventKind::ExecutionCompleted {
848 last_step: last_step.into_generic(),
849 last_attempt,
850 last_outcome: last_outcome.into_generic(),
851 step_elapsed,
852 attempt_elapsed,
853 },
854 StepEventKind::ExecutionFailed {
855 failed_step,
856 total_attempts,
857 step_elapsed,
858 attempt_elapsed,
859 message,
860 causes,
861 } => StepEventKind::ExecutionFailed {
862 failed_step: failed_step.into_generic(),
863 total_attempts,
864 step_elapsed,
865 attempt_elapsed,
866 message,
867 causes,
868 },
869 StepEventKind::ExecutionAborted {
870 aborted_step,
871 attempt,
872 step_elapsed,
873 attempt_elapsed,
874 message,
875 } => StepEventKind::ExecutionAborted {
876 aborted_step: aborted_step.into_generic(),
877 attempt,
878 step_elapsed,
879 attempt_elapsed,
880 message,
881 },
882 StepEventKind::Nested {
883 step,
884 attempt,
885 event,
886 step_elapsed,
887 attempt_elapsed,
888 } => StepEventKind::Nested {
889 step: step.into_generic(),
890 attempt,
891 event,
892 step_elapsed,
893 attempt_elapsed,
894 },
895 StepEventKind::Unknown => StepEventKind::Unknown,
896 }
897 }
898
899 pub fn step_outcome(&self) -> Option<&StepOutcome<S>> {
903 match self {
904 StepEventKind::StepCompleted { outcome, .. }
905 | StepEventKind::ExecutionCompleted {
906 last_outcome: outcome, ..
907 } => Some(outcome),
908 StepEventKind::NoStepsDefined
909 | StepEventKind::ExecutionStarted { .. }
910 | StepEventKind::ProgressReset { .. }
911 | StepEventKind::AttemptRetry { .. }
912 | StepEventKind::ExecutionFailed { .. }
913 | StepEventKind::ExecutionAborted { .. }
914 | StepEventKind::Nested { .. }
915 | StepEventKind::Unknown => None,
916 }
917 }
918}
919
920#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
927pub enum StepEventIsTerminal {
928 NonTerminal,
930
931 Terminal {
933 success: bool,
935 },
936}
937
938#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
951pub enum StepEventPriority {
952 Low,
956
957 High,
961}
962
963#[derive(Deserialize, Serialize)]
964#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
965#[derive_where(Clone, Debug, Eq, PartialEq)]
966#[serde(bound = "", rename_all = "snake_case", tag = "kind")]
967#[cfg_attr(
968 feature = "schemars08",
969 schemars(rename = "StepOutcomeFor{S}", bound = "S: JsonSchemaEngineSpec",)
970)]
971pub enum StepOutcome<S: EngineSpec> {
972 Success {
974 message: Option<Cow<'static, str>>,
976
977 metadata: Option<S::CompletionMetadata>,
979 },
980
981 Warning {
983 message: Cow<'static, str>,
985
986 metadata: Option<S::CompletionMetadata>,
988 },
989
990 Skipped {
992 message: Cow<'static, str>,
994
995 metadata: Option<S::SkippedMetadata>,
997 },
998}
999
1000impl<S: EngineSpec> StepOutcome<S> {
1001 pub fn from_generic(
1006 value: StepOutcome<GenericSpec>,
1007 ) -> Result<Self, ConvertGenericError> {
1008 let ret = match value {
1009 StepOutcome::Success { message, metadata } => {
1010 StepOutcome::Success {
1011 message,
1012 metadata: metadata
1013 .map(|metadata| {
1014 serde_json::from_value(metadata).map_err(|error| {
1015 ConvertGenericError::new("metadata", error)
1016 })
1017 })
1018 .transpose()?,
1019 }
1020 }
1021 StepOutcome::Warning { message, metadata } => {
1022 StepOutcome::Warning {
1023 message,
1024 metadata: metadata
1025 .map(|metadata| {
1026 serde_json::from_value(metadata).map_err(|error| {
1027 ConvertGenericError::new("metadata", error)
1028 })
1029 })
1030 .transpose()?,
1031 }
1032 }
1033 StepOutcome::Skipped { message, metadata } => {
1034 StepOutcome::Skipped {
1035 message,
1036 metadata: metadata
1037 .map(|metadata| {
1038 serde_json::from_value(metadata).map_err(|error| {
1039 ConvertGenericError::new("metadata", error)
1040 })
1041 })
1042 .transpose()?,
1043 }
1044 }
1045 };
1046 Ok(ret)
1047 }
1048
1049 pub fn completion_metadata(&self) -> Option<&S::CompletionMetadata> {
1054 match self {
1055 StepOutcome::Success { metadata, .. }
1056 | StepOutcome::Warning { metadata, .. } => metadata.as_ref(),
1057 StepOutcome::Skipped { .. } => None,
1058 }
1059 }
1060
1061 pub fn into_generic(self) -> StepOutcome<GenericSpec> {
1072 match self {
1073 StepOutcome::Success { message, metadata } => {
1074 StepOutcome::Success {
1075 message,
1076 metadata: metadata.map(|metadata| {
1077 serde_json::to_value(metadata)
1078 .unwrap_or(serde_json::Value::Null)
1079 }),
1080 }
1081 }
1082 StepOutcome::Warning { metadata, message } => {
1083 StepOutcome::Warning {
1084 message,
1085 metadata: metadata.map(|metadata| {
1086 serde_json::to_value(metadata)
1087 .unwrap_or(serde_json::Value::Null)
1088 }),
1089 }
1090 }
1091 StepOutcome::Skipped { metadata, message } => {
1092 StepOutcome::Skipped {
1093 message,
1094 metadata: metadata.map(|metadata| {
1095 serde_json::to_value(metadata)
1096 .unwrap_or(serde_json::Value::Null)
1097 }),
1098 }
1099 }
1100 }
1101 }
1102
1103 pub fn is_success_or_warning(&self) -> bool {
1106 match self {
1107 Self::Success { .. } | Self::Warning { .. } => true,
1108 Self::Skipped { .. } => false,
1109 }
1110 }
1111
1112 pub fn is_skipped(&self) -> bool {
1114 match self {
1115 Self::Skipped { .. } => true,
1116 Self::Success { .. } | Self::Warning { .. } => false,
1117 }
1118 }
1119
1120 pub fn message(&self) -> Option<&Cow<'static, str>> {
1122 match self {
1123 StepOutcome::Success { message, .. } => message.as_ref(),
1124 StepOutcome::Warning { message, .. }
1125 | StepOutcome::Skipped { message, .. } => Some(message),
1126 }
1127 }
1128}
1129
1130#[derive(Deserialize, Serialize)]
1131#[derive_where(Clone, Debug, Eq, PartialEq)]
1132#[serde(bound = "", rename_all = "snake_case")]
1133pub struct ProgressEvent<S: EngineSpec> {
1134 pub spec: String,
1142
1143 pub execution_id: ExecutionUuid,
1145
1146 pub total_elapsed: Duration,
1148
1149 #[serde(rename = "data")]
1151 pub kind: ProgressEventKind<S>,
1152}
1153
1154#[cfg(feature = "schemars08")]
1155impl<S: JsonSchemaEngineSpec> schemars::JsonSchema for ProgressEvent<S> {
1156 fn schema_name() -> String {
1157 format!("ProgressEventFor{}", S::schema_name())
1158 }
1159
1160 fn json_schema(
1161 generator: &mut schemars::r#gen::SchemaGenerator,
1162 ) -> schemars::schema::Schema {
1163 use crate::schema::with_description;
1164 use schemars::schema::{ObjectValidation, SchemaObject};
1165
1166 let mut obj = ObjectValidation::default();
1167 obj.properties.insert(
1168 "spec".to_owned(),
1169 with_description(
1170 generator.subschema_for::<String>(),
1171 "The specification that this event belongs to.",
1172 ),
1173 );
1174 obj.properties.insert(
1175 "execution_id".to_owned(),
1176 with_description(
1177 generator.subschema_for::<ExecutionUuid>(),
1178 "The execution ID.",
1179 ),
1180 );
1181 obj.properties.insert(
1182 "total_elapsed".to_owned(),
1183 with_description(
1184 generator.subschema_for::<Duration>(),
1185 "Total time elapsed since the start of execution.",
1186 ),
1187 );
1188 obj.properties.insert(
1189 "data".to_owned(),
1190 with_description(
1191 generator.subschema_for::<ProgressEventKind<S>>(),
1192 "The kind of event this is.",
1193 ),
1194 );
1195 obj.required = ["spec", "execution_id", "total_elapsed", "data"]
1196 .into_iter()
1197 .map(String::from)
1198 .collect();
1199
1200 let mut extensions = serde_json::Map::new();
1201 if let Some(info) = S::rust_type_info() {
1202 extensions.insert(
1203 "x-rust-type".to_owned(),
1204 crate::schema::rust_type_for_generic(&info, "ProgressEvent"),
1205 );
1206 }
1207
1208 SchemaObject {
1209 instance_type: Some(schemars::schema::InstanceType::Object.into()),
1210 object: Some(Box::new(obj)),
1211 extensions: extensions.into_iter().collect(),
1212 ..Default::default()
1213 }
1214 .into()
1215 }
1216}
1217
1218impl<S: EngineSpec> ProgressEvent<S> {
1219 pub fn from_generic(
1224 value: ProgressEvent<GenericSpec>,
1225 ) -> Result<Self, ConvertGenericError> {
1226 Ok(Self {
1227 spec: value.spec,
1228 execution_id: value.execution_id,
1229 total_elapsed: value.total_elapsed,
1230 kind: ProgressEventKind::from_generic(value.kind)
1231 .map_err(|error| error.parent("kind"))?,
1232 })
1233 }
1234
1235 pub fn into_generic(self) -> ProgressEvent<GenericSpec> {
1246 ProgressEvent {
1247 spec: self.spec,
1248 execution_id: self.execution_id,
1249 total_elapsed: self.total_elapsed,
1250 kind: self.kind.into_generic(),
1251 }
1252 }
1253}
1254
1255#[derive(Deserialize, Serialize)]
1256#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
1257#[derive_where(Clone, Debug, Eq, PartialEq)]
1258#[serde(bound = "", rename_all = "snake_case", tag = "kind")]
1259#[cfg_attr(
1260 feature = "schemars08",
1261 schemars(
1262 rename = "ProgressEventKindFor{S}",
1263 bound = "S: JsonSchemaEngineSpec",
1264 )
1265)]
1266pub enum ProgressEventKind<S: EngineSpec> {
1267 WaitingForProgress {
1272 step: StepInfoWithMetadata<S>,
1274
1275 attempt: usize,
1277
1278 step_elapsed: Duration,
1281
1282 attempt_elapsed: Duration,
1284 },
1285
1286 Progress {
1287 step: StepInfoWithMetadata<S>,
1289
1290 attempt: usize,
1292
1293 metadata: S::ProgressMetadata,
1295
1296 progress: Option<ProgressCounter>,
1298
1299 step_elapsed: Duration,
1302
1303 attempt_elapsed: Duration,
1305 },
1306
1307 Nested {
1308 step: StepInfoWithMetadata<S>,
1310
1311 attempt: usize,
1313
1314 event: Box<ProgressEvent<GenericSpec>>,
1316
1317 step_elapsed: Duration,
1320
1321 attempt_elapsed: Duration,
1323 },
1324
1325 #[serde(other, deserialize_with = "deserialize_ignore_any")]
1327 Unknown,
1328}
1329
1330#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1334pub enum LeafProgress<'a> {
1335 WaitingForProgress,
1336 Progress(Option<&'a ProgressCounter>),
1337 Unknown,
1338}
1339
1340impl<S: EngineSpec> ProgressEventKind<S> {
1341 pub fn progress_counter(&self) -> Option<&ProgressCounter> {
1343 match self.leaf_progress() {
1344 LeafProgress::Progress(counter) => counter,
1345 LeafProgress::WaitingForProgress | LeafProgress::Unknown => None,
1346 }
1347 }
1348
1349 pub fn leaf_progress(&self) -> LeafProgress<'_> {
1354 match self {
1355 ProgressEventKind::WaitingForProgress { .. } => {
1356 LeafProgress::WaitingForProgress
1357 }
1358 ProgressEventKind::Progress { progress, .. } => {
1359 LeafProgress::Progress(progress.as_ref())
1360 }
1361 ProgressEventKind::Nested { event, .. } => {
1362 event.kind.leaf_progress()
1363 }
1364 ProgressEventKind::Unknown => LeafProgress::Unknown,
1365 }
1366 }
1367
1368 pub fn leaf_attempt(&self) -> Option<usize> {
1373 match self {
1374 ProgressEventKind::WaitingForProgress { attempt, .. }
1375 | ProgressEventKind::Progress { attempt, .. } => Some(*attempt),
1376 ProgressEventKind::Nested { event, .. } => {
1377 event.kind.leaf_attempt()
1378 }
1379 ProgressEventKind::Unknown => None,
1380 }
1381 }
1382
1383 pub fn leaf_step_elapsed(&self) -> Option<Duration> {
1388 match self {
1389 ProgressEventKind::WaitingForProgress { step_elapsed, .. }
1390 | ProgressEventKind::Progress { step_elapsed, .. } => {
1391 Some(*step_elapsed)
1392 }
1393 ProgressEventKind::Nested { event, .. } => {
1394 event.kind.leaf_step_elapsed()
1395 }
1396 ProgressEventKind::Unknown => None,
1397 }
1398 }
1399
1400 pub fn leaf_attempt_elapsed(&self) -> Option<Duration> {
1405 match self {
1406 ProgressEventKind::WaitingForProgress {
1407 attempt_elapsed, ..
1408 }
1409 | ProgressEventKind::Progress { attempt_elapsed, .. } => {
1410 Some(*attempt_elapsed)
1411 }
1412 ProgressEventKind::Nested { event, .. } => {
1413 event.kind.leaf_attempt_elapsed()
1414 }
1415 ProgressEventKind::Unknown => None,
1416 }
1417 }
1418
1419 pub fn from_generic(
1424 value: ProgressEventKind<GenericSpec>,
1425 ) -> Result<Self, ConvertGenericError> {
1426 let ret = match value {
1427 ProgressEventKind::WaitingForProgress {
1428 step,
1429 attempt,
1430 step_elapsed,
1431 attempt_elapsed,
1432 } => ProgressEventKind::WaitingForProgress {
1433 step: StepInfoWithMetadata::from_generic(step)
1434 .map_err(|error| error.parent("step"))?,
1435 attempt,
1436 step_elapsed,
1437 attempt_elapsed,
1438 },
1439 ProgressEventKind::Progress {
1440 step,
1441 attempt,
1442 metadata,
1443 progress,
1444 step_elapsed,
1445 attempt_elapsed,
1446 } => ProgressEventKind::Progress {
1447 step: StepInfoWithMetadata::from_generic(step)
1448 .map_err(|error| error.parent("step"))?,
1449 attempt,
1450 metadata: serde_json::from_value(metadata).map_err(
1451 |error| ConvertGenericError::new("metadata", error),
1452 )?,
1453 progress,
1454 step_elapsed,
1455 attempt_elapsed,
1456 },
1457 ProgressEventKind::Nested {
1458 step,
1459 attempt,
1460 event,
1461 step_elapsed,
1462 attempt_elapsed,
1463 } => ProgressEventKind::Nested {
1464 step: StepInfoWithMetadata::from_generic(step)
1465 .map_err(|error| error.parent("step"))?,
1466 attempt,
1467 event,
1468 step_elapsed,
1469 attempt_elapsed,
1470 },
1471 ProgressEventKind::Unknown => ProgressEventKind::Unknown,
1472 };
1473 Ok(ret)
1474 }
1475
1476 pub fn into_generic(self) -> ProgressEventKind<GenericSpec> {
1487 match self {
1488 ProgressEventKind::WaitingForProgress {
1489 step,
1490 attempt,
1491 step_elapsed,
1492 attempt_elapsed,
1493 } => ProgressEventKind::WaitingForProgress {
1494 step: step.into_generic(),
1495 attempt,
1496 step_elapsed,
1497 attempt_elapsed,
1498 },
1499 ProgressEventKind::Progress {
1500 step,
1501 attempt,
1502 metadata,
1503 progress,
1504 step_elapsed,
1505 attempt_elapsed,
1506 } => ProgressEventKind::Progress {
1507 step: step.into_generic(),
1508 attempt,
1509 metadata: serde_json::to_value(metadata)
1510 .unwrap_or(serde_json::Value::Null),
1511 progress,
1512 step_elapsed,
1513 attempt_elapsed,
1514 },
1515 ProgressEventKind::Nested {
1516 step,
1517 attempt,
1518 event,
1519 step_elapsed,
1520 attempt_elapsed,
1521 } => ProgressEventKind::Nested {
1522 step: step.into_generic(),
1523 attempt,
1524 event,
1525 step_elapsed,
1526 attempt_elapsed,
1527 },
1528 ProgressEventKind::Unknown => ProgressEventKind::Unknown,
1529 }
1530 }
1531}
1532
1533#[derive(Deserialize, Serialize)]
1535#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
1536#[derive_where(Clone, Debug, Eq, PartialEq)]
1537#[serde(bound = "", rename_all = "snake_case")]
1538#[cfg_attr(
1539 feature = "schemars08",
1540 schemars(rename = "StepInfoFor{S}", bound = "S: JsonSchemaEngineSpec",)
1541)]
1542pub struct StepInfo<S: EngineSpec> {
1543 pub id: S::StepId,
1545
1546 pub component: S::Component,
1548
1549 pub description: Cow<'static, str>,
1551
1552 pub index: usize,
1554
1555 pub component_index: usize,
1557
1558 pub total_component_steps: usize,
1560}
1561
1562impl<S: EngineSpec> StepInfo<S> {
1563 pub fn is_last_step_in_component(&self) -> bool {
1565 self.component_index + 1 == self.total_component_steps
1566 }
1567
1568 pub fn from_generic(
1573 value: StepInfo<GenericSpec>,
1574 ) -> Result<Self, ConvertGenericError> {
1575 Ok(Self {
1576 id: serde_json::from_value(value.id)
1577 .map_err(|error| ConvertGenericError::new("id", error))?,
1578 component: serde_json::from_value(value.component).map_err(
1579 |error| ConvertGenericError::new("component", error),
1580 )?,
1581 description: value.description,
1582 index: value.index,
1583 component_index: value.component_index,
1584 total_component_steps: value.total_component_steps,
1585 })
1586 }
1587
1588 pub fn into_generic(self) -> StepInfo<GenericSpec> {
1599 StepInfo {
1600 id: serde_json::to_value(self.id)
1601 .unwrap_or(serde_json::Value::Null),
1602 component: serde_json::to_value(self.component)
1603 .unwrap_or(serde_json::Value::Null),
1604 description: self.description,
1605 index: self.index,
1606 component_index: self.component_index,
1607 total_component_steps: self.total_component_steps,
1608 }
1609 }
1610}
1611
1612#[derive(Deserialize, Serialize)]
1613#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
1614#[derive_where(Clone, Debug, Eq, PartialEq)]
1615#[serde(bound = "", rename_all = "snake_case")]
1616#[cfg_attr(
1617 feature = "schemars08",
1618 schemars(
1619 rename = "StepComponentSummaryFor{S}",
1620 bound = "S: JsonSchemaEngineSpec",
1621 )
1622)]
1623pub struct StepComponentSummary<S: EngineSpec> {
1624 pub component: S::Component,
1626
1627 pub total_component_steps: usize,
1629}
1630
1631impl<S: EngineSpec> StepComponentSummary<S> {
1632 pub fn from_generic(
1637 value: StepComponentSummary<GenericSpec>,
1638 ) -> Result<Self, ConvertGenericError> {
1639 Ok(Self {
1640 component: serde_json::from_value(value.component).map_err(
1641 |error| ConvertGenericError::new("component", error),
1642 )?,
1643 total_component_steps: value.total_component_steps,
1644 })
1645 }
1646
1647 pub fn into_generic(self) -> StepComponentSummary<GenericSpec> {
1658 StepComponentSummary {
1659 component: serde_json::to_value(self.component)
1660 .unwrap_or(serde_json::Value::Null),
1661 total_component_steps: self.total_component_steps,
1662 }
1663 }
1664}
1665
1666#[derive(Deserialize, Serialize)]
1668#[cfg_attr(feature = "schemars08", derive(schemars::JsonSchema))]
1669#[derive_where(Clone, Debug, Eq, PartialEq)]
1670#[serde(bound = "", rename_all = "snake_case")]
1671#[cfg_attr(
1672 feature = "schemars08",
1673 schemars(
1674 rename = "StepInfoWithMetadataFor{S}",
1675 bound = "S: JsonSchemaEngineSpec",
1676 )
1677)]
1678pub struct StepInfoWithMetadata<S: EngineSpec> {
1679 pub info: StepInfo<S>,
1681
1682 pub metadata: Option<S::StepMetadata>,
1684}
1685
1686impl<S: EngineSpec> StepInfoWithMetadata<S> {
1687 pub fn from_generic(
1692 value: StepInfoWithMetadata<GenericSpec>,
1693 ) -> Result<Self, ConvertGenericError> {
1694 Ok(Self {
1695 info: StepInfo::from_generic(value.info)
1696 .map_err(|error| error.parent("info"))?,
1697 metadata: value
1698 .metadata
1699 .map(|metadata| {
1700 serde_json::from_value(metadata).map_err(|error| {
1701 ConvertGenericError::new("metadata", error)
1702 })
1703 })
1704 .transpose()?,
1705 })
1706 }
1707
1708 pub fn into_generic(self) -> StepInfoWithMetadata<GenericSpec> {
1719 StepInfoWithMetadata {
1720 info: self.info.into_generic(),
1721 metadata: self.metadata.map(|metadata| {
1722 serde_json::to_value(metadata)
1723 .unwrap_or(serde_json::Value::Null)
1724 }),
1725 }
1726 }
1727}
1728
1729#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
1736#[serde(rename_all = "snake_case")]
1737pub struct ProgressCounter {
1738 pub current: u64,
1740
1741 pub total: Option<u64>,
1743
1744 pub units: ProgressUnits,
1746}
1747
1748#[cfg(feature = "schemars08")]
1749impl schemars::JsonSchema for ProgressCounter {
1750 fn schema_name() -> String {
1751 "ProgressCounter".to_owned()
1752 }
1753
1754 fn json_schema(
1755 generator: &mut schemars::r#gen::SchemaGenerator,
1756 ) -> schemars::schema::Schema {
1757 use crate::schema::{
1758 EVENTS_MODULE, rust_type_for_events, with_description,
1759 };
1760 use schemars::schema::{ObjectValidation, SchemaObject};
1761
1762 let mut obj = ObjectValidation::default();
1763 obj.properties.insert(
1764 "current".to_owned(),
1765 with_description(
1766 generator.subschema_for::<u64>(),
1767 "The current progress.",
1768 ),
1769 );
1770 obj.properties.insert(
1771 "total".to_owned(),
1772 with_description(
1773 generator.subschema_for::<Option<u64>>(),
1774 "The total progress.",
1775 ),
1776 );
1777 obj.properties.insert(
1778 "units".to_owned(),
1779 with_description(
1780 generator.subschema_for::<ProgressUnits>(),
1781 "Progress units.",
1782 ),
1783 );
1784 obj.required =
1785 ["current", "units"].into_iter().map(String::from).collect();
1786
1787 SchemaObject {
1788 metadata: Some(Box::new(schemars::schema::Metadata {
1789 description: Some("Current progress.".to_owned()),
1790 ..Default::default()
1791 })),
1792 instance_type: Some(schemars::schema::InstanceType::Object.into()),
1793 object: Some(Box::new(obj)),
1794 extensions: [(
1795 "x-rust-type".to_owned(),
1796 rust_type_for_events(&format!(
1797 "{EVENTS_MODULE}::ProgressCounter"
1798 )),
1799 )]
1800 .into_iter()
1801 .collect(),
1802 ..Default::default()
1803 }
1804 .into()
1805 }
1806}
1807
1808impl ProgressCounter {
1809 #[inline]
1811 pub fn new(
1812 current: u64,
1813 total: u64,
1814 units: impl Into<ProgressUnits>,
1815 ) -> Self {
1816 Self { current, total: Some(total), units: units.into() }
1817 }
1818
1819 #[inline]
1821 pub fn current(current: u64, units: impl Into<ProgressUnits>) -> Self {
1822 Self { current, total: None, units: units.into() }
1823 }
1824}
1825
1826#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
1827#[serde(transparent)]
1828pub struct ProgressUnits(pub Cow<'static, str>);
1829
1830#[cfg(feature = "schemars08")]
1831impl schemars::JsonSchema for ProgressUnits {
1832 fn schema_name() -> String {
1833 "ProgressUnits".to_owned()
1834 }
1835
1836 fn json_schema(
1837 generator: &mut schemars::r#gen::SchemaGenerator,
1838 ) -> schemars::schema::Schema {
1839 use crate::schema::{EVENTS_MODULE, rust_type_for_events};
1840
1841 let mut schema = match generator.subschema_for::<String>() {
1844 schemars::schema::Schema::Object(obj) => obj,
1845 other => {
1849 debug_assert!(
1850 false,
1851 "expected String to produce an Object \
1852 schema, got: {other:?}",
1853 );
1854 return other;
1855 }
1856 };
1857 schema.extensions.insert(
1858 "x-rust-type".to_owned(),
1859 rust_type_for_events(&format!("{EVENTS_MODULE}::ProgressUnits")),
1860 );
1861 schema.into()
1862 }
1863}
1864
1865impl ProgressUnits {
1866 pub fn new(s: impl Into<Cow<'static, str>>) -> Self {
1868 Self(s.into())
1869 }
1870
1871 pub const fn new_const(s: &'static str) -> Self {
1873 Self(Cow::Borrowed(s))
1874 }
1875
1876 pub const BYTES: Self = Self::new_const("bytes");
1880}
1881
1882impl AsRef<str> for ProgressUnits {
1883 fn as_ref(&self) -> &str {
1884 self.0.as_ref()
1885 }
1886}
1887
1888impl fmt::Display for ProgressUnits {
1889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1890 f.write_str(self.as_ref())
1891 }
1892}
1893
1894impl<T> From<T> for ProgressUnits
1895where
1896 T: Into<Cow<'static, str>>,
1897{
1898 fn from(value: T) -> Self {
1899 Self(value.into())
1900 }
1901}
1902
1903#[derive_where(Clone, Debug, Eq, PartialEq)]
1904pub enum StepProgress<S: EngineSpec> {
1905 Progress {
1907 progress: Option<ProgressCounter>,
1909
1910 metadata: S::ProgressMetadata,
1912 },
1913
1914 Reset {
1920 metadata: S::ProgressMetadata,
1922
1923 message: Cow<'static, str>,
1925 },
1926
1927 Retry {
1929 message: Cow<'static, str>,
1931 },
1932}
1933
1934impl<S: EngineSpec> StepProgress<S> {
1935 pub fn with_current_and_total(
1937 current: u64,
1938 total: u64,
1939 units: impl Into<ProgressUnits>,
1940 metadata: S::ProgressMetadata,
1941 ) -> Self {
1942 Self::Progress {
1943 progress: Some(ProgressCounter {
1944 current,
1945 total: Some(total),
1946 units: units.into(),
1947 }),
1948 metadata,
1949 }
1950 }
1951
1952 pub fn with_current(
1954 current: u64,
1955 units: impl Into<ProgressUnits>,
1956 metadata: S::ProgressMetadata,
1957 ) -> Self {
1958 Self::Progress {
1959 progress: Some(ProgressCounter {
1960 current,
1961 total: None,
1962 units: units.into(),
1963 }),
1964 metadata,
1965 }
1966 }
1967
1968 pub fn progress(metadata: S::ProgressMetadata) -> Self {
1970 Self::Progress { progress: None, metadata }
1971 }
1972
1973 pub fn reset(
1975 metadata: S::ProgressMetadata,
1976 message: impl Into<Cow<'static, str>>,
1977 ) -> Self {
1978 Self::Reset { metadata, message: message.into() }
1979 }
1980
1981 pub fn retry(message: impl Into<Cow<'static, str>>) -> Self {
1983 Self::Retry { message: message.into() }
1984 }
1985}
1986
1987#[derive_where(Clone, Debug, Default, Eq, PartialEq)]
1992#[derive(Deserialize, Serialize)]
1993#[serde(bound = "", rename_all = "snake_case")]
1994pub struct EventReport<S: EngineSpec> {
1995 pub step_events: Vec<StepEvent<S>>,
1999
2000 pub progress_events: Vec<ProgressEvent<S>>,
2006
2007 pub root_execution_id: Option<ExecutionUuid>,
2013
2014 pub last_seen: Option<usize>,
2018}
2019
2020#[cfg(feature = "schemars08")]
2021impl<S: JsonSchemaEngineSpec> schemars::JsonSchema for EventReport<S> {
2022 fn schema_name() -> String {
2023 format!("EventReportFor{}", S::schema_name())
2024 }
2025
2026 fn json_schema(
2027 generator: &mut schemars::r#gen::SchemaGenerator,
2028 ) -> schemars::schema::Schema {
2029 use crate::schema::with_description;
2030 use schemars::schema::{ObjectValidation, SchemaObject};
2031
2032 let mut obj = ObjectValidation::default();
2033 obj.properties.insert(
2034 "step_events".to_owned(),
2035 with_description(
2036 generator.subschema_for::<Vec<StepEvent<S>>>(),
2037 "A list of step events.",
2038 ),
2039 );
2040 obj.properties.insert(
2041 "progress_events".to_owned(),
2042 with_description(
2043 generator.subschema_for::<Vec<ProgressEvent<S>>>(),
2044 "A list of progress events, or whether we're \
2045 currently waiting for a progress event.",
2046 ),
2047 );
2048 obj.properties.insert(
2049 "root_execution_id".to_owned(),
2050 with_description(
2051 generator.subschema_for::<Option<ExecutionUuid>>(),
2052 "The root execution ID for this report.",
2053 ),
2054 );
2055 obj.properties.insert(
2056 "last_seen".to_owned(),
2057 with_description(
2058 generator.subschema_for::<Option<usize>>(),
2059 "The last event seen.",
2060 ),
2061 );
2062 obj.required = ["step_events", "progress_events"]
2063 .into_iter()
2064 .map(String::from)
2065 .collect();
2066
2067 let mut extensions = serde_json::Map::new();
2068 if let Some(info) = S::rust_type_info() {
2069 extensions.insert(
2070 "x-rust-type".to_owned(),
2071 crate::schema::rust_type_for_generic(&info, "EventReport"),
2072 );
2073 }
2074
2075 SchemaObject {
2076 metadata: Some(Box::new(schemars::schema::Metadata {
2077 description: Some(
2078 "An oxide-update-engine event report.\
2079 \n\nRemote reports can be passed into a \
2080 `StepContext`, in which case they show up as \
2081 nested events."
2082 .to_owned(),
2083 ),
2084 ..Default::default()
2085 })),
2086 instance_type: Some(schemars::schema::InstanceType::Object.into()),
2087 object: Some(Box::new(obj)),
2088 extensions: extensions.into_iter().collect(),
2089 ..Default::default()
2090 }
2091 .into()
2092 }
2093}
2094
2095impl<S: EngineSpec> EventReport<S> {
2096 pub fn from_generic(
2101 value: EventReport<GenericSpec>,
2102 ) -> Result<Self, ConvertGenericError> {
2103 Ok(Self {
2104 step_events: value
2105 .step_events
2106 .into_iter()
2107 .enumerate()
2108 .map(|(index, event)| {
2109 StepEvent::from_generic(event).map_err(|error| {
2110 error.parent_array("step_events", index)
2111 })
2112 })
2113 .collect::<Result<Vec<_>, _>>()?,
2114 progress_events: value
2115 .progress_events
2116 .into_iter()
2117 .enumerate()
2118 .map(|(index, event)| {
2119 ProgressEvent::from_generic(event).map_err(|error| {
2120 error.parent_array("progress_events", index)
2121 })
2122 })
2123 .collect::<Result<Vec<_>, _>>()?,
2124 root_execution_id: value.root_execution_id,
2125 last_seen: value.last_seen,
2126 })
2127 }
2128
2129 pub fn into_generic(self) -> EventReport<GenericSpec> {
2140 EventReport {
2141 step_events: self
2142 .step_events
2143 .into_iter()
2144 .map(|event| event.into_generic())
2145 .collect(),
2146 progress_events: self
2147 .progress_events
2148 .into_iter()
2149 .map(|event| event.into_generic())
2150 .collect(),
2151 root_execution_id: self.root_execution_id,
2152 last_seen: self.last_seen,
2153 }
2154 }
2155}
2156
2157#[cfg(test)]
2158mod tests {
2159 use super::*;
2160 use crate::spec::EngineSpec;
2161
2162 enum TestSpec {}
2163
2164 impl EngineSpec for TestSpec {
2165 fn spec_name() -> String {
2166 "TestSpec".to_owned()
2167 }
2168
2169 type Component = String;
2170 type StepId = usize;
2171 type StepMetadata = serde_json::Value;
2172 type ProgressMetadata = serde_json::Value;
2173 type CompletionMetadata = serde_json::Value;
2174 type SkippedMetadata = serde_json::Value;
2175 type Error = anyhow::Error;
2176 }
2177
2178 fn test_execution_id() -> ExecutionUuid {
2179 "2cc08a14-5e96-4917-bc70-e98293a3b703".parse().expect("parsed UUID")
2180 }
2181
2182 #[test]
2183 fn step_event_parse_unknown() {
2184 let execution_id = test_execution_id();
2185 let tests = [
2186 (
2187 r#"
2188 {
2189 "spec": "TestSpec",
2190 "execution_id": "2cc08a14-5e96-4917-bc70-e98293a3b703",
2191 "event_index": 0,
2192 "total_elapsed": {
2193 "secs": 0,
2194 "nanos": 0
2195 },
2196 "data": {
2197 "kind": "unknown_variant",
2198 "last_step": {
2199 "info": {
2200 "id": 0,
2201 "component": "foo",
2202 "description": "Description",
2203 "index": 0,
2204 "component_index": 0,
2205 "total_component_steps": 1
2206 },
2207 "metadata": null
2208 },
2209 "last_attempt": 1,
2210 "last_outcome": {
2211 "kind": "success",
2212 "metadata": null
2213 },
2214 "step_elapsed": {
2215 "secs": 0,
2216 "nanos": 0
2217 },
2218 "attempt_elapsed": {
2219 "secs": 0,
2220 "nanos": 0
2221 }
2222 }
2223 }
2224 "#,
2225 StepEvent {
2226 spec: TestSpec::spec_name(),
2227 execution_id,
2228 event_index: 0,
2229 total_elapsed: Duration::ZERO,
2230 kind: StepEventKind::Unknown,
2231 },
2232 ),
2233 (
2234 r#"
2235 {
2236 "spec": "TestSpec",
2237 "execution_id": "2cc08a14-5e96-4917-bc70-e98293a3b703",
2238 "event_index": 1,
2239 "total_elapsed": {
2240 "secs": 0,
2241 "nanos": 0
2242 },
2243 "data": {
2244 "kind": "execution_completed",
2245 "last_step": {
2246 "info": {
2247 "id": 0,
2248 "component": "foo",
2249 "description": "Description",
2250 "index": 0,
2251 "component_index": 0,
2252 "total_component_steps": 1
2253 },
2254 "metadata": null
2255 },
2256 "last_attempt": 1,
2257 "last_outcome": {
2258 "kind": "success",
2259 "message": null,
2260 "metadata": null
2261 },
2262 "step_elapsed": {
2263 "secs": 0,
2264 "nanos": 0
2265 },
2266 "attempt_elapsed": {
2267 "secs": 0,
2268 "nanos": 0
2269 },
2270 "unknown_field": 123
2271 }
2272 }
2273 "#,
2274 StepEvent::<TestSpec> {
2275 spec: TestSpec::spec_name(),
2276 execution_id,
2277 event_index: 1,
2278 total_elapsed: Duration::ZERO,
2279 kind: StepEventKind::ExecutionCompleted {
2280 last_step: StepInfoWithMetadata {
2281 info: StepInfo {
2282 id: 0,
2283 component: "foo".to_owned(),
2284 description: "Description".into(),
2285 index: 0,
2286 component_index: 0,
2287 total_component_steps: 1,
2288 },
2289 metadata: None,
2290 },
2291 last_attempt: 1,
2292 last_outcome: StepOutcome::Success {
2293 message: None,
2294 metadata: None,
2295 },
2296 step_elapsed: Duration::ZERO,
2297 attempt_elapsed: Duration::ZERO,
2298 },
2299 },
2300 ),
2301 ];
2302
2303 for (index, (input, expected)) in tests.into_iter().enumerate() {
2304 let actual: StepEvent<TestSpec> = serde_json::from_str(input)
2305 .unwrap_or_else(|error| {
2306 panic!("index {index}: unknown variant deserialized correctly: {error}")
2307 });
2308 assert_eq!(expected, actual, "input matches actual output");
2309 }
2310 }
2311
2312 #[test]
2313 fn progress_event_parse_unknown() {
2314 let execution_id = test_execution_id();
2315
2316 let tests = [
2317 (
2318 r#"
2319 {
2320 "spec": "TestSpec",
2321 "execution_id": "2cc08a14-5e96-4917-bc70-e98293a3b703",
2322 "total_elapsed": {
2323 "secs": 0,
2324 "nanos": 0
2325 },
2326 "data": {
2327 "kind": "unknown_variant",
2328 "step": {
2329 "info": {
2330 "id": 0,
2331 "component": "foo",
2332 "description": "Description",
2333 "index": 0,
2334 "component_index": 0,
2335 "total_component_steps": 1
2336 },
2337 "metadata": null
2338 },
2339 "attempt": 1,
2340 "metadata": null,
2341 "progress": {
2342 "current": 123,
2343 "total": null
2344 },
2345 "step_elapsed": {
2346 "secs": 0,
2347 "nanos": 0
2348 },
2349 "attempt_elapsed": {
2350 "secs": 0,
2351 "nanos": 0
2352 }
2353 }
2354 }
2355 "#,
2356 ProgressEvent {
2357 spec: TestSpec::spec_name(),
2358 execution_id,
2359 total_elapsed: Duration::ZERO,
2360 kind: ProgressEventKind::Unknown,
2361 },
2362 ),
2363 (
2364 r#"
2365 {
2366 "spec": "TestSpec",
2367 "execution_id": "2cc08a14-5e96-4917-bc70-e98293a3b703",
2368 "total_elapsed": {
2369 "secs": 0,
2370 "nanos": 0
2371 },
2372 "data": {
2373 "kind": "progress",
2374 "step": {
2375 "info": {
2376 "id": 0,
2377 "component": "foo",
2378 "description": "Description",
2379 "index": 0,
2380 "component_index": 0,
2381 "total_component_steps": 1
2382 },
2383 "metadata": null
2384 },
2385 "attempt": 1,
2386 "metadata": null,
2387 "progress": {
2388 "current": 123,
2389 "total": null,
2390 "units": "bytes"
2391 },
2392 "step_elapsed": {
2393 "secs": 0,
2394 "nanos": 0
2395 },
2396 "attempt_elapsed": {
2397 "secs": 0,
2398 "nanos": 0
2399 },
2400 "unknown_field": 123
2401 }
2402 }
2403 "#,
2404 ProgressEvent::<TestSpec> {
2405 spec: TestSpec::spec_name(),
2406 execution_id,
2407 total_elapsed: Duration::ZERO,
2408 kind: ProgressEventKind::Progress {
2409 step: StepInfoWithMetadata {
2410 info: StepInfo {
2411 id: 0,
2412 component: "foo".to_owned(),
2413 description: "Description".into(),
2414 index: 0,
2415 component_index: 0,
2416 total_component_steps: 1,
2417 },
2418 metadata: None,
2419 },
2420 attempt: 1,
2421 metadata: serde_json::Value::Null,
2422 progress: Some(ProgressCounter::current(
2423 123,
2424 ProgressUnits::BYTES,
2425 )),
2426 step_elapsed: Duration::ZERO,
2427 attempt_elapsed: Duration::ZERO,
2428 },
2429 },
2430 ),
2431 ];
2432
2433 for (index, (input, expected)) in tests.into_iter().enumerate() {
2434 let actual: ProgressEvent<TestSpec> = serde_json::from_str(input)
2435 .unwrap_or_else(|error| {
2436 panic!("index {index}: unknown variant deserialized correctly: {error}")
2437 });
2438 assert_eq!(expected, actual, "input matches actual output");
2439 }
2440 }
2441
2442 fn generic_step() -> StepInfoWithMetadata<GenericSpec> {
2443 StepInfoWithMetadata {
2444 info: StepInfo {
2445 id: serde_json::Value::Null,
2446 component: serde_json::Value::Null,
2447 description: "Description".into(),
2448 index: 0,
2449 component_index: 0,
2450 total_component_steps: 1,
2451 },
2452 metadata: None,
2453 }
2454 }
2455
2456 fn generic_progress_event(
2457 kind: ProgressEventKind<GenericSpec>,
2458 ) -> ProgressEvent<GenericSpec> {
2459 ProgressEvent {
2460 spec: GenericSpec::spec_name(),
2461 execution_id: test_execution_id(),
2462 total_elapsed: Duration::ZERO,
2463 kind,
2464 }
2465 }
2466
2467 fn generic_progress(
2468 progress: Option<ProgressCounter>,
2469 ) -> ProgressEventKind<GenericSpec> {
2470 ProgressEventKind::Progress {
2471 step: generic_step(),
2472 attempt: 1,
2473 metadata: serde_json::Value::Null,
2474 progress,
2475 step_elapsed: Duration::ZERO,
2476 attempt_elapsed: Duration::ZERO,
2477 }
2478 }
2479
2480 fn generic_waiting() -> ProgressEventKind<GenericSpec> {
2481 ProgressEventKind::WaitingForProgress {
2482 step: generic_step(),
2483 attempt: 1,
2484 step_elapsed: Duration::ZERO,
2485 attempt_elapsed: Duration::ZERO,
2486 }
2487 }
2488
2489 fn nest(
2490 inner: ProgressEventKind<GenericSpec>,
2491 ) -> ProgressEventKind<GenericSpec> {
2492 ProgressEventKind::Nested {
2493 step: generic_step(),
2494 attempt: 1,
2495 event: Box::new(generic_progress_event(inner)),
2496 step_elapsed: Duration::ZERO,
2497 attempt_elapsed: Duration::ZERO,
2498 }
2499 }
2500
2501 #[test]
2502 fn leaf_progress_recurses_to_innermost_counter() {
2503 let counter = ProgressCounter::current(42, ProgressUnits::BYTES);
2504 let leaf = generic_progress(Some(counter.clone()));
2505
2506 let nested = nest(nest(leaf));
2507
2508 assert_eq!(
2509 nested.leaf_progress(),
2510 LeafProgress::Progress(Some(&counter)),
2511 "leaf_progress recurses to the innermost counter",
2512 );
2513 assert_eq!(
2514 nested.progress_counter(),
2515 Some(&counter),
2516 "progress_counter agrees with leaf_progress",
2517 );
2518 }
2519
2520 #[test]
2521 fn leaf_progress_distinguishes_progress_none_from_waiting() {
2522 let progress_none = generic_progress(None);
2523 assert_eq!(
2524 progress_none.leaf_progress(),
2525 LeafProgress::Progress(None),
2526 "Progress with no counter maps to Progress(None)",
2527 );
2528 assert_eq!(
2529 progress_none.progress_counter(),
2530 None,
2531 "Progress(None) has no progress counter",
2532 );
2533
2534 let waiting = generic_waiting();
2535 assert_eq!(
2536 waiting.leaf_progress(),
2537 LeafProgress::WaitingForProgress,
2538 "WaitingForProgress maps to LeafProgress::WaitingForProgress",
2539 );
2540 assert_ne!(
2541 waiting.leaf_progress(),
2542 progress_none.leaf_progress(),
2543 "WaitingForProgress is distinct from Progress(None)",
2544 );
2545 }
2546
2547 #[test]
2548 fn leaf_progress_recurses_to_nested_waiting() {
2549 let nested = nest(generic_waiting());
2550 assert_eq!(
2551 nested.leaf_progress(),
2552 LeafProgress::WaitingForProgress,
2553 "leaf_progress recurses through Nested to WaitingForProgress",
2554 );
2555 assert_eq!(
2556 nested.progress_counter(),
2557 None,
2558 "nested WaitingForProgress has no progress counter",
2559 );
2560 }
2561
2562 #[test]
2563 fn leaf_progress_unknown() {
2564 let unknown = ProgressEventKind::<GenericSpec>::Unknown;
2565 assert_eq!(
2566 unknown.leaf_progress(),
2567 LeafProgress::Unknown,
2568 "Unknown maps to LeafProgress::Unknown",
2569 );
2570 assert_eq!(
2571 unknown.progress_counter(),
2572 None,
2573 "Unknown has no progress counter",
2574 );
2575 }
2576
2577 #[cfg(feature = "schemars08")]
2580 mod schema_tests {
2581 use super::*;
2582 use crate::schema::RustTypeInfo;
2583 use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::Schema};
2584
2585 fn get_rust_type_ext(schema: &Schema) -> Option<&serde_json::Value> {
2588 match schema {
2589 Schema::Object(obj) => obj.extensions.get("x-rust-type"),
2590 Schema::Bool(_) => None,
2591 }
2592 }
2593
2594 fn assert_rust_type_ext(
2597 x_rust_type: &serde_json::Value,
2598 expected_crate: &str,
2599 expected_version: &str,
2600 expected_path: &str,
2601 ) {
2602 assert_eq!(
2603 x_rust_type.get("crate").and_then(|v| v.as_str()),
2604 Some(expected_crate),
2605 "x-rust-type crate"
2606 );
2607 assert_eq!(
2608 x_rust_type.get("version").and_then(|v| v.as_str()),
2609 Some(expected_version),
2610 "x-rust-type version"
2611 );
2612 assert_eq!(
2613 x_rust_type.get("path").and_then(|v| v.as_str()),
2614 Some(expected_path),
2615 "x-rust-type path"
2616 );
2617 }
2618
2619 #[test]
2622 fn execution_uuid_kind_rust_type() {
2623 let mut generator = SchemaGenerator::default();
2624 let schema = ExecutionUuidKind::json_schema(&mut generator);
2625 let xrt = get_rust_type_ext(&schema).expect("x-rust-type present");
2626 assert_rust_type_ext(
2627 xrt,
2628 "oxide-update-engine-types",
2629 env!("CARGO_PKG_VERSION"),
2630 "oxide_update_engine_types::events\
2631 ::ExecutionUuidKind",
2632 );
2633 }
2634
2635 #[test]
2636 fn execution_uuid_lifted_rust_type() {
2637 let mut generator = SchemaGenerator::default();
2641 let schema = ExecutionUuid::json_schema(&mut generator);
2642 let xrt = get_rust_type_ext(&schema)
2643 .expect("x-rust-type present on ExecutionUuid");
2644 assert_rust_type_ext(
2645 xrt,
2646 "oxide-update-engine-types",
2647 env!("CARGO_PKG_VERSION"),
2648 "oxide_update_engine_types::events\
2649 ::ExecutionUuid",
2650 );
2651 }
2652
2653 #[test]
2656 fn event_report_generic_spec_schema() {
2657 let schema = schemars::schema_for!(EventReport<GenericSpec>);
2658 let json = serde_json::to_string_pretty(&schema)
2659 .expect("serialized schema");
2660 expectorate::assert_contents(
2661 "tests/output/event_report_generic_spec_schema.json",
2662 &json,
2663 );
2664 }
2665
2666 impl schemars::JsonSchema for super::TestSpec {
2670 fn schema_name() -> String {
2671 "TestSpec".to_owned()
2672 }
2673
2674 fn json_schema(_: &mut SchemaGenerator) -> Schema {
2675 Schema::Bool(true)
2676 }
2677 }
2678
2679 #[test]
2680 fn step_event_no_rust_type_without_info() {
2681 let mut generator = SchemaGenerator::default();
2682 let schema =
2683 StepEvent::<super::TestSpec>::json_schema(&mut generator);
2684 assert!(
2685 get_rust_type_ext(&schema).is_none(),
2686 "no x-rust-type when spec returns None"
2687 );
2688 }
2689
2690 #[test]
2691 fn progress_event_no_rust_type_without_info() {
2692 let mut generator = SchemaGenerator::default();
2693 let schema =
2694 ProgressEvent::<super::TestSpec>::json_schema(&mut generator);
2695 assert!(
2696 get_rust_type_ext(&schema).is_none(),
2697 "no x-rust-type when spec returns None"
2698 );
2699 }
2700
2701 #[test]
2702 fn event_report_no_rust_type_without_info() {
2703 let mut generator = SchemaGenerator::default();
2704 let schema =
2705 EventReport::<super::TestSpec>::json_schema(&mut generator);
2706 assert!(
2707 get_rust_type_ext(&schema).is_none(),
2708 "no x-rust-type when spec returns None"
2709 );
2710 }
2711
2712 enum TestSpecWithInfo {}
2719
2720 impl crate::spec::EngineSpec for TestSpecWithInfo {
2721 fn spec_name() -> String {
2722 "TestSpecWithInfo".to_owned()
2723 }
2724
2725 type Component = serde_json::Value;
2726 type StepId = serde_json::Value;
2727 type StepMetadata = serde_json::Value;
2728 type ProgressMetadata = serde_json::Value;
2729 type CompletionMetadata = serde_json::Value;
2730 type SkippedMetadata = serde_json::Value;
2731 type Error = anyhow::Error;
2732
2733 fn rust_type_info() -> Option<RustTypeInfo> {
2734 Some(RustTypeInfo {
2735 crate_name: "my-external-crate",
2736 version: "1.2.3",
2737 path: "my_external_crate::MySpec",
2738 })
2739 }
2740 }
2741
2742 impl JsonSchema for TestSpecWithInfo {
2743 fn schema_name() -> String {
2744 "TestSpecWithInfo".to_owned()
2745 }
2746
2747 fn json_schema(_: &mut SchemaGenerator) -> Schema {
2748 Schema::Bool(true)
2749 }
2750 }
2751
2752 fn assert_external_spec_rust_type(
2756 x_rust_type: &serde_json::Value,
2757 expected_outer_path: &str,
2758 expected_param_crate: &str,
2759 expected_param_version: &str,
2760 expected_param_path: &str,
2761 ) {
2762 assert_rust_type_ext(
2764 x_rust_type,
2765 "oxide-update-engine-types",
2766 env!("CARGO_PKG_VERSION"),
2767 expected_outer_path,
2768 );
2769
2770 let params = x_rust_type
2772 .get("parameters")
2773 .and_then(|v| v.as_array())
2774 .expect("parameters array present");
2775 assert_eq!(params.len(), 1, "exactly one parameter");
2776 let param_xrt = params[0]
2777 .get("x-rust-type")
2778 .expect("parameter x-rust-type present");
2779 assert_rust_type_ext(
2780 param_xrt,
2781 expected_param_crate,
2782 expected_param_version,
2783 expected_param_path,
2784 );
2785 }
2786
2787 #[test]
2788 fn step_event_external_spec_rust_type() {
2789 let mut generator = SchemaGenerator::default();
2790 let schema =
2791 StepEvent::<TestSpecWithInfo>::json_schema(&mut generator);
2792 let xrt = get_rust_type_ext(&schema).expect("x-rust-type present");
2793 assert_external_spec_rust_type(
2794 xrt,
2795 "oxide_update_engine_types::events::StepEvent",
2796 "my-external-crate",
2797 "1.2.3",
2798 "my_external_crate::MySpec",
2799 );
2800 }
2801
2802 #[test]
2803 fn progress_event_external_spec_rust_type() {
2804 let mut generator = SchemaGenerator::default();
2805 let schema =
2806 ProgressEvent::<TestSpecWithInfo>::json_schema(&mut generator);
2807 let xrt = get_rust_type_ext(&schema).expect("x-rust-type present");
2808 assert_external_spec_rust_type(
2809 xrt,
2810 "oxide_update_engine_types::events\
2811 ::ProgressEvent",
2812 "my-external-crate",
2813 "1.2.3",
2814 "my_external_crate::MySpec",
2815 );
2816 }
2817
2818 #[test]
2819 fn event_report_external_spec_rust_type() {
2820 let mut generator = SchemaGenerator::default();
2821 let schema =
2822 EventReport::<TestSpecWithInfo>::json_schema(&mut generator);
2823 let xrt = get_rust_type_ext(&schema).expect("x-rust-type present");
2824 assert_external_spec_rust_type(
2825 xrt,
2826 "oxide_update_engine_types::events\
2827 ::EventReport",
2828 "my-external-crate",
2829 "1.2.3",
2830 "my_external_crate::MySpec",
2831 );
2832 }
2833 }
2834}