cmake_file_api/
query.rs

1use crate::objects;
2use crate::objects::ObjectKind;
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5use std::{fs, io};
6
7/// Errors for writing queries
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum WriterError {
11    #[error("IO error: {0}")]
12    IO(io::Error),
13
14    #[error("Failed to serialize query: {0}")]
15    Parse(serde_json::Error),
16
17    #[error("Client name not set")]
18    ClientNameNotSet,
19}
20
21impl From<io::Error> for WriterError {
22    fn from(err: io::Error) -> Self {
23        WriterError::IO(err)
24    }
25}
26
27impl From<serde_json::Error> for WriterError {
28    fn from(err: serde_json::Error) -> Self {
29        WriterError::Parse(err)
30    }
31}
32
33/// Write queries for cmake-file-api.
34///
35/// # Example
36///
37/// ```no_run
38/// use cmake_file_api::{query, objects};
39/// # let build_dir = std::path::Path::new(".");
40///
41/// query::Writer::default()
42///   .request_object::<objects::CodeModelV2>()
43///   .write_stateless(&build_dir)
44///   .expect("Failed to write query");
45/// ```
46#[derive(Default)]
47pub struct Writer {
48    query: Query,
49    client_name: Option<String>,
50}
51
52impl Writer {
53    /// Request cmake-file-api object
54    pub fn request_object<T: objects::Object>(&mut self) -> &mut Self {
55        self.query.requests.push(Request {
56            kind: T::kind(),
57            version: OptionalVersion {
58                major: T::major(),
59                minor: None,
60            },
61        });
62        self
63    }
64
65    /// Request cmake-file-api object with exact version (minor version only used for stateful queries)
66    pub fn add_request_exact<T: objects::Object>(&mut self, minor: u32) -> &mut Self {
67        self.query.requests.push(Request {
68            kind: T::kind(),
69            version: OptionalVersion {
70                major: T::major(),
71                minor: Some(minor),
72            },
73        });
74        self
75    }
76
77    /// Helper function to request all objects
78    pub fn request_all_objects(&mut self) -> &mut Self {
79        self.request_object::<objects::CodeModelV2>()
80            .request_object::<objects::ConfigureLogV1>()
81            .request_object::<objects::CacheV2>()
82            .request_object::<objects::ToolchainsV1>()
83            .request_object::<objects::CMakeFilesV1>()
84    }
85
86    /// Set client data
87    /// Only used for stateful queries
88    ///
89    /// # Arguments
90    ///
91    /// * `client_name` - Client name
92    /// * `client_data` - Client data (JSON)
93    pub fn set_client(&mut self, client_name: &str, client_data: serde_json::Value) -> &mut Self {
94        self.query.client = Some(client_data);
95        self.client_name = Some(client_name.to_owned());
96        self
97    }
98
99    /// Write stateless query
100    /// For every object requested, a file is created in the query folder e.g. `<build_dir>/.cmake/api/v1/query/codemodel-v2`
101    ///
102    /// # Errors
103    ///
104    /// Returns an error if the query folder could not be created
105    /// Returns an error if the query file could not be written
106    pub fn write_stateless<P: AsRef<Path>>(&self, build_dir: P) -> Result<(), WriterError> {
107        let query_dir = dir(build_dir);
108
109        // create query folder
110        fs::create_dir_all(&query_dir)?;
111
112        for obj in &self.query.requests {
113            let query_file =
114                query_dir.join(format!("{}-v{}", obj.kind.as_str(), obj.version.major));
115            fs::write(&query_file, "")?;
116        }
117
118        Ok(())
119    }
120
121    /// Write stateful query
122    /// A single `<client-name>/query.json` file is created in the query folder containing all requested objects and when set the client data
123    ///
124    /// # Arguments
125    ///
126    /// * `build_dir` - Build directory
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the query file could not be written
131    pub fn write_stateful<P: AsRef<Path>>(&self, build_dir: P) -> Result<(), WriterError> {
132        let query_dir = dir(build_dir);
133        let client_dir = query_dir.join(
134            self.client_name
135                .as_ref()
136                .ok_or(WriterError::ClientNameNotSet)?,
137        );
138
139        // create query folder
140        fs::create_dir_all(&client_dir)?;
141
142        // create query file
143        let query_file = client_dir.join("query.json");
144        let query = serde_json::to_string(&self.query)?;
145        fs::write(query_file, query)?;
146
147        Ok(())
148    }
149}
150
151#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
152struct OptionalVersion {
153    major: u32,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    minor: Option<u32>,
156}
157
158#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
159struct Request {
160    kind: ObjectKind,
161    version: OptionalVersion,
162}
163
164#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
165struct Query {
166    requests: Vec<Request>,
167    client: Option<serde_json::Value>,
168}
169/// Get query folder for a given build directory
170pub fn dir<P: AsRef<Path>>(build_dir: P) -> PathBuf {
171    Path::new(build_dir.as_ref())
172        .join(".cmake")
173        .join("api")
174        .join("v1")
175        .join("query")
176}