1use std::time::{Duration, SystemTime};
2
3use super::{
4 CommandExecutionContext, CommandIdentity, CommandRecordId, CommandResultSummary, CommandSource,
5 DEFAULT_STREAM_LIMIT, ExitStatusSummary, OutputRetention, StreamSummary,
6};
7use crate::{ExecutionMode, JjCommandSpec, RefreshPlan, SafetyClass};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct CommandRecord {
12 pub id: CommandRecordId,
14 pub command: CommandIdentity,
16 pub source: CommandSource,
18 pub context: CommandExecutionContext,
20 pub timing: CommandTiming,
22 pub result: CommandResultSummary,
24 pub retention: OutputRetention,
26 pub refresh: RefreshPlan,
28 pub safety: SafetyClass,
30 pub execution_mode: ExecutionMode,
32 pub operation_id: Option<String>,
34}
35
36impl CommandRecord {
37 pub(super) fn started(id: CommandRecordId, input: CommandRecordStart) -> Self {
38 Self {
39 id,
40 command: input.command,
41 source: input.source,
42 context: input.context,
43 timing: CommandTiming {
44 started_at: input.started_at,
45 ended_at: None,
46 duration: None,
47 },
48 result: CommandResultSummary::default(),
49 retention: input.retention,
50 refresh: input.refresh,
51 safety: input.safety,
52 execution_mode: input.execution_mode,
53 operation_id: None,
54 }
55 }
56
57 pub(super) fn finish(&mut self, mut finish: CommandRecordFinish) -> bool {
58 if self.timing.ended_at.is_some() {
59 return false;
60 }
61
62 let duration = finish
63 .ended_at
64 .duration_since(self.timing.started_at)
65 .unwrap_or_default();
66 finish.result.apply_retention(&self.retention);
67 self.timing.ended_at = Some(finish.ended_at);
68 self.timing.duration = Some(duration);
69 self.result = finish.result;
70 self.operation_id = finish.operation_id;
71 true
72 }
73}
74
75#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct CommandRecordStart {
78 pub command: CommandIdentity,
80 pub source: CommandSource,
82 pub context: CommandExecutionContext,
84 pub started_at: SystemTime,
86 pub retention: OutputRetention,
88 pub refresh: RefreshPlan,
90 pub safety: SafetyClass,
92 pub execution_mode: ExecutionMode,
94}
95
96impl CommandRecordStart {
97 #[must_use]
99 pub fn from_spec(spec: &JjCommandSpec, source: CommandSource) -> Self {
100 Self {
101 command: CommandIdentity::from_spec(spec),
102 source,
103 context: CommandExecutionContext::from_spec(spec),
104 started_at: SystemTime::now(),
105 retention: OutputRetention::summary_only(DEFAULT_STREAM_LIMIT, DEFAULT_STREAM_LIMIT),
106 refresh: spec.refresh_plan(),
107 safety: spec.safety(),
108 execution_mode: spec.mode(),
109 }
110 }
111
112 #[must_use]
114 pub const fn with_started_at(mut self, started_at: SystemTime) -> Self {
115 self.started_at = started_at;
116 self
117 }
118
119 #[must_use]
121 pub fn with_retention(mut self, retention: OutputRetention) -> Self {
122 self.retention = retention;
123 self
124 }
125}
126
127#[derive(Clone, Debug, Eq, PartialEq)]
129pub struct CommandRecordFinish {
130 pub ended_at: SystemTime,
132 pub result: CommandResultSummary,
134 pub operation_id: Option<String>,
136}
137
138impl CommandRecordFinish {
139 #[must_use]
141 pub const fn from_result(
142 result: CommandResultSummary,
143 operation_id: Option<String>,
144 ended_at: SystemTime,
145 ) -> Self {
146 Self {
147 ended_at,
148 result,
149 operation_id,
150 }
151 }
152
153 #[must_use]
155 pub fn from_exit_code(
156 code: i32,
157 stdout: impl AsRef<[u8]>,
158 stderr: impl AsRef<[u8]>,
159 ended_at: SystemTime,
160 ) -> Self {
161 Self::from_result(
162 CommandResultSummary {
163 exit_status: Some(ExitStatusSummary::code(code)),
164 spawn_error: None,
165 stdout: StreamSummary::from_bytes(stdout.as_ref(), DEFAULT_STREAM_LIMIT),
166 stderr: StreamSummary::from_bytes(stderr.as_ref(), DEFAULT_STREAM_LIMIT),
167 },
168 None,
169 ended_at,
170 )
171 }
172
173 #[must_use]
175 pub fn from_spawn_error(
176 error: impl Into<String>,
177 stdout: impl AsRef<[u8]>,
178 stderr: impl AsRef<[u8]>,
179 ended_at: SystemTime,
180 ) -> Self {
181 Self::from_result(
182 CommandResultSummary {
183 exit_status: None,
184 spawn_error: Some(error.into()),
185 stdout: StreamSummary::from_bytes(stdout.as_ref(), DEFAULT_STREAM_LIMIT),
186 stderr: StreamSummary::from_bytes(stderr.as_ref(), DEFAULT_STREAM_LIMIT),
187 },
188 None,
189 ended_at,
190 )
191 }
192}
193
194#[derive(Clone, Debug, Eq, PartialEq)]
196pub struct CommandTiming {
197 pub started_at: SystemTime,
199 pub ended_at: Option<SystemTime>,
201 pub duration: Option<Duration>,
203}