1use serde_json::Value;
4
5use crate::rpc_support::{GestaltError, gestalt_error_code, http_status_to_gestalt_code};
6
7#[derive(Clone, Debug, thiserror::Error)]
12#[error("{message}")]
13pub struct InvokeResultError {
14 pub app: String,
16 pub operation: String,
18 pub status: Option<u16>,
20 pub reason: Option<String>,
22 pub message: String,
24 pub body: Option<Box<Value>>,
26 pub raw_body: Vec<u8>,
28}
29
30impl InvokeResultError {
31 pub fn gestalt_code(&self) -> i32 {
33 if let Some(status) = self.status {
34 return http_status_to_gestalt_code(status as i32);
35 }
36 if self.reason.as_deref() == Some("graphql_errors")
37 || self.message == "app invoke response is not valid JSON"
38 || self.message == "operation result body is not valid JSON"
39 {
40 return gestalt_error_code::INTERNAL;
41 }
42 gestalt_error_code::UNKNOWN
43 }
44}
45
46#[derive(Debug, thiserror::Error)]
49pub enum InvokeError {
50 #[error(transparent)]
52 Transport(#[from] GestaltError),
53 #[error(transparent)]
55 Result(#[from] Box<InvokeResultError>),
56}
57
58pub fn decode_app_result(
63 app: &str,
64 operation: &str,
65 status: i32,
66 body: &[u8],
67) -> Result<Value, Box<InvokeResultError>> {
68 let parsed = parse_json_result_body(body);
69 if !is_success(status) {
70 return Err(http_status_error(app, operation, status, body, parsed));
71 }
72 let parsed = match parsed {
73 Ok(parsed) => parsed,
74 Err(_) => {
75 return Err(Box::new(InvokeResultError {
76 app: app.to_string(),
77 operation: operation.to_string(),
78 status: None,
79 reason: None,
80 message: "app invoke response is not valid JSON".to_string(),
81 body: None,
82 raw_body: body.to_vec(),
83 }));
84 }
85 };
86 if let Some(status_value) = parsed.get("status").and_then(Value::as_str) {
87 if status_value == "error" {
88 let mut error = InvokeResultError {
89 app: app.to_string(),
90 operation: operation.to_string(),
91 status: None,
92 reason: None,
93 message: "app invoke failed".to_string(),
94 body: None,
95 raw_body: body.to_vec(),
96 };
97 apply_invoke_error_fields(&mut error, &parsed);
98 error.body = Some(Box::new(parsed));
99 return Err(Box::new(error));
100 }
101 if status_value == "success" {
102 if let Some(data) = parsed.get("data") {
103 return Ok(data.clone());
104 }
105 }
106 }
107 Ok(parsed)
108}
109
110pub fn decode_graphql_result(
114 app: &str,
115 status: i32,
116 body: &[u8],
117) -> Result<Value, Box<InvokeResultError>> {
118 let decoded = decode_app_result(app, "graphql", status, body)?;
119 if let Ok(raw) = parse_json_result_body(body) {
120 graphql_errors(app, body, &raw)?;
121 }
122 graphql_errors(app, body, &decoded)?;
123 Ok(decoded)
124}
125
126pub fn is_success(status: i32) -> bool {
129 (200..=299).contains(&status)
130}
131
132pub fn error_for_status(
137 app: &str,
138 operation: &str,
139 status: i32,
140 body: &[u8],
141) -> Result<(), Box<InvokeResultError>> {
142 if is_success(status) {
143 return Ok(());
144 }
145 Err(http_status_error(
146 app,
147 operation,
148 status,
149 body,
150 parse_json_result_body(body),
151 ))
152}
153
154fn http_status_error(
158 app: &str,
159 operation: &str,
160 status: i32,
161 body: &[u8],
162 parsed: Result<Value, serde_json::Error>,
163) -> Box<InvokeResultError> {
164 let mut error = InvokeResultError {
165 app: app.to_string(),
166 operation: operation.to_string(),
167 status: u16::try_from(status).ok(),
168 reason: None,
169 message: format!("app invoke failed with status {status}"),
170 body: None,
171 raw_body: body.to_vec(),
172 };
173 if let Ok(parsed) = parsed {
174 apply_invoke_error_fields(&mut error, &parsed);
175 error.body = Some(Box::new(parsed));
176 }
177 Box::new(error)
178}
179
180fn parse_json_result_body(body: &[u8]) -> Result<Value, serde_json::Error> {
181 if body.iter().all(u8::is_ascii_whitespace) {
182 return Ok(serde_json::json!({}));
183 }
184 serde_json::from_slice(body)
185}
186
187fn graphql_errors(app: &str, raw_body: &[u8], value: &Value) -> Result<(), Box<InvokeResultError>> {
188 let Some(errors) = value.get("errors").and_then(Value::as_array) else {
189 return Ok(());
190 };
191 if errors.is_empty() {
192 return Ok(());
193 }
194 let message = errors
195 .first()
196 .and_then(|first| first.get("message"))
197 .and_then(Value::as_str)
198 .filter(|text| !text.trim().is_empty())
199 .unwrap_or("GraphQL returned errors")
200 .to_string();
201 Err(Box::new(InvokeResultError {
202 app: app.to_string(),
203 operation: "graphql".to_string(),
204 status: None,
205 reason: Some("graphql_errors".to_string()),
206 message,
207 body: Some(Box::new(value.clone())),
208 raw_body: raw_body.to_vec(),
209 }))
210}
211
212fn apply_invoke_error_fields(error: &mut InvokeResultError, parsed: &Value) {
213 let nested = parsed.get("error");
214 let nested_message = nested
215 .and_then(|value| value.get("message"))
216 .and_then(Value::as_str)
217 .filter(|text| !text.trim().is_empty());
218 let nested_reason = nested
219 .and_then(|value| value.get("code"))
220 .and_then(Value::as_str)
221 .filter(|text| !text.trim().is_empty());
222 let top_message = parsed
223 .get("message")
224 .and_then(Value::as_str)
225 .filter(|text| !text.trim().is_empty());
226 let top_reason = parsed
227 .get("code")
228 .and_then(Value::as_str)
229 .filter(|text| !text.trim().is_empty());
230 if let Some(message) = nested_message.or(top_message) {
231 error.message = message.to_string();
232 }
233 if let Some(reason) = nested_reason.or(top_reason) {
234 error.reason = Some(reason.to_string());
235 }
236}