cmake_file_api/
index.rs

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    /// information about the instance of `CMake` that generated the reply
13    pub cmake: CMake,
14
15    /// list of objects that are referenced in the reply
16    pub objects: Vec<ReplyFileReference>,
17
18    /// map of replies to client queries
19    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/// information about the instance of `CMake` that generated the reply
33#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35#[serde(deny_unknown_fields)]
36#[non_exhaustive]
37pub struct CMakeVersion {
38    /// specifying the major version component
39    pub major: i32,
40
41    /// specifying the minor version component
42    pub minor: i32,
43
44    /// specifying the patch version component
45    pub patch: i32,
46
47    /// specifying the version suffix, if any, e.g. g0abc3
48    pub suffix: String,
49
50    /// specifying the full version in the format `<major>.<minor>.<patch>[-<suffix>]`
51    pub string: String,
52
53    /// indicating whether the version was built from a version controlled source tree with local modifications
54    pub is_dirty: bool,
55}
56
57/// paths to things that come with `CMake`
58#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60#[serde(deny_unknown_fields)]
61#[non_exhaustive]
62pub struct CMakePaths {
63    /// absolute path to cmake tool
64    pub cmake: PathBuf,
65
66    /// absolute path to ctest tool
67    pub ctest: PathBuf,
68
69    /// absolute path to cpack tool
70    pub cpack: PathBuf,
71
72    /// absolute path to the directory containing CMake resources like the Modules/ directory
73    pub root: PathBuf,
74}
75
76/// describing the `CMake` generator used for the build
77#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79#[serde(deny_unknown_fields)]
80#[non_exhaustive]
81pub struct CMakeGenerator {
82    /// specifying whether the generator supports multiple output configurations
83    pub multi_config: bool,
84
85    /// specifying the name of the generator
86    pub name: String,
87
88    /// If the generator supports CMAKE_GENERATOR_PLATFORM, this is a string specifying the generator platform name
89    pub platform: Option<String>,
90}
91
92/// represents a reference to another reply file
93#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95#[serde(deny_unknown_fields)]
96#[non_exhaustive]
97pub struct ReplyFileReference {
98    /// specifying one of the Object Kinds
99    pub kind: ObjectKind,
100
101    /// object version
102    pub version: MajorMinor,
103
104    /// path relative to the reply index file to another JSON file containing the object
105    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}