solti_api/grpc/convert/
run.rs1use solti_model::TaskRun;
7
8use super::time::system_time_to_ms;
9use crate::error::ApiError;
10use crate::proto_api;
11use crate::visibility::run_is_visible;
12
13impl TryFrom<TaskRun> for proto_api::TaskRunInfo {
14 type Error = ApiError;
15
16 fn try_from(run: TaskRun) -> Result<Self, Self::Error> {
17 if !run_is_visible(&run) {
18 return Err(ApiError::Internal(
19 "handler returned an Embedded task run with no wire representation".into(),
20 ));
21 }
22
23 let (workload, generation, attempt, phase, started_at, finished_at, error, exit_code) =
24 run.into_parts();
25 Ok(proto_api::TaskRunInfo {
26 workload_api_version: workload.api_version().to_owned(),
27 workload_kind: workload.kind().to_owned(),
28 generation,
29 phase: proto_api::TaskPhase::try_from(phase)? as i32,
30 finished_at: finished_at.map(system_time_to_ms).transpose()?,
31 started_at: system_time_to_ms(started_at)?,
32 exit_code,
33 attempt,
34 error,
35 })
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use solti_model::{TaskPhase, WORKLOAD_API_VERSION, WorkloadTypeMeta};
43 use std::time::{Duration, UNIX_EPOCH};
44
45 #[test]
46 fn run_converts_all_fields() {
47 let started = UNIX_EPOCH + Duration::from_millis(1_700_000_000_000);
48 let finished = UNIX_EPOCH + Duration::from_millis(1_700_000_001_500);
49
50 let workload = WorkloadTypeMeta::new("example.io/v1", "DatabaseBackup").unwrap();
51 let run = TaskRun::from_parts(
52 workload,
53 3,
54 2,
55 TaskPhase::Failed,
56 started,
57 Some(finished),
58 Some("boom".into()),
59 Some(137),
60 )
61 .unwrap();
62
63 let proto = proto_api::TaskRunInfo::try_from(run).unwrap();
64
65 assert_eq!(proto.workload_api_version, "example.io/v1");
66 assert_eq!(proto.workload_kind, "DatabaseBackup");
67 assert_eq!(proto.attempt, 2);
68 assert_eq!(proto.generation, 3);
69 assert_eq!(proto.phase, proto_api::TaskPhase::Failed as i32);
70 assert_eq!(proto.started_at, 1_700_000_000_000);
71 assert_eq!(proto.finished_at, Some(1_700_000_001_500));
72 assert_eq!(proto.error.as_deref(), Some("boom"));
73 assert_eq!(proto.exit_code, Some(137));
74 }
75
76 #[test]
77 fn run_active_has_no_finished_timestamp() {
78 let workload = WorkloadTypeMeta::new(WORKLOAD_API_VERSION, "Subprocess").unwrap();
79 let run = TaskRun::starting(1, 1, workload).unwrap();
80 let proto = proto_api::TaskRunInfo::try_from(run).unwrap();
81 assert_eq!(proto.finished_at, None);
82 assert_eq!(proto.exit_code, None);
83 }
84
85 #[test]
86 fn embedded_run_is_not_exposed() {
87 let workload = WorkloadTypeMeta::new(WORKLOAD_API_VERSION, "Embedded").unwrap();
88 let run = TaskRun::starting(1, 1, workload).unwrap();
89
90 let error = proto_api::TaskRunInfo::try_from(run).unwrap_err();
91 assert!(matches!(error, ApiError::Internal(message) if message.contains("Embedded")));
92 }
93}