1pub const SCHEMA_VERSION: u32 = 1;
5
6mod data {
8 #![expect(
9 clippy::disallowed_macros,
10 reason = "schemars' JsonSchema derive expands concat!"
11 )]
12
13 #[derive(Clone, Debug, Eq, PartialEq)]
15 #[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
16 #[cfg_attr(feature = "json", serde(tag = "status", rename_all = "snake_case"))]
17 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
18 pub enum Envelope<T> {
19 Ok {
21 schema_version: u32,
23 data: T,
25 },
26 Err {
28 schema_version: u32,
30 error: ErrorBody,
32 },
33 }
34
35 #[derive(Clone, Debug, Eq, PartialEq)]
37 #[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
38 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
39 pub struct ErrorBody {
40 pub bin: String,
42 pub message: String,
44 }
45}
46
47pub use data::{Envelope, ErrorBody};
48
49impl<T> Envelope<T> {
50 #[must_use]
52 pub fn ok(data: T) -> Self {
53 Self::Ok {
54 schema_version: SCHEMA_VERSION,
55 data,
56 }
57 }
58
59 #[must_use]
61 pub fn err(error: ErrorBody) -> Self {
62 Self::Err {
63 schema_version: SCHEMA_VERSION,
64 error,
65 }
66 }
67}
68
69impl ErrorBody {
70 #[must_use]
72 pub fn new(bin: impl Into<String>, message: impl Into<String>) -> Self {
73 Self {
74 bin: bin.into(),
75 message: message.into(),
76 }
77 }
78}
79
80#[cfg(all(test, feature = "json"))]
81mod tests {
82 use indoc::indoc;
83
84 use super::{Envelope, ErrorBody, SCHEMA_VERSION};
85
86 #[test]
87 fn ok_envelope_roundtrip() {
88 let env = Envelope::ok("demo");
89 let json = serde_json::to_string(&env).unwrap();
90 assert!(json.contains("\"schema_version\":1"));
91 let back: Envelope<String> = serde_json::from_str(&json).unwrap();
92 assert_eq!(back, Envelope::ok("demo".to_owned()));
93 assert_eq!(SCHEMA_VERSION, 1);
94 }
95
96 #[test]
97 fn err_envelope_shape() {
98 let env = Envelope::<()>::err(ErrorBody::new("toy", "missing token"));
99 let json = serde_json::to_string_pretty(&env).unwrap();
100 let expected = indoc! {r#"
101 {
102 "status": "err",
103 "schema_version": 1,
104 "error": {
105 "bin": "toy",
106 "message": "missing token"
107 }
108 }
109 "#};
110 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
111 let expect: serde_json::Value = serde_json::from_str(expected).unwrap();
112 assert_eq!(value, expect);
113 }
114}