Skip to main content

actionqueue_cli/cmd/
mod.rs

1//! Command execution primitives for the ActionQueue CLI control plane.
2
3use serde::Serialize;
4
5pub mod daemon;
6pub mod stats;
7pub mod submit;
8
9/// Stable classification for CLI failure lanes.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11#[serde(rename_all = "snake_case")]
12pub enum ErrorKind {
13    /// Invalid invocation shape or argument syntax.
14    Usage,
15    /// Contract-level input validation failure.
16    Validation,
17    /// Local runtime failure while executing command flow.
18    Runtime,
19    /// Connectivity failure class (reserved for remote control-plane paths).
20    Connectivity,
21}
22
23impl ErrorKind {
24    /// Maps error kind to deterministic process exit code.
25    pub const fn exit_code(self) -> i32 {
26        match self {
27            ErrorKind::Usage => 2,
28            ErrorKind::Validation => 3,
29            ErrorKind::Runtime => 4,
30            ErrorKind::Connectivity => 5,
31        }
32    }
33}
34
35/// Typed CLI command failure.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct CliError {
38    kind: ErrorKind,
39    code: &'static str,
40    message: String,
41}
42
43impl CliError {
44    /// Builds a usage-class error.
45    pub fn usage(code: &'static str, message: impl Into<String>) -> Self {
46        Self { kind: ErrorKind::Usage, code, message: message.into() }
47    }
48
49    /// Builds a validation-class error.
50    pub fn validation(code: &'static str, message: impl Into<String>) -> Self {
51        Self { kind: ErrorKind::Validation, code, message: message.into() }
52    }
53
54    /// Builds a runtime-class error.
55    pub fn runtime(code: &'static str, message: impl Into<String>) -> Self {
56        Self { kind: ErrorKind::Runtime, code, message: message.into() }
57    }
58
59    /// Builds a connectivity-class error.
60    pub fn connectivity(code: &'static str, message: impl Into<String>) -> Self {
61        Self { kind: ErrorKind::Connectivity, code, message: message.into() }
62    }
63
64    /// Returns failure classification.
65    pub const fn kind(&self) -> ErrorKind {
66        self.kind
67    }
68
69    /// Returns stable machine-readable error code.
70    pub const fn code(&self) -> &'static str {
71        self.code
72    }
73
74    /// Returns human-readable error message.
75    pub fn message(&self) -> &str {
76        &self.message
77    }
78}
79
80impl std::fmt::Display for CliError {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "{}: {}", self.code, self.message)
83    }
84}
85
86impl std::error::Error for CliError {}
87
88/// Structured stderr payload for deterministic automation-safe failures.
89#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
90pub struct ErrorPayload<'a> {
91    /// Stable error class taxonomy.
92    pub error_kind: ErrorKind,
93    /// Stable machine-readable failure code.
94    pub error_code: &'a str,
95    /// Human-readable detail string.
96    pub message: &'a str,
97}
98
99/// Command success output payload lane.
100#[derive(Debug, Clone, PartialEq)]
101#[must_use]
102pub enum CommandOutput {
103    /// Human-readable deterministic text output.
104    Text(String),
105    /// Machine-readable deterministic JSON output.
106    Json(serde_json::Value),
107}
108
109/// Returns current Unix timestamp seconds.
110pub fn now_unix_seconds() -> Result<u64, CliError> {
111    std::time::SystemTime::now()
112        .duration_since(std::time::UNIX_EPOCH)
113        .map_err(|err| CliError::runtime("runtime_clock_error", err.to_string()))
114        .map(|duration| duration.as_secs())
115}
116
117/// Resolves CLI data-root with deterministic default behavior.
118///
119/// When no override is provided, defaults to `$HOME/.actionqueue/data`. If the
120/// `HOME` environment variable is not set, falls back to `.actionqueue/data`
121/// relative to the current working directory.
122pub fn resolve_data_dir(override_dir: Option<&std::path::Path>) -> std::path::PathBuf {
123    override_dir.map(ToOwned::to_owned).unwrap_or_else(|| {
124        std::env::var("HOME")
125            .map(|h| std::path::PathBuf::from(h).join(".actionqueue/data"))
126            .unwrap_or_else(|_| std::path::PathBuf::from(".actionqueue/data"))
127    })
128}