1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use crate::objects;
use crate::objects::ObjectKind;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{fs, io};

/// Errors for writing queries
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum WriterError {
    #[error("IO error: {0}")]
    IO(io::Error),

    #[error("Failed to serialize query: {0}")]
    Parse(serde_json::Error),

    #[error("Client name not set")]
    ClientNameNotSet,
}

impl From<io::Error> for WriterError {
    fn from(err: io::Error) -> Self {
        WriterError::IO(err)
    }
}

impl From<serde_json::Error> for WriterError {
    fn from(err: serde_json::Error) -> Self {
        WriterError::Parse(err)
    }
}

/// Write queries for cmake-file-api.
///
/// # Example
///
/// ```no_run
/// use cmake_file_api::{query, objects};
/// # let build_dir = std::path::Path::new(".");
///
/// query::Writer::default()
///   .request_object::<objects::CodeModelV2>()
///   .write_stateless(&build_dir)
///   .expect("Failed to write query");
/// ```
#[derive(Default)]
pub struct Writer {
    query: Query,
    client_name: Option<String>,
}

impl Writer {
    /// Request cmake-file-api object
    pub fn request_object<T: objects::Object>(&mut self) -> &mut Self {
        self.query.requests.push(Request {
            kind: T::kind(),
            version: OptionalVersion {
                major: T::major(),
                minor: None,
            },
        });
        self
    }

    /// Request cmake-file-api object with exact version (minor version only used for stateful queries)
    pub fn add_request_exact<T: objects::Object>(&mut self, minor: u32) -> &mut Self {
        self.query.requests.push(Request {
            kind: T::kind(),
            version: OptionalVersion {
                major: T::major(),
                minor: Some(minor),
            },
        });
        self
    }

    /// Helper function to request all objects
    pub fn request_all_objects(&mut self) -> &mut Self {
        self.request_object::<objects::CodeModelV2>()
            .request_object::<objects::ConfigureLogV1>()
            .request_object::<objects::CacheV2>()
            .request_object::<objects::ToolchainsV1>()
            .request_object::<objects::CMakeFilesV1>()
    }

    /// Set client data
    /// Only used for stateful queries
    ///
    /// # Arguments
    ///
    /// * `client_name` - Client name
    /// * `client_data` - Client data (JSON)
    pub fn set_client(&mut self, client_name: &str, client_data: serde_json::Value) -> &mut Self {
        self.query.client = Some(client_data);
        self.client_name = Some(client_name.to_owned());
        self
    }

    /// Write stateless query
    /// For every object requested, a file is created in the query folder e.g. `<build_dir>/.cmake/api/v1/query/codemodel-v2`
    ///
    /// # Errors
    ///
    /// Returns an error if the query folder could not be created
    /// Returns an error if the query file could not be written
    pub fn write_stateless<P: AsRef<Path>>(&self, build_dir: P) -> Result<(), WriterError> {
        let query_dir = dir(build_dir);

        // create query folder
        fs::create_dir_all(&query_dir)?;

        for obj in &self.query.requests {
            let query_file =
                query_dir.join(format!("{}-v{}", obj.kind.as_str(), obj.version.major));
            fs::write(&query_file, "")?;
        }

        Ok(())
    }

    /// Write stateful query
    /// A single `<client-name>/query.json` file is created in the query folder containing all requested objects and when set the client data
    ///
    /// # Arguments
    ///
    /// * `build_dir` - Build directory
    ///
    /// # Errors
    ///
    /// Returns an error if the query file could not be written
    pub fn write_stateful<P: AsRef<Path>>(&self, build_dir: P) -> Result<(), WriterError> {
        let query_dir = dir(build_dir);
        let client_dir = query_dir.join(
            self.client_name
                .as_ref()
                .ok_or(WriterError::ClientNameNotSet)?,
        );

        // create query folder
        fs::create_dir_all(&client_dir)?;

        // create query file
        let query_file = client_dir.join("query.json");
        let query = serde_json::to_string(&self.query)?;
        fs::write(query_file, query)?;

        Ok(())
    }
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct OptionalVersion {
    major: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    minor: Option<u32>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Request {
    kind: ObjectKind,
    version: OptionalVersion,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Query {
    requests: Vec<Request>,
    client: Option<serde_json::Value>,
}
/// Get query folder for a given build directory
pub fn dir<P: AsRef<Path>>(build_dir: P) -> PathBuf {
    Path::new(build_dir.as_ref())
        .join(".cmake")
        .join("api")
        .join("v1")
        .join("query")
}