Skip to main content

bijux_cli/contracts/
envelope.rs

1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use super::command::CommandPath;
8
9/// Stable output envelope metadata.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
11pub struct OutputEnvelopeMetaV1 {
12    /// Envelope version identifier.
13    pub version: String,
14    /// Canonical command path.
15    pub command: CommandPath,
16    /// RFC3339 timestamp.
17    pub timestamp: String,
18}
19
20impl OutputEnvelopeMetaV1 {
21    /// Build metadata with required fields.
22    pub fn new(version: &str, command: CommandPath, timestamp: &str) -> Result<Self, String> {
23        if version.trim().is_empty() {
24            return Err("meta.version cannot be empty".to_string());
25        }
26        if timestamp.trim().is_empty() {
27            return Err("meta.timestamp cannot be empty".to_string());
28        }
29        Ok(Self { version: version.to_string(), command, timestamp: timestamp.to_string() })
30    }
31}
32
33/// Stable success payload envelope.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
35pub struct OutputEnvelopeV1 {
36    /// Fixed status marker.
37    pub status: String,
38    /// Command-specific payload.
39    pub data: Value,
40    /// Shared metadata.
41    pub meta: OutputEnvelopeMetaV1,
42}
43
44impl OutputEnvelopeV1 {
45    /// Build a success envelope using fixed status.
46    #[must_use]
47    pub fn success(data: Value, meta: OutputEnvelopeMetaV1) -> Self {
48        Self { status: "ok".to_string(), data, meta }
49    }
50}
51
52/// Stable structured error details.
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
54pub struct ErrorDetailsV1 {
55    /// Stable machine failure identifier.
56    pub failure: Option<String>,
57    /// Arbitrary additional context.
58    #[serde(default)]
59    pub context: BTreeMap<String, Value>,
60}
61
62/// Stable structured error payload.
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
64pub struct ErrorPayloadV1 {
65    /// Stable symbolic error code.
66    pub code: String,
67    /// Human-readable message.
68    pub message: String,
69    /// Error category (`usage`, `validation`, `plugin`, `internal`).
70    pub category: String,
71    /// Structured optional details.
72    #[serde(default)]
73    pub details: Option<ErrorDetailsV1>,
74}
75
76impl ErrorPayloadV1 {
77    /// Build a validated error payload.
78    pub fn new(code: &str, message: &str, category: &str) -> Result<Self, String> {
79        if code.trim().is_empty() {
80            return Err("error.code cannot be empty".to_string());
81        }
82        if message.trim().is_empty() {
83            return Err("error.message cannot be empty".to_string());
84        }
85        if category.trim().is_empty() {
86            return Err("error.category cannot be empty".to_string());
87        }
88        Ok(Self {
89            code: code.to_string(),
90            message: message.to_string(),
91            category: category.to_string(),
92            details: None,
93        })
94    }
95}
96
97/// Stable error envelope.
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
99pub struct ErrorEnvelopeV1 {
100    /// Fixed status marker.
101    pub status: String,
102    /// Structured error payload.
103    pub error: ErrorPayloadV1,
104    /// Shared metadata.
105    pub meta: OutputEnvelopeMetaV1,
106}
107
108impl ErrorEnvelopeV1 {
109    /// Build an error envelope using fixed status.
110    #[must_use]
111    pub fn failure(error: ErrorPayloadV1, meta: OutputEnvelopeMetaV1) -> Self {
112        Self { status: "error".to_string(), error, meta }
113    }
114}