1use serde_json::{Value, json};
4
5#[must_use]
7#[allow(clippy::needless_pass_by_value)]
8pub fn ok_response(command: &str, data: Value) -> Value {
9 json!({
10 "ok": true,
11 "command": command,
12 "data": data
13 })
14}
15
16#[must_use]
18#[allow(clippy::needless_pass_by_value)]
19pub fn err_response(command: &str, code: &str, message: &str, details: Value) -> Value {
20 json!({
21 "ok": false,
22 "command": command,
23 "error": {
24 "code": code,
25 "message": message,
26 "details": details
27 }
28 })
29}
30
31#[must_use]
33#[allow(clippy::needless_pass_by_value)]
34pub fn render_response(
35 command: &str,
36 json_output: bool,
37 data: Value,
38 text: impl Into<String>,
39) -> String {
40 render_response_parts(command, json_output, || data, || text.into())
41}
42
43#[must_use]
45#[allow(clippy::needless_pass_by_value)]
46pub fn render_response_with<F>(command: &str, json_output: bool, data: Value, text: F) -> String
47where
48 F: FnOnce() -> String,
49{
50 render_response_parts(command, json_output, || data, text)
51}
52
53#[must_use]
55#[allow(clippy::needless_pass_by_value)]
56pub fn render_response_parts<D, T>(command: &str, json_output: bool, data: D, text: T) -> String
57where
58 D: FnOnce() -> Value,
59 T: FnOnce() -> String,
60{
61 if json_output {
62 ok_response(command, data()).to_string()
63 } else {
64 text()
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use std::cell::Cell;
71
72 use serde_json::json;
73
74 use super::*;
75
76 #[test]
77 fn ok_response_contains_expected_shape() {
78 let value = ok_response("list", json!({ "x": 1 }));
79 assert_eq!(value["ok"], json!(true));
80 assert_eq!(value["command"], json!("list"));
81 assert_eq!(value["data"]["x"], json!(1));
82 }
83
84 #[test]
85 fn err_response_contains_expected_shape() {
86 let value = err_response("list", "ERROR", "bad", json!({}));
87 assert_eq!(value["ok"], json!(false));
88 assert_eq!(value["error"]["code"], json!("ERROR"));
89 assert_eq!(value["error"]["message"], json!("bad"));
90 }
91
92 #[test]
93 fn render_response_uses_json_envelope_when_requested() {
94 let value = render_response("list", true, json!({"x": 1}), "text");
95 assert!(value.contains("\"ok\":true"));
96 }
97
98 #[test]
99 fn render_response_with_skips_text_builder_for_json_output() {
100 let called = Cell::new(false);
101 let value = render_response_with("list", true, json!({"x": 1}), || {
102 called.set(true);
103 String::from("text")
104 });
105
106 assert!(value.contains("\"ok\":true"));
107 assert!(!called.get());
108 }
109
110 #[test]
111 fn render_response_with_builds_text_for_text_output() {
112 let value = render_response_with("list", false, json!({"x": 1}), || String::from("text"));
113 assert_eq!(value, "text");
114 }
115
116 #[test]
117 fn render_response_parts_skips_text_builder_for_json_output() {
118 let called = Cell::new(false);
119 let value = render_response_parts(
120 "list",
121 true,
122 || json!({"x": 1}),
123 || {
124 called.set(true);
125 String::from("text")
126 },
127 );
128
129 assert!(value.contains("\"ok\":true"));
130 assert!(!called.get());
131 }
132
133 #[test]
134 fn render_response_parts_skips_data_builder_for_text_output() {
135 let called = Cell::new(false);
136 let value = render_response_parts(
137 "list",
138 false,
139 || {
140 called.set(true);
141 json!({"x": 1})
142 },
143 || String::from("text"),
144 );
145
146 assert_eq!(value, "text");
147 assert!(!called.get());
148 }
149}