1use crate::objects::{MajorMinor, ObjectKind};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9#[serde(deny_unknown_fields)]
10#[non_exhaustive]
11pub struct Index {
12 pub cmake: CMake,
14
15 pub objects: Vec<ReplyFileReference>,
17
18 pub reply: HashMap<String, ReplyField>,
20}
21
22#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24#[serde(deny_unknown_fields)]
25#[non_exhaustive]
26pub struct CMake {
27 pub version: CMakeVersion,
28 pub paths: CMakePaths,
29 pub generator: CMakeGenerator,
30}
31
32#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35#[serde(deny_unknown_fields)]
36#[non_exhaustive]
37pub struct CMakeVersion {
38 pub major: i32,
40
41 pub minor: i32,
43
44 pub patch: i32,
46
47 pub suffix: String,
49
50 pub string: String,
52
53 pub is_dirty: bool,
55}
56
57#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60#[serde(deny_unknown_fields)]
61#[non_exhaustive]
62pub struct CMakePaths {
63 pub cmake: PathBuf,
65
66 pub ctest: PathBuf,
68
69 pub cpack: PathBuf,
71
72 pub root: PathBuf,
74}
75
76#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79#[serde(deny_unknown_fields)]
80#[non_exhaustive]
81pub struct CMakeGenerator {
82 pub multi_config: bool,
84
85 pub name: String,
87
88 pub platform: Option<String>,
90}
91
92#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95#[serde(deny_unknown_fields)]
96#[non_exhaustive]
97pub struct ReplyFileReference {
98 pub kind: ObjectKind,
100
101 pub version: MajorMinor,
103
104 pub json_file: PathBuf,
106}
107
108#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110#[non_exhaustive]
111pub struct Error {
112 pub error: String,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[serde(untagged)]
117#[non_exhaustive]
118pub enum ClientField {
119 Error(Error),
120 ReplyFileReference(ReplyFileReference),
121 QueryJson(QueryJson),
122}
123
124#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
125#[serde(untagged)]
126#[non_exhaustive]
127pub enum ReplyField {
128 Error(Error),
129 ReplyFileReference(ReplyFileReference),
130 Client(HashMap<String, ClientField>),
131 #[default]
132 Unknown,
133}
134
135#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
136#[serde(deny_unknown_fields)]
137#[non_exhaustive]
138pub struct QueryJson {
139 pub client: Option<Value>,
140 pub requests: Option<Value>,
141 pub responses: Option<Value>,
142}
143
144#[cfg(test)]
145mod testing {
146 use super::*;
147 use serde_json::json;
148 use std::collections::HashMap;
149
150 #[test]
151 fn test_cmake() {
152 let json = json!({
153 "generator" :
154 {
155 "multiConfig" : true,
156 "name" : "Visual Studio 16 2019",
157 "platform" : "x64"
158 },
159 "paths" :
160 {
161 "cmake" : "C:/Program Files/CMake/bin/cmake.exe",
162 "cpack" : "C:/Program Files/CMake/bin/cpack.exe",
163 "ctest" : "C:/Program Files/CMake/bin/ctest.exe",
164 "root" : "C:/Program Files/CMake/share/cmake-3.27"
165 },
166 "version" : {
167 "isDirty": false,
168 "major": 3,
169 "minor": 27,
170 "patch": 7,
171 "string": "3.27.7",
172 "suffix": ""
173 }
174 });
175
176 let cmake = serde_json::from_value::<CMake>(json).unwrap();
177
178 assert_eq!(
179 cmake,
180 CMake {
181 version: CMakeVersion {
182 is_dirty: false,
183 major: 3,
184 minor: 27,
185 patch: 7,
186 string: "3.27.7".into(),
187 suffix: String::new(),
188 },
189 paths: CMakePaths {
190 cmake: "C:/Program Files/CMake/bin/cmake.exe".into(),
191 cpack: "C:/Program Files/CMake/bin/cpack.exe".into(),
192 ctest: "C:/Program Files/CMake/bin/ctest.exe".into(),
193 root: "C:/Program Files/CMake/share/cmake-3.27".into(),
194 },
195 generator: CMakeGenerator {
196 multi_config: true,
197 platform: Some("x64".into()),
198 name: "Visual Studio 16 2019".into(),
199 },
200 }
201 );
202 }
203
204 #[test]
205 fn test_cmake_with_unknown_field() {
206 let json = json!({
207 "generator" :
208 {
209 "multiConfig" : true,
210 "name" : "Visual Studio 16 2019",
211 "platform" : "x64",
212 "test" : "test"
213 },
214 "paths" :
215 {
216 "cmake" : "C:/Program Files/CMake/bin/cmake.exe",
217 "cpack" : "C:/Program Files/CMake/bin/cpack.exe",
218 "ctest" : "C:/Program Files/CMake/bin/ctest.exe",
219 "root" : "C:/Program Files/CMake/share/cmake-3.27"
220 },
221 "version" : {
222 "isDirty": false,
223 "major": 3,
224 "minor": 27,
225 "patch": 7,
226 "string": "3.27.7",
227 "suffix": ""
228 }
229 });
230
231 assert_eq!(
232 serde_json::from_value::<CMake>(json)
233 .unwrap_err()
234 .to_string(),
235 "unknown field `test`, expected one of `multiConfig`, `name`, `platform`"
236 );
237 }
238
239 #[test]
240 fn test_objects() {
241 let json = json!([
242 {
243 "jsonFile" : "codemodel-v2-b29a741ae0dbe513e631.json",
244 "kind" : "codemodel",
245 "version" :
246 {
247 "major" : 2,
248 "minor" : 6
249 }
250 },
251 {
252 "jsonFile" : "configureLog-v1-cac906d276896c7cc320.json",
253 "kind" : "configureLog",
254 "version" :
255 {
256 "major" : 1,
257 "minor" : 0
258 }
259 }
260 ]);
261
262 let objects = serde_json::from_value::<Vec<ReplyFileReference>>(json).unwrap();
263 assert_eq!(
264 objects,
265 vec![
266 ReplyFileReference {
267 json_file: "codemodel-v2-b29a741ae0dbe513e631.json".into(),
268 kind: ObjectKind::CodeModel,
269 version: MajorMinor { major: 2, minor: 6 }
270 },
271 ReplyFileReference {
272 json_file: "configureLog-v1-cac906d276896c7cc320.json".into(),
273 kind: ObjectKind::ConfigureLog,
274 version: MajorMinor { major: 1, minor: 0 }
275 }
276 ]
277 );
278 }
279
280 #[test]
281 fn test_reply_with_error() {
282 let json = json!({
283 "test_error" :
284 {
285 "error" : "test error"
286 }
287 });
288
289 let reply = serde_json::from_value::<HashMap<String, ReplyField>>(json).unwrap();
290 let item = reply.iter().next().unwrap();
291
292 assert!(match item.1 {
293 ReplyField::Error(e) => e.error == "test error",
294 _ => false,
295 });
296 }
297 #[test]
298 fn test_reply_with_reply_ref() {
299 let json = json!({
300 "codemodel-v2" :
301 {
302 "jsonFile" : "codemodel-v2-b29a741ae0dbe513e631.json",
303 "kind" : "codemodel",
304 "version" :
305 {
306 "major" : 2,
307 "minor" : 6
308 }
309 }
310 });
311
312 let reply = serde_json::from_value::<HashMap<String, ReplyField>>(json).unwrap();
313 let item = reply.iter().next().unwrap();
314 assert_eq!(item.0, "codemodel-v2");
315 assert!(match item.1 {
316 ReplyField::ReplyFileReference(e) =>
317 *e == ReplyFileReference {
318 json_file: "codemodel-v2-b29a741ae0dbe513e631.json".into(),
319 kind: ObjectKind::CodeModel,
320 version: MajorMinor { major: 2, minor: 6 },
321 },
322 _ => false,
323 });
324 }
325
326 #[test]
327 fn test_reply_client_with_reply_ref() {
328 let json = json!({
329 "codemodel-v2" :
330 {
331 "jsonFile" : "codemodel-v2-b29a741ae0dbe513e631.json",
332 "kind" : "codemodel",
333 "version" :
334 {
335 "major" : 2,
336 "minor" : 6
337 }
338 }
339 });
340
341 let reply = serde_json::from_value::<HashMap<String, ClientField>>(json).unwrap();
342 let item = reply.iter().next().unwrap();
343 assert_eq!(item.0, "codemodel-v2");
344 assert!(match item.1 {
345 ClientField::ReplyFileReference(e) =>
346 *e == ReplyFileReference {
347 json_file: "codemodel-v2-b29a741ae0dbe513e631.json".into(),
348 kind: ObjectKind::CodeModel,
349 version: MajorMinor { major: 2, minor: 6 },
350 },
351 _ => false,
352 });
353 }
354
355 #[test]
356 fn test_reply_client_with_error() {
357 let json = json!({
358 "bad_query.json" :
359 {
360 "error" : "unknown query file"
361 }
362 });
363
364 let reply = serde_json::from_value::<HashMap<String, ClientField>>(json).unwrap();
365 let item = reply.iter().next().unwrap();
366 assert_eq!(item.0, "bad_query.json");
367 assert!(match item.1 {
368 ClientField::Error(e) => e.error == "unknown query file",
369 _ => false,
370 });
371 }
372
373 #[test]
374 fn test_reply_query_json_with_client() {
375 let json = json!({
376 "client" :
377 {
378 "myData" : 10
379 },
380 });
381
382 let query_json = serde_json::from_value::<QueryJson>(json).unwrap();
383 assert_eq!(query_json.client.unwrap()["myData"], 10);
384 }
385
386 #[test]
387 fn test_reply_query_json_with_requests() {
388 let json = json!({
389 "requests" :
390 [
391 {
392 "kind" : "codemodel",
393 "version" : 2
394 }
395 ]
396 });
397
398 let query_json = serde_json::from_value::<QueryJson>(json).unwrap();
399 assert!(query_json
400 .requests
401 .unwrap()
402 .as_array()
403 .unwrap()
404 .first()
405 .unwrap()
406 .is_object());
407 }
408
409 #[test]
410 fn test_reply_query_json_with_responses() {
411 let json = json!({
412 "responses" :
413 [
414 {
415 "jsonFile" : "codemodel-v2-b29a741ae0dbe513e631.json",
416 "kind" : "codemodel",
417 "version" :
418 {
419 "major" : 2,
420 "minor" : 6
421 }
422 },
423 {
424 "error": "error"
425 }
426 ]
427 });
428
429 let query_json = serde_json::from_value::<QueryJson>(json).unwrap();
430 assert!(query_json
431 .responses
432 .unwrap()
433 .as_array()
434 .unwrap()
435 .first()
436 .unwrap()
437 .is_object());
438 }
439 #[test]
440 fn test_reply_query_json_with_response_error() {
441 let json = json!({
442 "responses" :
443 {
444 "error" : "unknown request kind 'bad_name'"
445 }
446 });
447
448 let query_json = serde_json::from_value::<QueryJson>(json).unwrap();
449 assert!(query_json.responses.unwrap().is_object());
450 }
451
452 #[test]
453 fn test_index() {
454 let json = json!({
455 "cmake": {
456 "version": {
457 "major": 3, "minor": 14, "patch": 0, "suffix": "",
458 "string": "3.14.0", "isDirty": false
459 },
460 "paths": {
461 "cmake": "/prefix/bin/cmake",
462 "ctest": "/prefix/bin/ctest",
463 "cpack": "/prefix/bin/cpack",
464 "root": "/prefix/share/cmake-3.14"
465 },
466 "generator": {
467 "multiConfig": false,
468 "name": "Unix Makefiles"
469 }
470 },
471 "objects": [
472 { "kind": "codemodel",
473 "version": { "major": 1, "minor": 0 },
474 "jsonFile": "test.json" },
475 ],
476 "reply": {
477 "<kind>-v<major>": { "kind": "codemodel",
478 "version": { "major": 1, "minor": 0 },
479 "jsonFile": "test.json" },
480 "<unknown>": { "error": "unknown query file" },
481 "client-<client>": {
482 "<kind>-v<major>": { "kind": "codemodel",
483 "version": { "major": 1, "minor": 0 },
484 "jsonFile": "test.json" },
485 "<unknown>": { "error": "unknown query file" },
486 "query.json": {
487 "requests": [ {}, {}, {} ],
488 "responses": [
489 { "kind": "codemodel",
490 "version": { "major": 1, "minor": 0 },
491 "jsonFile": "test.json" },
492 { "error": "unknown query file" },
493 ],
494 "client": {}
495 }
496 }
497 }
498 });
499
500 serde_json::from_value::<Index>(json).unwrap();
501 }
502}