1#[must_use = "a middleware result must be returned from `execute`"]
23pub struct MiddlewareResult<T> {
24 successful: bool,
25 error_message: Option<String>,
26 warnings: Vec<String>,
27 output: Option<T>,
28}
29
30impl<T: serde::Serialize> MiddlewareResult<T> {
31 pub fn ok(output: T) -> Self {
34 Self {
35 successful: true,
36 error_message: None,
37 warnings: Vec::new(),
38 output: Some(output),
39 }
40 }
41}
42
43impl<T> MiddlewareResult<T> {
44 pub fn failure(msg: impl Into<String>) -> Self {
46 Self {
47 successful: false,
48 error_message: Some(msg.into()),
49 warnings: Vec::new(),
50 output: None,
51 }
52 }
53
54 pub fn with_warning(mut self, msg: impl Into<String>) -> Self {
55 self.warnings.push(msg.into());
56 self
57 }
58
59 #[must_use]
61 pub fn is_success(&self) -> bool {
62 self.successful
63 }
64
65 #[must_use]
67 pub fn error_message(&self) -> Option<&str> {
68 self.error_message.as_deref()
69 }
70
71 #[must_use]
73 pub fn warnings(&self) -> &[String] {
74 &self.warnings
75 }
76}
77
78impl<T: serde::Serialize> MiddlewareResult<T> {
79 #[must_use]
84 pub fn into_wit(self) -> crate::bindings::MiddlewareResult {
85 let mut output: Vec<(String, String)> = Vec::new();
86 if let Some(value) = self.output {
87 match serde_json::to_value(&value) {
88 Ok(serde_json::Value::Object(map)) => {
89 for (k, v) in map {
90 output.push((k, v.to_string()));
91 }
92 }
93 Ok(serde_json::Value::Null) => {}
96 Ok(other) => {
97 return crate::bindings::MiddlewareResult {
98 successful: false,
99 error_message: Some(format!("middleware output must serialize to a JSON object, got {other}")),
100 warnings: self.warnings,
101 output: Vec::new(),
102 };
103 }
104 Err(e) => {
105 return crate::bindings::MiddlewareResult {
106 successful: false,
107 error_message: Some(format!("output serialization failed: {e}")),
108 warnings: self.warnings,
109 output: Vec::new(),
110 };
111 }
112 }
113 }
114 crate::bindings::MiddlewareResult {
115 successful: self.successful,
116 error_message: self.error_message,
117 warnings: self.warnings,
118 output,
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn ok_is_successful_no_warnings() {
129 let w = MiddlewareResult::ok(()).into_wit();
130 assert!(w.successful);
131 assert!(w.error_message.is_none());
132 assert!(w.warnings.is_empty());
133 assert!(w.output.is_empty());
134 }
135
136 #[test]
137 fn no_output_serializes_to_empty() {
138 let w = MiddlewareResult::ok(crate::NoOutput {}).into_wit();
139 assert!(w.successful);
140 assert!(w.output.is_empty());
141 }
142
143 #[test]
144 fn failure_carries_message_and_is_not_successful() {
145 let w = MiddlewareResult::<()>::failure("boom").into_wit();
146 assert!(!w.successful);
147 assert_eq!(w.error_message.as_deref(), Some("boom"));
148 }
149
150 #[test]
151 fn with_warning_is_chainable() {
152 let w = MiddlewareResult::ok(()).with_warning("careful").into_wit();
153 assert!(w.successful);
154 assert_eq!(w.warnings, vec!["careful".to_string()]);
155 }
156
157 #[test]
158 fn typed_output_spreads_fields_into_json_values() {
159 #[derive(serde::Serialize)]
160 struct Out {
161 tag: String,
162 count: u32,
163 }
164 let w = MiddlewareResult::ok(Out {
165 tag: "v1.2.3".to_string(),
166 count: 7,
167 })
168 .into_wit();
169 assert!(w.successful);
170 let map: std::collections::HashMap<_, _> = w.output.into_iter().collect();
172 assert_eq!(map["tag"], "\"v1.2.3\"");
173 assert_eq!(map["count"], "7");
174 }
175
176 #[test]
177 fn accessors_expose_outcome() {
178 let ok = MiddlewareResult::ok(()).with_warning("w");
179 assert!(ok.is_success());
180 assert_eq!(ok.error_message(), None);
181 assert_eq!(ok.warnings(), &["w".to_string()]);
182
183 let bad = MiddlewareResult::<()>::failure("boom");
184 assert!(!bad.is_success());
185 assert_eq!(bad.error_message(), Some("boom"));
186 assert!(bad.warnings().is_empty());
187 }
188
189 #[test]
190 fn output_serialize_error_degrades_to_failure() {
191 #[derive(serde::Serialize)]
194 struct Bad {
195 bad: std::collections::HashMap<(i32, i32), i32>,
196 }
197 let mut bad = std::collections::HashMap::new();
198 bad.insert((1, 2), 3);
199 let w = MiddlewareResult::ok(Bad { bad }).into_wit();
200 assert!(!w.successful);
201 assert!(
202 w.error_message.as_deref().unwrap().contains("serialization"),
203 "message should indicate a serialization failure; got {:?}",
204 w.error_message
205 );
206 }
207
208 #[test]
209 fn non_object_output_degrades_to_failure() {
210 let w = MiddlewareResult::ok(42).with_warning("kept").into_wit();
211 assert!(!w.successful);
212 assert_eq!(
213 w.error_message.as_deref(),
214 Some("middleware output must serialize to a JSON object, got 42")
215 );
216 assert_eq!(w.warnings, vec!["kept".to_string()]);
217 assert!(w.output.is_empty());
218 }
219}