Skip to main content

rustev_surface/
out.rs

1//! Command output (spec 006, 3.1.2 and 3.2): one sorted, compact JSON object
2//! per command, with library records embedded as their record canonical
3//! bytes, and the exit code its status maps to.
4
5use std::collections::BTreeMap;
6
7use serde::Serialize;
8use serde_json::value::RawValue;
9
10use crate::host::IoFail;
11
12/// Exit codes (spec 006, 3.2).
13pub mod code {
14    pub const OK: u8 = 0;
15    pub const USAGE: u8 = 2;
16    pub const IO: u8 = 3;
17    pub const INVALID_INPUT: u8 = 4;
18    pub const REFUSED: u8 = 5;
19    pub const REJECTED: u8 = 6;
20    pub const CANCELLED: u8 = 7;
21    pub const EVIDENCE_NOT_DELIVERED: u8 = 8;
22    pub const DIVERGED: u8 = 9;
23    pub const INCOMPARABLE: u8 = 10;
24    pub const FAIL: u8 = 11;
25    pub const UNKNOWN: u8 = 12;
26}
27
28/// The stdout object of one command.
29#[derive(Debug, Clone)]
30pub struct Out(BTreeMap<String, Box<RawValue>>);
31
32fn raw_json<T: Serialize + ?Sized>(v: &T) -> Box<RawValue> {
33    // Every value the CLI prints serializes; a failure is a defect.
34    serde_json::value::to_raw_value(v).expect("output value serializes")
35}
36
37impl Out {
38    pub fn new(command: &str, status: &str) -> Self {
39        Out(BTreeMap::new())
40            .set("command", command)
41            .set("status", status)
42    }
43
44    pub fn set<T: Serialize + ?Sized>(mut self, key: &str, value: &T) -> Self {
45        self.0.insert(key.into(), raw_json(value));
46        self
47    }
48
49    /// Embed bytes that are already JSON (record canonical documents)
50    /// without re-serializing them.
51    pub fn raw(mut self, key: &str, bytes: Vec<u8>) -> Self {
52        let value = String::from_utf8(bytes)
53            .ok()
54            .and_then(|s| RawValue::from_string(s).ok())
55            .expect("record canonical bytes are JSON");
56        self.0.insert(key.into(), value);
57        self
58    }
59
60    pub fn status(&self) -> &str {
61        self.0
62            .get("status")
63            .map_or("", |v| v.get().trim_matches('"'))
64    }
65
66    pub fn bytes(&self) -> Vec<u8> {
67        let mut b = serde_json::to_vec(&self.0).expect("output serializes");
68        b.push(b'\n');
69        b
70    }
71}
72
73/// A finished command: its exit code and its output.
74#[derive(Debug, Clone)]
75pub struct Done {
76    pub code: u8,
77    pub out: Out,
78}
79
80/// Command results: `Err` carries a finished failure so `?` can stop early.
81pub type Res = Result<Done, Done>;
82
83impl Done {
84    pub fn new(code: u8, out: Out) -> Self {
85        Done { code, out }
86    }
87
88    pub fn ok(out: Out) -> Self {
89        Done::new(code::OK, out)
90    }
91
92    pub fn io(command: &str, e: IoFail) -> Self {
93        Done::new(
94            code::IO,
95            Out::new(command, "io_error")
96                .set("path", &e.path)
97                .set("detail", &e.detail),
98        )
99    }
100
101    /// A supplied document other than a definition or plan was refused.
102    pub fn invalid(command: &str, input: &str, detail: impl Into<String>) -> Self {
103        Done::new(
104            code::INVALID_INPUT,
105            Out::new(command, "invalid_input")
106                .set("input", input)
107                .set("detail", &detail.into()),
108        )
109    }
110}