Skip to main content

fallow_types/
cloud.rs

1//! Cross-surface strings for the cloud runtime-coverage path.
2//!
3//! The cloud commands of `fallow coverage` and the matching MCP cloud tools
4//! refuse the same call for the same reason, and an agent reads both.
5//! Keeping the refusal text here makes the two surfaces say one sentence
6//! instead of holding two copies that drift apart.
7
8/// A cloud command that needs a Fallow Cloud API key. The refusal names the
9/// command that ran, so the example line can be copied as it is.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CloudCommand {
12    /// `fallow coverage analyze --cloud`, and the MCP `get_cloud_runtime_context` tool.
13    Analyze,
14    /// `fallow coverage review-packet`, and the MCP `get_cloud_review_packet` tool.
15    ReviewPacket,
16    /// `fallow coverage deployment-changes`, and the MCP `get_cloud_deployment_changes` tool.
17    DeploymentChanges,
18}
19
20impl CloudCommand {
21    /// A minimal command line for this command, without the API key.
22    #[must_use]
23    pub const fn example(self) -> &'static str {
24        match self {
25            Self::Analyze => "fallow coverage analyze --cloud --repo owner/repo",
26            Self::ReviewPacket => "fallow coverage review-packet --repo owner/repo",
27            Self::DeploymentChanges => "fallow coverage deployment-changes --repo owner/repo",
28        }
29    }
30}
31
32/// Refusal emitted when no Fallow Cloud API key is available. Names the
33/// environment variable first because that is the only route the MCP tools
34/// have; the flag and the command line of `command` follow for the CLI surface.
35#[must_use]
36pub fn cloud_api_key_missing_message(command: CloudCommand) -> String {
37    format!(
38        "Cloud runtime coverage requires an API key.\n\nSet FALLOW_API_KEY or pass --api-key:\n\n  FALLOW_API_KEY=fallow_live_... {}",
39        command.example()
40    )
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn each_command_names_itself_in_the_example() {
49        for (command, subcommand) in [
50            (CloudCommand::Analyze, "coverage analyze --cloud"),
51            (CloudCommand::ReviewPacket, "coverage review-packet"),
52            (
53                CloudCommand::DeploymentChanges,
54                "coverage deployment-changes",
55            ),
56        ] {
57            let message = cloud_api_key_missing_message(command);
58            assert!(message.starts_with("Cloud runtime coverage requires an API key."));
59            assert!(
60                message.ends_with(&format!(
61                    "fallow_live_... fallow {subcommand} --repo owner/repo"
62                )),
63                "{command:?}: {message}"
64            );
65        }
66    }
67}