icinga2_api/types/
query.rs

1//! Types related to query API calls
2
3use serde::{Deserialize, Serialize};
4
5use super::{enums::object_type::IcingaObjectType, metadata::IcingaMetadata};
6
7/// wrapper for Json results
8#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
9pub struct ResultsWrapper<T> {
10    /// the internal field in the Icinga2 object containing all an array of the actual results
11    pub results: Vec<T>,
12}
13
14/// the result of an icinga query to a type with joins
15#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
16pub struct QueryResultObjectWithJoins<Obj, ObjJoins> {
17    /// dependency attributes
18    pub attrs: Obj,
19    /// joins
20    pub joins: ObjJoins,
21    /// metadata, only contains data if the parameter meta contains one or more values
22    pub meta: IcingaMetadata,
23    /// object name
24    pub name: String,
25    /// type of icinga object, should always be the one matching Obj
26    #[serde(rename = "type")]
27    pub object_type: IcingaObjectType,
28}
29
30/// the result of an icinga query to a type without joins
31#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
32pub struct QueryResultObject<Obj> {
33    /// dependency attributes
34    pub attrs: Obj,
35    /// metadata, only contains data if the parameter meta contains one or more values
36    pub meta: IcingaMetadata,
37    /// object name
38    pub name: String,
39    /// type of icinga object, should always be the one matching Obj
40    #[serde(rename = "type")]
41    pub object_type: IcingaObjectType,
42}
43
44/// represents a type of icinga object which can returned by a query
45pub trait QueryableObject {
46    /// the type of the endpoint for listing all objects of this type
47    type ListEndpoint;
48
49    /// returns the endpoint constructed by calling the builder's build method
50    /// without calling any of the builder methods first
51    ///
52    /// # Errors
53    ///
54    /// this returns any errors the builder's builder() call produces
55    fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error>;
56}
57
58/// implement a query REST API Endpoint for the given Icinga type with join support
59macro_rules! query_with_joins {
60    ($name:ident, $builder_name:ident, $object_category:path, $path_component:path, $return_type:ty, $join_types:ty, $join_return_type:ty, $object_type:expr, $url_fragment:expr) => {
61        use std::collections::BTreeMap;
62
63#[rustfmt::skip]
64        use crate::types::{
65            enums::object_type::IcingaObjectType,
66            filter::IcingaFilter,
67            join_types::{
68                add_joins_to_url,
69                $path_component::{$join_return_type, $join_types},
70                IcingaJoins,
71            },
72            metadata::{add_meta_to_url, IcingaMetadataType},
73            query::{QueryableObject, QueryResultObject, QueryResultObjectWithJoins, ResultsWrapper},
74            rest::{RestApiEndpoint, RestApiResponse},
75            $object_category::{
76                $path_component::{
77                    $return_type,
78                }
79            }
80        };
81
82        /// query for Icinga objects of this type
83        #[allow(clippy::missing_errors_doc)]
84        #[derive(Debug, Clone, derive_builder::Builder)]
85        #[builder(
86            build_fn(error = "crate::error::Error", validate = "Self::validate"),
87            derive(Debug)
88        )]
89        pub struct $name<'a> {
90            /// the joins (related objects) to return along with each result
91            #[builder(default, setter(strip_option, into))]
92            joins: Option<IcingaJoins<'a, $join_types>>,
93            /// the metadata to return along with each result
94            #[builder(default, setter(strip_option, into))]
95            meta: Option<Vec<IcingaMetadataType>>,
96            /// filter the results
97            #[builder(default, setter(strip_option, into))]
98            filter: Option<IcingaFilter>,
99        }
100
101        impl<'a> $name<'a> {
102            /// create a new builder for this endpoint
103            ///
104            /// this is usually the first step to calling this REST API endpoint
105            #[must_use]
106            pub fn builder() -> $builder_name<'a> {
107                $builder_name::default()
108            }
109        }
110
111        impl QueryableObject for $return_type {
112            type ListEndpoint = $name<'static>;
113
114            fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error> {
115                $name::builder().build()
116            }
117        }
118
119        impl<'a> $builder_name<'a> {
120            /// makes sure the filter object type is the correct one for the type of return values this endpoint returns
121            ///
122            /// # Errors
123            ///
124            /// this returns an error if the filter field object type does not match the return type of the API call
125            pub fn validate(&self) -> Result<(), crate::error::Error> {
126                let expected = $object_type;
127                if let Some(Some(filter)) = &self.filter {
128                    if filter.object_type != expected {
129                        Err(crate::error::Error::FilterObjectTypeMismatch(
130                            vec![expected],
131                            filter.object_type.to_owned(),
132                        ))
133                    } else {
134                        Ok(())
135                    }
136                } else {
137                    Ok(())
138                }
139            }
140        }
141
142        impl<'a> RestApiEndpoint for $name<'a> {
143            type RequestBody = IcingaFilter;
144
145            fn method(&self) -> Result<reqwest::Method, crate::error::Error> {
146                Ok(reqwest::Method::GET)
147            }
148
149            fn url(&self, base_url: &url::Url) -> Result<url::Url, crate::error::Error> {
150                let mut url = base_url
151                    .join($url_fragment)
152                    .map_err(crate::error::Error::CouldNotParseUrlFragment)?;
153                if let Some(joins) = &self.joins {
154                    add_joins_to_url(&mut url, &joins)?;
155                }
156                if let Some(meta) = &self.meta {
157                    add_meta_to_url(&mut url, &meta)?;
158                }
159                Ok(url)
160            }
161
162            fn request_body(
163                &self,
164            ) -> Result<Option<std::borrow::Cow<Self::RequestBody>>, crate::error::Error>
165            where
166                Self::RequestBody: Clone + serde::Serialize + std::fmt::Debug,
167            {
168                Ok(self.filter.as_ref().map(|f| std::borrow::Cow::Borrowed(f)))
169            }
170        }
171
172        impl<'a> RestApiResponse<$name<'a>> for ResultsWrapper<QueryResultObject<$return_type>> {}
173
174        impl<'a> RestApiResponse<$name<'a>>
175            for ResultsWrapper<QueryResultObject<BTreeMap<String, serde_json::Value>>>
176        {
177        }
178
179        impl<'a> RestApiResponse<$name<'a>>
180            for ResultsWrapper<QueryResultObjectWithJoins<$return_type, $join_return_type>>
181        {
182        }
183
184        impl<'a> RestApiResponse<$name<'a>>
185            for ResultsWrapper<
186                QueryResultObjectWithJoins<BTreeMap<String, serde_json::Value>, $join_return_type>,
187            >
188        {
189        }
190
191        impl<'a> RestApiResponse<$name<'a>>
192            for ResultsWrapper<
193                QueryResultObjectWithJoins<$return_type, BTreeMap<String, serde_json::Value>>,
194            >
195        {
196        }
197
198        impl<'a> RestApiResponse<$name<'a>>
199            for ResultsWrapper<
200                QueryResultObjectWithJoins<
201                    BTreeMap<String, serde_json::Value>,
202                    BTreeMap<String, serde_json::Value>,
203                >,
204            >
205        {
206        }
207    };
208}
209pub(crate) use query_with_joins;
210
211/// implement a query REST API Endpoint for the given Icinga type without join support
212macro_rules! query {
213    ($name:ident, $builder_name:ident, $object_category:path, $path_component:path, $return_type:ty, $object_type:expr, $url_fragment:expr) => {
214        use std::collections::BTreeMap;
215
216#[rustfmt::skip]
217        use crate::types::{
218            enums::object_type::IcingaObjectType,
219            filter::IcingaFilter,
220            metadata::{add_meta_to_url, IcingaMetadataType},
221            query::{QueryableObject, QueryResultObject, ResultsWrapper},
222            rest::{RestApiEndpoint, RestApiResponse},
223            $object_category::{
224                $path_component::{
225                    $return_type,
226                },
227            },
228        };
229
230        /// query for Icinga objects of this type
231        #[allow(clippy::missing_errors_doc)]
232        #[derive(Debug, Clone, derive_builder::Builder)]
233        #[builder(
234            build_fn(error = "crate::error::Error", validate = "Self::validate"),
235            derive(Debug)
236        )]
237        pub struct $name {
238            /// the metadata to return along with each result
239            #[builder(default, setter(strip_option, into))]
240            meta: Option<Vec<IcingaMetadataType>>,
241            /// filter the results
242            #[builder(default, setter(strip_option, into))]
243            filter: Option<IcingaFilter>,
244        }
245
246        impl $name {
247            /// create a new builder for this endpoint
248            ///
249            /// this is usually the first step to calling this REST API endpoint
250            #[must_use]
251            pub fn builder() -> $builder_name {
252                $builder_name::default()
253            }
254        }
255
256        impl QueryableObject for $return_type {
257            type ListEndpoint = $name;
258
259            fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error> {
260                $name::builder().build()
261            }
262        }
263
264        impl $builder_name {
265            /// makes sure the filter object type is the correct one for the type of return values this endpoint returns
266            ///
267            /// # Errors
268            ///
269            /// this returns an error if the filter field object type does not match the return type of the API call
270            pub fn validate(&self) -> Result<(), crate::error::Error> {
271                let expected = $object_type;
272                if let Some(Some(filter)) = &self.filter {
273                    if filter.object_type != expected {
274                        Err(crate::error::Error::FilterObjectTypeMismatch(
275                            vec![expected],
276                            filter.object_type.to_owned(),
277                        ))
278                    } else {
279                        Ok(())
280                    }
281                } else {
282                    Ok(())
283                }
284            }
285        }
286
287        impl RestApiEndpoint for $name {
288            type RequestBody = IcingaFilter;
289
290            fn method(&self) -> Result<reqwest::Method, crate::error::Error> {
291                Ok(reqwest::Method::GET)
292            }
293
294            fn url(&self, base_url: &url::Url) -> Result<url::Url, crate::error::Error> {
295                let mut url = base_url
296                    .join($url_fragment)
297                    .map_err(crate::error::Error::CouldNotParseUrlFragment)?;
298                if let Some(meta) = &self.meta {
299                    add_meta_to_url(&mut url, &meta)?;
300                }
301                Ok(url)
302            }
303
304            fn request_body(
305                &self,
306            ) -> Result<Option<std::borrow::Cow<Self::RequestBody>>, crate::error::Error>
307            where
308                Self::RequestBody: Clone + serde::Serialize + std::fmt::Debug,
309            {
310                Ok(self.filter.as_ref().map(|f| std::borrow::Cow::Borrowed(f)))
311            }
312        }
313
314        impl RestApiResponse<$name> for ResultsWrapper<QueryResultObject<$return_type>> {}
315
316        impl RestApiResponse<$name>
317            for ResultsWrapper<QueryResultObject<BTreeMap<String, serde_json::Value>>>
318        {
319        }
320    };
321}
322pub(crate) use query;