1use alloc::{collections::BTreeMap, string::String, vec::Vec};
4
5use semver::Version;
6
7use crate::{ComponentId, FlowId, NodeId, PackId, SessionKey};
8
9#[cfg(feature = "schemars")]
10use schemars::JsonSchema;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13#[cfg(all(feature = "serde", feature = "time"))]
14use serde_with::serde_as;
15#[cfg(feature = "time")]
16use time::OffsetDateTime;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
22#[cfg_attr(feature = "schemars", derive(JsonSchema))]
23pub enum RunStatus {
24 Success,
26 PartialFailure,
28 Failure,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
36#[cfg_attr(feature = "schemars", derive(JsonSchema))]
37pub enum NodeStatus {
38 Ok,
40 Skipped,
42 Error,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[cfg_attr(feature = "schemars", derive(JsonSchema))]
50pub struct NodeSummary {
51 pub node_id: NodeId,
53 pub component: ComponentId,
55 pub status: NodeStatus,
57 pub duration_ms: u64,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Hash)]
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64#[cfg_attr(feature = "schemars", derive(JsonSchema))]
65pub struct TranscriptOffset {
66 pub start: u64,
68 pub end: u64,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
75#[cfg_attr(feature = "schemars", derive(JsonSchema))]
76pub struct NodeFailure {
77 pub code: String,
79 pub message: String,
81 #[cfg_attr(
83 feature = "serde",
84 serde(default, skip_serializing_if = "BTreeMap::is_empty")
85 )]
86 pub details: BTreeMap<String, String>,
87 #[cfg_attr(
89 feature = "serde",
90 serde(default, skip_serializing_if = "Vec::is_empty")
91 )]
92 pub transcript_offsets: Vec<TranscriptOffset>,
93 #[cfg_attr(
95 feature = "serde",
96 serde(default, skip_serializing_if = "Vec::is_empty")
97 )]
98 pub log_paths: Vec<String>,
99}
100
101#[cfg(feature = "time")]
103#[cfg_attr(feature = "serde", serde_as)]
104#[derive(Clone, Debug, PartialEq)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(feature = "schemars", derive(JsonSchema))]
107pub struct RunResult {
108 pub session_id: SessionKey,
110 pub pack_id: PackId,
112 #[cfg_attr(
114 feature = "serde",
115 serde_as(as = "serde_with::formats::DisplayFromStr")
116 )]
117 #[cfg_attr(
118 feature = "schemars",
119 schemars(with = "String", description = "SemVer version")
120 )]
121 pub pack_version: Version,
122 pub flow_id: FlowId,
124 #[cfg_attr(
126 feature = "schemars",
127 schemars(with = "String", description = "RFC3339 timestamp (UTC)")
128 )]
129 pub started_at_utc: OffsetDateTime,
130 #[cfg_attr(
132 feature = "schemars",
133 schemars(with = "String", description = "RFC3339 timestamp (UTC)")
134 )]
135 pub finished_at_utc: OffsetDateTime,
136 pub status: RunStatus,
138 #[cfg_attr(
140 feature = "serde",
141 serde(default, skip_serializing_if = "Vec::is_empty")
142 )]
143 pub node_summaries: Vec<NodeSummary>,
144 #[cfg_attr(
146 feature = "serde",
147 serde(default, skip_serializing_if = "Vec::is_empty")
148 )]
149 pub failures: Vec<NodeFailure>,
150 #[cfg_attr(
152 feature = "serde",
153 serde(default, skip_serializing_if = "Option::is_none")
154 )]
155 pub artifacts_dir: Option<String>,
156}
157
158#[cfg(feature = "time")]
159impl RunResult {
160 pub fn duration_ms(&self) -> u64 {
162 let duration = self.finished_at_utc - self.started_at_utc;
163 duration.whole_milliseconds().max(0) as u64
164 }
165}