ready_set_rust/
workflow.rs1use std::process::{Command, ExitStatus, Stdio};
4
5use ready_set_sdk::{
6 CapabilityAction, CapabilityActionKind, CapabilityRunReport, CapabilityVerb, Context, Error,
7 ExitCode, Output, OutputMode, Result, RunStatus,
8};
9
10use crate::workspace::Workspace;
11
12pub fn go_capability(capability: &str, workspace: &Workspace, ctx: &Context) -> Result<ExitCode> {
19 let Some(args) = cargo_args(capability) else {
20 return Ok(ExitCode::UserError);
21 };
22 let capture_json = matches!(ctx.output_mode(), OutputMode::Json);
23
24 if capture_json {
25 let output = Command::new("cargo")
26 .args(args)
27 .current_dir(&workspace.root)
28 .stdin(Stdio::null())
29 .stdout(Stdio::piped())
30 .stderr(Stdio::piped())
31 .output()
32 .map_err(map_cargo_spawn_error)?;
33 let report = report_for_status(capability, args, output.status);
34 let exit_code = workflow_exit_code(output.status);
35 let mut out = Output::for_context(ctx, std::io::stdout());
36 out.json(&report)?;
37 Ok(exit_code)
38 } else {
39 let status = Command::new("cargo")
40 .args(args)
41 .current_dir(&workspace.root)
42 .status()
43 .map_err(map_cargo_spawn_error)?;
44 Ok(workflow_exit_code(status))
45 }
46}
47
48fn cargo_args(capability: &str) -> Option<&'static [&'static str]> {
49 match capability {
50 "formatting" => Some(&["fmt", "--check"]),
51 "linting" => Some(&["clippy", "--workspace", "--all-targets"]),
52 _ => None,
53 }
54}
55
56fn report_for_status(capability: &str, args: &[&str], status: ExitStatus) -> CapabilityRunReport {
57 let command = command_label(args);
58 let (run_status, action) = if status.success() {
59 (
60 RunStatus::Ok,
61 CapabilityAction {
62 kind: CapabilityActionKind::Run,
63 summary: format!("{command} completed"),
64 path: None,
65 },
66 )
67 } else {
68 (
69 RunStatus::Failed,
70 CapabilityAction {
71 kind: CapabilityActionKind::Error,
72 summary: format!("{command} {}", status_summary(status)),
73 path: None,
74 },
75 )
76 };
77
78 CapabilityRunReport {
79 id: capability.into(),
80 verb: CapabilityVerb::Go,
81 status: run_status,
82 actions: vec![action],
83 }
84}
85
86fn command_label(args: &[&str]) -> String {
87 format!("cargo {}", args.join(" "))
88}
89
90fn status_summary(status: ExitStatus) -> String {
91 status.code().map_or_else(
92 || "terminated before reporting an exit code".into(),
93 |code| format!("exited with code {code}"),
94 )
95}
96
97fn workflow_exit_code(status: ExitStatus) -> ExitCode {
98 if status.success() {
99 ExitCode::Ok
100 } else if status.code().is_some() {
101 ExitCode::UserError
102 } else {
103 ExitCode::SystemError
104 }
105}
106
107fn map_cargo_spawn_error(err: std::io::Error) -> Error {
108 if err.kind() == std::io::ErrorKind::NotFound {
109 Error::MissingDependency {
110 name: "cargo".into(),
111 hint: Some("install Rust and Cargo, or ensure cargo is on PATH".into()),
112 }
113 } else {
114 Error::Io(err)
115 }
116}