cmake_file_api/
reply.rs

1use crate::{index, objects, reply};
2use serde::de::DeserializeOwned;
3use std::ffi::OsStr;
4use std::path::{Path, PathBuf};
5use std::{fs, io};
6
7/// Errors for reading replies
8#[non_exhaustive]
9#[derive(Debug, thiserror::Error)]
10pub enum ReaderError {
11    #[error("IO error: {0}")]
12    IO(io::Error),
13
14    #[error("Failed to deserialize reply: {0}")]
15    Parse(serde_json::Error),
16
17    #[error("cmake-file-api is not generated for this build directory")]
18    FileApiNotGenerated,
19
20    #[error("failed to find object")]
21    ObjectNotFound,
22}
23
24impl From<io::Error> for ReaderError {
25    fn from(err: io::Error) -> Self {
26        ReaderError::IO(err)
27    }
28}
29
30impl From<serde_json::Error> for ReaderError {
31    fn from(err: serde_json::Error) -> Self {
32        ReaderError::Parse(err)
33    }
34}
35
36/// Reader for cmake-file-api replies
37///
38/// Example:
39///
40/// ```no_run
41/// use cmake_file_api::{query, objects};
42/// # let build_dir = std::path::Path::new(".");
43///
44/// query::Writer::default()
45///   .request_object::<objects::CodeModelV2>()
46///   .write_stateless(&build_dir)
47///   .expect("Failed to write query");
48/// ```
49pub struct Reader {
50    /// Build directory
51    build_dir: PathBuf,
52
53    /// Index file
54    index: index::Index,
55}
56
57impl Reader {
58    /// Create a new reader from a build directory
59    ///
60    /// # Errors
61    ///
62    /// `ReaderError::FileApiNotGenerated`: if the cmake-file-api is not generated for the build directory
63    /// `ReaderError::IO`: if an IO error occurs while reading the index file
64    /// `ReaderError::Parse`: if an error occurs while parsing the index file
65    pub fn from_build_dir<P: AsRef<Path>>(build_dir: P) -> Result<Self, ReaderError> {
66        let index_file = index_file(build_dir.as_ref()).ok_or(ReaderError::FileApiNotGenerated)?;
67        let index = Reader::parse_reply(index_file)?;
68        Ok(Reader {
69            build_dir: build_dir.as_ref().to_path_buf(),
70            index,
71        })
72    }
73
74    #[must_use]
75    pub fn build_dir(&self) -> &Path {
76        &self.build_dir
77    }
78
79    #[must_use]
80    pub fn index(&self) -> &index::Index {
81        &self.index
82    }
83
84    #[must_use]
85    pub fn has_object<T: objects::Object>(&self) -> bool {
86        self.find_object(T::kind(), T::major()).is_some()
87    }
88
89    /// read object
90    ///
91    /// # Errors
92    ///
93    /// `ReaderError::ObjectNotFound`: if the index file does not contain the requested object
94    /// `ReaderError::IO`: if an IO error occurs while reading the object file
95    /// `ReaderError::Parse`: if an error occurs while parsing the object file
96    pub fn read_object<T: objects::Object + DeserializeOwned>(&self) -> Result<T, ReaderError> {
97        let reply_reference = self
98            .find_object(T::kind(), T::major())
99            .ok_or(ReaderError::ObjectNotFound)?;
100        let reply_file = reply::dir(&self.build_dir).join(&reply_reference.json_file);
101        let mut object: T = Reader::parse_reply(reply_file)?;
102
103        object.resolve_references(self)?;
104
105        Ok(object)
106    }
107
108    /// Parse a reply file into a given object type
109    pub(crate) fn parse_reply<P: AsRef<Path>, Object: DeserializeOwned>(
110        reply_file: P,
111    ) -> Result<Object, ReaderError> {
112        let content = fs::read_to_string(&reply_file)?;
113
114        let object = serde_json::from_str(content.as_str())?;
115
116        Ok(object)
117    }
118
119    /// Find an object in the index file
120    fn find_object(
121        &self,
122        kind: objects::ObjectKind,
123        major: u32,
124    ) -> Option<&index::ReplyFileReference> {
125        self.index
126            .objects
127            .iter()
128            .find(|obj| obj.kind == kind && obj.version.major == major)
129    }
130}
131
132/// Get cmake-file-api reply path for a given build directory
133pub fn dir<P: AsRef<Path>>(build_dir: P) -> PathBuf {
134    Path::new(build_dir.as_ref())
135        .join(".cmake")
136        .join("api")
137        .join("v1")
138        .join("reply")
139}
140
141/// Get cmake-file-api index file path for a given build directory
142pub fn index_file<P: AsRef<Path>>(build_dir: P) -> Option<PathBuf> {
143    let reply_dir = dir(build_dir);
144
145    if !reply_dir.exists() {
146        return None;
147    }
148
149    // find json file with 'index-' prefix
150    fs::read_dir(&reply_dir).ok()?.find_map(|entry| {
151        let path = entry.ok()?.path();
152        if path.is_file() {
153            if let Some(file_name) = path.file_name().and_then(OsStr::to_str) {
154                if file_name.starts_with("index-")
155                    && path
156                        .extension()
157                        .map_or(false, |ext| ext.eq_ignore_ascii_case("json"))
158                {
159                    return Some(path);
160                }
161            }
162        }
163        None
164    })
165}
166
167/// Check if cmake-file-api is available for a given build directory
168pub fn is_available<P: AsRef<Path>>(build_dir: P) -> bool {
169    index_file(build_dir).is_some()
170}