conversation_api/execution/
facade_response.rs1use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum ResponseDetail {
8 #[default]
9 Full,
10 Essential,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum FacadeStatus {
16 Success,
17 Partial,
18 Error,
19 Cancelled,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct FacadeProblem {
25 pub code: String,
26 pub message: String,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub details: Option<Value>,
29}
30
31#[derive(Clone, Debug, PartialEq, Serialize)]
35#[serde(tag = "status", rename_all = "snake_case")]
36pub enum FacadeResponse {
37 Success {
38 #[serde(skip_serializing_if = "Option::is_none")]
39 data: Option<Value>,
40 #[serde(skip_serializing_if = "Vec::is_empty")]
41 warnings: Vec<FacadeProblem>,
42 },
43 Partial {
44 data: Value,
45 #[serde(skip_serializing_if = "Vec::is_empty")]
46 warnings: Vec<FacadeProblem>,
47 },
48 Error {
49 error: FacadeProblem,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 data: Option<String>,
52 },
53 Cancelled {
54 #[serde(skip_serializing_if = "Vec::is_empty")]
55 warnings: Vec<FacadeProblem>,
56 },
57}
58
59impl FacadeResponse {
60 pub fn acknowledged() -> Self {
62 Self::Success {
63 data: None,
64 warnings: vec![],
65 }
66 }
67 pub fn cancelled() -> Self {
68 Self::Cancelled { warnings: vec![] }
69 }
70 pub fn success(data: Value) -> Self {
71 Self::Success {
72 data: Some(data),
73 warnings: vec![],
74 }
75 }
76 pub fn partial(data: Value) -> Self {
77 Self::Partial {
78 data,
79 warnings: vec![],
80 }
81 }
82 pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
83 Self::Error {
84 error: FacadeProblem {
85 code: code.into(),
86 message: message.into(),
87 details: None,
88 },
89 data: None,
90 }
91 }
92 pub fn with_details(mut self, details: Value) -> Self {
93 if let Self::Error { error, .. } = &mut self {
94 error.details = Some(details);
95 }
96 self
97 }
98 pub fn status(&self) -> FacadeStatus {
99 match self {
100 Self::Success { .. } => FacadeStatus::Success,
101 Self::Partial { .. } => FacadeStatus::Partial,
102 Self::Error { .. } => FacadeStatus::Error,
103 Self::Cancelled { .. } => FacadeStatus::Cancelled,
104 }
105 }
106 pub fn is_error(&self) -> bool {
107 matches!(self, Self::Partial { .. } | Self::Error { .. })
108 }
109 pub fn data(&self) -> Option<&Value> {
110 match self {
111 Self::Success { data, .. } => data.as_ref(),
112 Self::Partial { data, .. } => Some(data),
113 _ => None,
114 }
115 }
116 pub fn data_mut(&mut self) -> Option<&mut Value> {
117 match self {
118 Self::Success { data, .. } => data.as_mut(),
119 Self::Partial { data, .. } => Some(data),
120 _ => None,
121 }
122 }
123 pub fn with_essential_data(self, text: String) -> Self {
127 match self {
128 Self::Success {
129 data: Some(_),
130 warnings,
131 } => Self::Success {
132 data: Some(Value::String(text)),
133 warnings,
134 },
135 Self::Partial { warnings, .. } => Self::Partial {
136 data: Value::String(text),
137 warnings,
138 },
139 Self::Error { mut error, .. } => {
140 error.details = None;
141 Self::Error {
142 error,
143 data: Some(text),
144 }
145 }
146 terminal => terminal,
147 }
148 }
149 pub fn with_warning(mut self, warning: FacadeProblem) -> Self {
150 match &mut self {
151 Self::Success { warnings, .. }
152 | Self::Partial { warnings, .. }
153 | Self::Cancelled { warnings } => warnings.push(warning),
154 _ => {}
155 }
156 self
157 }
158}
159
160impl<'de> Deserialize<'de> for FacadeResponse {
161 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
162 use serde::de::Error;
163 let value = Value::deserialize(deserializer)?;
164 let mut fields = value
165 .as_object()
166 .cloned()
167 .ok_or_else(|| D::Error::custom("Facade response must be an object"))?;
168 let status = fields
169 .remove("status")
170 .ok_or_else(|| D::Error::custom("Missing Facade status"))?;
171 let response = match status.as_str() {
172 Some("success" | "partial") => {
173 let data = fields.remove("data");
174 if status == "partial" && data.is_none() {
175 return Err(D::Error::custom("Partial outcome requires data"));
176 }
177 let warnings = fields
178 .remove("warnings")
179 .map(serde_json::from_value)
180 .transpose()
181 .map_err(D::Error::custom)?
182 .unwrap_or_default();
183 if status == "success" {
184 Self::Success { data, warnings }
185 } else {
186 Self::Partial {
187 data: data.expect("partial data checked"),
188 warnings,
189 }
190 }
191 }
192 Some("error") => Self::Error {
193 data: fields
194 .remove("data")
195 .map(serde_json::from_value)
196 .transpose()
197 .map_err(D::Error::custom)?,
198 error: serde_json::from_value(
199 fields
200 .remove("error")
201 .ok_or_else(|| D::Error::custom("Missing Facade error"))?,
202 )
203 .map_err(D::Error::custom)?,
204 },
205 Some("cancelled") => Self::Cancelled {
206 warnings: fields
207 .remove("warnings")
208 .map(serde_json::from_value)
209 .transpose()
210 .map_err(D::Error::custom)?
211 .unwrap_or_default(),
212 },
213 _ => return Err(D::Error::custom("Unknown Facade status")),
214 };
215 if !fields.is_empty() {
216 return Err(D::Error::custom("Unknown Facade response field"));
217 }
218 Ok(response)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use serde_json::json;
226 #[test]
227 fn strict_response_roundtrip() {
228 for value in [
229 json!({"status":"success","data":null}),
230 json!({"status":"success","data":"items: []"}),
231 json!({"status":"success"}),
232 json!({"status":"success","warnings":[{"code":"SYNC_PENDING","message":"Saved"}]}),
233 json!({"status":"partial","data":{"done":["a"],"failed":["b"]}}),
234 json!({"status":"error","error":{"code":"STALE","message":"Changed"}}),
235 json!({"status":"error","error":{"code":"BAD_REQUEST","message":"Invalid"},"data":"status: error\n"}),
236 json!({"status":"cancelled"}),
237 ] {
238 let parsed: FacadeResponse = serde_json::from_value(value.clone()).unwrap();
239 assert_eq!(serde_json::to_value(parsed).unwrap(), value);
240 }
241 for value in [
242 json!({"status":"partial"}),
243 json!({"status":"success","text":"legacy"}),
244 json!({"status":"success","data":{},"text":"bad"}),
245 json!({"status":"cancelled","data":{}}),
246 json!({"status":"error","error":{"message":"bad"}}),
247 json!({"status":"error","error":{"code":"BAD","message":"Invalid"},"data":{}}),
248 json!({"status":"error","error":{"code":"BAD","message":"Invalid"},"text":"legacy"}),
249 json!({"status":"done","data":{}}),
250 ] {
251 assert!(serde_json::from_value::<FacadeResponse>(value).is_err());
252 }
253 }
254 #[test]
255 fn resource_failure_is_not_invocation_failure() {
256 let result = FacadeResponse::success(json!({"status":"failed","success":false}));
257 assert!(!result.is_error());
258 assert_eq!(
259 result.with_essential_data("state: failed".into()).status(),
260 FacadeStatus::Success
261 );
262 }
263}