Skip to main content

callisto_cli/commands/
validate.rs

1use std::process::ExitCode;
2
3use callisto_graph::commands::ValidateOptions;
4
5use crate::cli::{GlobalArgs, OutputFormat, ValidateArgs};
6use crate::error::CliError;
7use crate::output::write_json;
8use crate::render;
9use crate::runner::CliCommandRunner;
10use crate::workspace::load_workspace;
11
12pub fn handle(args: ValidateArgs, global: &GlobalArgs) -> Result<ExitCode, CliError> {
13    let runner = CliCommandRunner;
14    let ws = load_workspace(global, &runner)?;
15
16    let opts = ValidateOptions {
17        staged: args.staged,
18        since: args.since,
19        strict: args.strict,
20        strict_graph: args.strict_graph,
21    };
22
23    let report = callisto_graph::commands::validate(&ws, &opts)?;
24
25    match global.format {
26        OutputFormat::Json => write_json(&mut std::io::stdout(), &report)?,
27        OutputFormat::Text => render::render_validate(&report, &mut std::io::stdout())?,
28    }
29
30    if report.ok {
31        Ok(ExitCode::SUCCESS)
32    } else {
33        Ok(ExitCode::FAILURE)
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    /// A changeset that parses cleanly (non-empty entries, non-empty summary)
42    /// but names a package absent from the workspace -- a validation-level
43    /// `Error` diagnostic (`UnknownPackage`), not a parse-time failure.
44    fn seed_workspace_with_unknown_package_changeset() -> tempfile::TempDir {
45        let tmp = tempfile::TempDir::new().unwrap();
46        let root = tmp.path();
47        std::fs::write(root.join("Cargo.toml"), "[workspace]\nmembers = []\nresolver = \"2\"\n").unwrap();
48        std::fs::write(root.join("callisto.toml"), "").unwrap();
49        let changeset_dir = root.join(".changeset");
50        std::fs::create_dir_all(&changeset_dir).unwrap();
51        std::fs::write(
52            changeset_dir.join("bad.md"),
53            "---\nnot-a-real-package: patch\n---\n\nSome change.\n",
54        )
55        .unwrap();
56        tmp
57    }
58
59    fn opts() -> ValidateArgs {
60        ValidateArgs {
61            staged: false,
62            since: None,
63            strict: false,
64            strict_graph: false,
65        }
66    }
67
68    #[test]
69    fn handle_text_format_reports_clean_workspace_as_success() {
70        let tmp = tempfile::TempDir::new().unwrap();
71        let root = tmp.path();
72        std::fs::write(root.join("Cargo.toml"), "[workspace]\nmembers = []\nresolver = \"2\"\n").unwrap();
73        std::fs::write(root.join("callisto.toml"), "").unwrap();
74
75        let global = GlobalArgs {
76            format: OutputFormat::Text,
77            cwd: root.to_path_buf(),
78            dry_run: false,
79        };
80
81        let result = handle(opts(), &global);
82        assert_eq!(result.unwrap(), ExitCode::SUCCESS);
83    }
84
85    #[test]
86    fn handle_returns_failure_exit_code_when_report_is_not_ok() {
87        let tmp = seed_workspace_with_unknown_package_changeset();
88        let global = GlobalArgs {
89            format: OutputFormat::Json,
90            cwd: tmp.path().to_path_buf(),
91            dry_run: false,
92        };
93
94        let result = handle(opts(), &global).expect("validate should not error on an invalid-but-parseable changeset");
95        assert_eq!(
96            result,
97            ExitCode::FAILURE,
98            "a workspace with a changeset naming an unknown package must report ok=false"
99        );
100    }
101}