1use eyre::Result;
2use serde::{Deserialize, Serialize};
3use serde_json::value::Value;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize)]
20#[serde(rename_all = "camelCase")]
21pub struct GqlRequest {
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub operation_name: Option<String>,
24 #[serde(skip_serializing_if = "HashMap::is_empty")]
25 pub variables: HashMap<String, Value>,
26 pub query: String,
27}
28
29impl GqlRequest {
30 pub fn new(query: &str) -> Self {
32 GqlRequest {
33 operation_name: None,
34 variables: HashMap::new(),
35 query: query.to_string(),
36 }
37 }
38
39 pub fn new_with_variable<T: Serialize>(query: &str, variable: &str, object: &T) -> Self {
46 GqlRequest {
47 operation_name: None,
48 variables: [(variable.to_string(), serde_json::json!(object))]
49 .iter()
50 .cloned()
51 .collect(),
52 query: query.to_string(),
53 }
54 }
55
56 pub fn new_with_op(operation_name: &str, query: &str) -> Self {
62 GqlRequest {
63 operation_name: Some(operation_name.to_string()),
64 variables: HashMap::new(),
65 query: query.to_string(),
66 }
67 }
68 pub fn add_variable<T: Serialize>(&mut self, name: &str, object: &T) -> Result<()> {
69 if self.operation_name.is_none() && !self.variables.is_empty() {
70 Err(eyre::eyre!(
71 "Not possible to add variable when using anonymous query/mutation"
72 ))
73 } else {
74 let json = serde_json::json!(object);
75 self.variables.insert(name.to_string(), json);
76 Ok(())
77 }
78 }
79}
80
81#[derive(Debug, Deserialize)]
82pub struct GqlResponse<T> {
83 pub data: Option<T>,
84 pub errors: Option<Vec<ErrorMsg>>,
85}
86
87#[derive(Debug, Deserialize)]
88pub struct ErrorMsg {
89 pub message: String,
90 pub locations: Vec<Location>,
91 pub path: Option<Vec<Value>>,
92 pub extensions: Option<Value>,
93}
94
95#[derive(Debug, Deserialize)]
96pub struct Location {
97 pub line: i32,
98 pub column: i32,
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn variable_add_test() {
107 #[derive(Serialize)]
108 struct TestQuery {
109 pub title: String,
110 }
111 let test = TestQuery {
112 title: "Rocket Engineering".to_string(),
113 };
114
115 let mut request = GqlRequest::new_with_variable("", "test", &test);
116 assert!(request.add_variable("test", &test).is_err())
117 }
118
119 #[test]
120 fn empty_variables_test() {
121 let query = "{ apiVersion }";
122 let expected_body = serde_json::json!({
123 "query": query,
124 });
125
126 let request = GqlRequest::new("{ apiVersion }");
127 let request = serde_json::json!(&request);
128 assert_eq!(request, expected_body);
129 }
130
131 #[test]
132 fn request_test() {
133 #[derive(Serialize)]
134 struct TestQuery {
135 pub title: String,
136 }
137 let exp_data = r#"
138 {
139 "operationName": "createBook",
140 "variables": {
141 "book": {
142 "title": "Rocket Engineering"
143 }
144 },
145 "query": "mutation createBook($book: createBook!) { createBook(book: $book) { title }}"
146 }
147 "#;
148
149 let test_query = TestQuery {
150 title: "Rocket Engineering".to_string(),
151 };
152 let op_name = "createBook";
153 let query = "mutation createBook($book: createBook!) { createBook(book: $book) { title }}";
154
155 let mut gql_request = GqlRequest::new_with_op(op_name, query);
156 gql_request.add_variable("book", &test_query).unwrap();
157
158 let request = serde_json::json!(gql_request);
159 let expected: serde_json::Value = serde_json::from_str(exp_data).unwrap();
160
161 assert_eq!(request["operationName"], expected["operationName"]);
162 assert_eq!(request, expected);
163 }
164
165 #[test]
166 fn request_anonymous_test() {
167 #[derive(Serialize)]
168 struct TestQuery {
169 pub title: String,
170 }
171 let exp_data = r#"
172 {
173 "variables": {
174 "book": {
175 "title": "Rocket Engineering"
176 }
177 },
178 "query": "mutation ($book: createBook!) { createBook(book: $book) { title }}"
179 }
180 "#;
181
182 let test_query = TestQuery {
183 title: "Rocket Engineering".to_string(),
184 };
185 let query = "mutation ($book: createBook!) { createBook(book: $book) { title }}";
186 let gql_request = GqlRequest::new_with_variable(query, "book", &test_query);
187
188 let request = serde_json::json!(gql_request);
189 let expected: serde_json::Value = serde_json::from_str(exp_data).unwrap();
190
191 assert_eq!(request, expected);
192 }
193
194 #[test]
195 fn response_test() {
196 let expected = r#"{"data":{"sensor":{"createdAt":"2020-09-15T07:08:54.668686+00:00","id":"59de6057-e913-45e3-95b1-e628741443fd","location":null,"macaddress":"DC:A6:32:0B:62:37","name":"unnamed-59de6057-e913-45e3-95b1-e628741443fd","updatedAt":"2020-09-15T07:08:54.668686+00:00"}}}"#;
197
198 #[derive(Debug, Deserialize)]
199 #[serde(rename_all = "camelCase")]
200 pub struct Sensor {
201 pub name: String,
202 pub location: Option<String>,
203 pub macaddress: String,
204 pub created_at: String,
205 pub updated_at: String,
206 }
207
208 #[derive(Debug, Deserialize)]
209 #[serde(rename_all = "camelCase")]
210 pub struct SensorData {
211 pub sensor: Sensor,
212 }
213
214 let response: GqlResponse<SensorData> = serde_json::from_str(expected).unwrap();
215
216 let data = response.data.unwrap();
217
218 assert_eq!(
219 data.sensor.name,
220 "unnamed-59de6057-e913-45e3-95b1-e628741443fd"
221 );
222 }
223
224 #[test]
226 fn error_response_ext_test() {
227 let expected = r#"{ "errors": [ { "message": "Cannot query field \"named\" on type \"Country\". Did you mean \"name\"?", "locations": [ { "line": 34, "column": 5 } ], "extensions": { "code": "GRAPHQL_VALIDATION_FAILED" } } ] }"#;
228
229 #[derive(Debug, Deserialize)]
230 #[serde(rename_all = "camelCase")]
231 struct Country {
232 #[allow(dead_code)]
233 name: String,
234 }
235
236 let response: GqlResponse<Country> = serde_json::from_str(expected).unwrap();
237
238 assert!(response.data.is_none());
239 assert!(response.errors.is_some());
240
241 let errors = response.errors.unwrap();
242
243 assert_eq!(errors.len(), 1);
244
245 let error = errors.first().unwrap();
246 assert_eq!(
247 error.message,
248 r#"Cannot query field "named" on type "Country". Did you mean "name"?"#
249 );
250 assert_eq!(error.locations.len(), 1);
251 let location = error.locations.first().unwrap();
252 assert_eq!(location.line, 34);
253 assert_eq!(location.column, 5);
254 }
255
256 #[test]
258 fn error_response_path_test() {
259 let expected = r#"{ "data": null, "errors": [ { "message": "Failed to parse \"UUID\": invalid length: expected one of [36, 32], found 7", "locations": [ { "line": 2, "column": 14 } ], "path": [ "sensor" ] } ] }"#;
260
261 #[derive(Debug, Deserialize)]
262 #[serde(rename_all = "camelCase")]
263 struct Country {
264 #[allow(dead_code)]
265 name: String,
266 }
267
268 let response: GqlResponse<Country> = serde_json::from_str(expected).unwrap();
269
270 assert!(response.data.is_none());
271 assert!(response.errors.is_some());
272
273 let errors = response.errors.unwrap();
274
275 assert_eq!(errors.len(), 1);
276
277 let error = errors.first().unwrap();
278 assert_eq!(
279 error.message,
280 r#"Failed to parse "UUID": invalid length: expected one of [36, 32], found 7"#
281 );
282 assert_eq!(error.locations.len(), 1);
283 let location = error.locations.first().unwrap();
284 assert_eq!(location.line, 2);
285 assert_eq!(location.column, 14);
286
287 assert!(error.path.is_some());
288 }
289}