gestalt/
invoke_support.rs1use serde_json::Value;
4
5use crate::rpc_support::GestaltError;
6
7#[derive(Clone, Debug, thiserror::Error)]
10#[error("{message}")]
11pub struct InvokeResultError {
12 pub app: String,
14 pub operation: String,
16 pub status: Option<u16>,
18 pub code: Option<String>,
20 pub message: String,
22 pub body: Option<Box<Value>>,
24 pub raw_body: Vec<u8>,
26}
27
28#[derive(Debug, thiserror::Error)]
31pub enum InvokeError {
32 #[error(transparent)]
34 Transport(#[from] GestaltError),
35 #[error(transparent)]
37 Result(#[from] Box<InvokeResultError>),
38}
39
40pub fn decode_app_result(
45 app: &str,
46 operation: &str,
47 status: i32,
48 body: &[u8],
49) -> Result<Value, Box<InvokeResultError>> {
50 let parsed = parse_json_result_body(body);
51 if status >= 400 {
52 return Err(http_status_error(app, operation, status, body, parsed));
53 }
54 let parsed = match parsed {
55 Ok(parsed) => parsed,
56 Err(_) => {
57 return Err(Box::new(InvokeResultError {
58 app: app.to_string(),
59 operation: operation.to_string(),
60 status: None,
61 code: None,
62 message: "app invoke response is not valid JSON".to_string(),
63 body: None,
64 raw_body: body.to_vec(),
65 }));
66 }
67 };
68 if let Some(status_value) = parsed.get("status").and_then(Value::as_str) {
69 if status_value == "error" {
70 let mut error = InvokeResultError {
71 app: app.to_string(),
72 operation: operation.to_string(),
73 status: None,
74 code: None,
75 message: "app invoke failed".to_string(),
76 body: None,
77 raw_body: body.to_vec(),
78 };
79 apply_invoke_error_fields(&mut error, &parsed);
80 error.body = Some(Box::new(parsed));
81 return Err(Box::new(error));
82 }
83 if status_value == "success" {
84 if let Some(data) = parsed.get("data") {
85 return Ok(data.clone());
86 }
87 }
88 }
89 Ok(parsed)
90}
91
92pub fn decode_graphql_result(
96 app: &str,
97 status: i32,
98 body: &[u8],
99) -> Result<Value, Box<InvokeResultError>> {
100 let decoded = decode_app_result(app, "graphql", status, body)?;
101 if let Ok(raw) = parse_json_result_body(body) {
102 graphql_errors(app, body, &raw)?;
103 }
104 graphql_errors(app, body, &decoded)?;
105 Ok(decoded)
106}
107
108pub fn is_success(status: i32) -> bool {
111 (200..=299).contains(&status)
112}
113
114pub fn error_for_status(
119 app: &str,
120 operation: &str,
121 status: i32,
122 body: &[u8],
123) -> Result<(), Box<InvokeResultError>> {
124 if status < 400 {
125 return Ok(());
126 }
127 Err(http_status_error(
128 app,
129 operation,
130 status,
131 body,
132 parse_json_result_body(body),
133 ))
134}
135
136fn http_status_error(
140 app: &str,
141 operation: &str,
142 status: i32,
143 body: &[u8],
144 parsed: Result<Value, serde_json::Error>,
145) -> Box<InvokeResultError> {
146 let mut error = InvokeResultError {
147 app: app.to_string(),
148 operation: operation.to_string(),
149 status: u16::try_from(status).ok(),
150 code: None,
151 message: format!("app invoke failed with status {status}"),
152 body: None,
153 raw_body: body.to_vec(),
154 };
155 if let Ok(parsed) = parsed {
156 apply_invoke_error_fields(&mut error, &parsed);
157 error.body = Some(Box::new(parsed));
158 }
159 Box::new(error)
160}
161
162fn parse_json_result_body(body: &[u8]) -> Result<Value, serde_json::Error> {
163 if body.iter().all(u8::is_ascii_whitespace) {
164 return Ok(serde_json::json!({}));
165 }
166 serde_json::from_slice(body)
167}
168
169fn graphql_errors(app: &str, raw_body: &[u8], value: &Value) -> Result<(), Box<InvokeResultError>> {
170 let Some(errors) = value.get("errors").and_then(Value::as_array) else {
171 return Ok(());
172 };
173 if errors.is_empty() {
174 return Ok(());
175 }
176 let message = errors
177 .first()
178 .and_then(|first| first.get("message"))
179 .and_then(Value::as_str)
180 .filter(|text| !text.trim().is_empty())
181 .unwrap_or("GraphQL returned errors")
182 .to_string();
183 Err(Box::new(InvokeResultError {
184 app: app.to_string(),
185 operation: "graphql".to_string(),
186 status: None,
187 code: Some("graphql_errors".to_string()),
188 message,
189 body: Some(Box::new(value.clone())),
190 raw_body: raw_body.to_vec(),
191 }))
192}
193
194fn apply_invoke_error_fields(error: &mut InvokeResultError, parsed: &Value) {
195 let nested = parsed.get("error");
196 let nested_message = nested
197 .and_then(|value| value.get("message"))
198 .and_then(Value::as_str)
199 .filter(|text| !text.trim().is_empty());
200 let nested_code = nested
201 .and_then(|value| value.get("code"))
202 .and_then(Value::as_str)
203 .filter(|text| !text.trim().is_empty());
204 let top_message = parsed
205 .get("message")
206 .and_then(Value::as_str)
207 .filter(|text| !text.trim().is_empty());
208 let top_code = parsed
209 .get("code")
210 .and_then(Value::as_str)
211 .filter(|text| !text.trim().is_empty());
212 if let Some(message) = nested_message.or(top_message) {
213 error.message = message.to_string();
214 }
215 if let Some(code) = nested_code.or(top_code) {
216 error.code = Some(code.to_string());
217 }
218}