Skip to main content

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:ident, $path_component:ident, $return_type:ident, $join_types:ident, $join_return_type:ident, $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::$path_component::$return_type,
76        };
77
78        /// query for Icinga objects of this type
79        #[allow(
80            clippy::missing_errors_doc,
81            reason = "derive_builder generated build() returns Result; #[expect] does not propagate through derive_builder, only #[allow] does"
82        )]
83        #[derive(Debug, Clone, derive_builder::Builder)]
84        #[builder(
85            build_fn(error = "crate::error::Error", validate = "Self::validate"),
86            derive(Debug)
87        )]
88        pub struct $name {
89            /// the joins (related objects) to return along with each result
90            #[builder(default, setter(strip_option, into))]
91            joins: Option<IcingaJoins<'static, $join_types>>,
92            /// the metadata to return along with each result
93            #[builder(default, setter(strip_option, into))]
94            meta: Option<Vec<IcingaMetadataType>>,
95            /// filter the results
96            #[builder(default, setter(strip_option, into))]
97            filter: Option<IcingaFilter>,
98        }
99
100        impl $name {
101            /// create a new builder for this endpoint
102            ///
103            /// this is usually the first step to calling this REST API endpoint
104            #[must_use]
105            pub fn builder() -> $builder_name {
106                $builder_name::default()
107            }
108        }
109
110        impl QueryableObject for $return_type {
111            type ListEndpoint = $name;
112
113            fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error> {
114                $name::builder().build()
115            }
116        }
117
118        impl $builder_name {
119            /// makes sure the filter object type is the correct one for the type of return values this endpoint returns
120            ///
121            /// # Errors
122            ///
123            /// this returns an error if the filter field object type does not match the return type of the API call
124            pub fn validate(&self) -> Result<(), crate::error::Error> {
125                let expected = $object_type;
126                if let Some(Some(filter)) = &self.filter {
127                    if filter.object_type != expected {
128                        Err(crate::error::Error::FilterObjectTypeMismatch(
129                            vec![expected],
130                            filter.object_type.to_owned(),
131                        ))
132                    } else {
133                        Ok(())
134                    }
135                } else {
136                    Ok(())
137                }
138            }
139        }
140
141        impl RestApiEndpoint for $name {
142            type RequestBody = IcingaFilter;
143
144            fn method(&self) -> Result<reqwest::Method, crate::error::Error> {
145                Ok(reqwest::Method::GET)
146            }
147
148            fn url(&self, base_url: &url::Url) -> Result<url::Url, crate::error::Error> {
149                let mut url = base_url
150                    .join($url_fragment)
151                    .map_err(crate::error::Error::CouldNotParseUrlFragment)?;
152                if let Some(joins) = &self.joins {
153                    add_joins_to_url(&mut url, &joins)?;
154                }
155                if let Some(meta) = &self.meta {
156                    add_meta_to_url(&mut url, &meta)?;
157                }
158                Ok(url)
159            }
160
161            fn request_body(
162                &self,
163            ) -> Result<Option<std::borrow::Cow<'_, Self::RequestBody>>, crate::error::Error>
164            where
165                Self::RequestBody: Clone + serde::Serialize + std::fmt::Debug,
166            {
167                Ok(self.filter.as_ref().map(|f| std::borrow::Cow::Borrowed(f)))
168            }
169        }
170
171        impl RestApiResponse<$name> for ResultsWrapper<QueryResultObject<$return_type>> {}
172
173        impl RestApiResponse<$name>
174            for ResultsWrapper<QueryResultObject<BTreeMap<String, serde_json::Value>>>
175        {
176        }
177
178        impl RestApiResponse<$name>
179            for ResultsWrapper<QueryResultObjectWithJoins<$return_type, $join_return_type>>
180        {
181        }
182
183        impl RestApiResponse<$name>
184            for ResultsWrapper<
185                QueryResultObjectWithJoins<BTreeMap<String, serde_json::Value>, $join_return_type>,
186            >
187        {
188        }
189
190        impl RestApiResponse<$name>
191            for ResultsWrapper<
192                QueryResultObjectWithJoins<$return_type, BTreeMap<String, serde_json::Value>>,
193            >
194        {
195        }
196
197        impl RestApiResponse<$name>
198            for ResultsWrapper<
199                QueryResultObjectWithJoins<
200                    BTreeMap<String, serde_json::Value>,
201                    BTreeMap<String, serde_json::Value>,
202                >,
203            >
204        {
205        }
206    };
207}
208pub(crate) use query_with_joins;
209
210/// implement a query REST API Endpoint for the given Icinga type without join support
211macro_rules! query {
212    ($name:ident, $builder_name:ident, $object_category:ident, $path_component:ident, $return_type:ident, $object_type:expr, $url_fragment:expr) => {
213        use std::collections::BTreeMap;
214
215#[rustfmt::skip]
216        use crate::types::{
217            enums::object_type::IcingaObjectType,
218            filter::IcingaFilter,
219            metadata::{add_meta_to_url, IcingaMetadataType},
220            query::{QueryableObject, QueryResultObject, ResultsWrapper},
221            rest::{RestApiEndpoint, RestApiResponse},
222            $object_category::$path_component::$return_type,
223        };
224
225        /// query for Icinga objects of this type
226        #[allow(
227            clippy::missing_errors_doc,
228            reason = "derive_builder generated build() returns Result; #[expect] does not propagate through derive_builder, only #[allow] does"
229        )]
230        #[derive(Debug, Clone, derive_builder::Builder)]
231        #[builder(
232            build_fn(error = "crate::error::Error", validate = "Self::validate"),
233            derive(Debug)
234        )]
235        pub struct $name {
236            /// the metadata to return along with each result
237            #[builder(default, setter(strip_option, into))]
238            meta: Option<Vec<IcingaMetadataType>>,
239            /// filter the results
240            #[builder(default, setter(strip_option, into))]
241            filter: Option<IcingaFilter>,
242        }
243
244        impl $name {
245            /// create a new builder for this endpoint
246            ///
247            /// this is usually the first step to calling this REST API endpoint
248            #[must_use]
249            pub fn builder() -> $builder_name {
250                $builder_name::default()
251            }
252        }
253
254        impl QueryableObject for $return_type {
255            type ListEndpoint = $name;
256
257            fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error> {
258                $name::builder().build()
259            }
260        }
261
262        impl $builder_name {
263            /// makes sure the filter object type is the correct one for the type of return values this endpoint returns
264            ///
265            /// # Errors
266            ///
267            /// this returns an error if the filter field object type does not match the return type of the API call
268            pub fn validate(&self) -> Result<(), crate::error::Error> {
269                let expected = $object_type;
270                if let Some(Some(filter)) = &self.filter {
271                    if filter.object_type != expected {
272                        Err(crate::error::Error::FilterObjectTypeMismatch(
273                            vec![expected],
274                            filter.object_type.to_owned(),
275                        ))
276                    } else {
277                        Ok(())
278                    }
279                } else {
280                    Ok(())
281                }
282            }
283        }
284
285        impl RestApiEndpoint for $name {
286            type RequestBody = IcingaFilter;
287
288            fn method(&self) -> Result<reqwest::Method, crate::error::Error> {
289                Ok(reqwest::Method::GET)
290            }
291
292            fn url(&self, base_url: &url::Url) -> Result<url::Url, crate::error::Error> {
293                let mut url = base_url
294                    .join($url_fragment)
295                    .map_err(crate::error::Error::CouldNotParseUrlFragment)?;
296                if let Some(meta) = &self.meta {
297                    add_meta_to_url(&mut url, &meta)?;
298                }
299                Ok(url)
300            }
301
302            fn request_body(
303                &self,
304            ) -> Result<Option<std::borrow::Cow<'_, Self::RequestBody>>, crate::error::Error>
305            where
306                Self::RequestBody: Clone + serde::Serialize + std::fmt::Debug,
307            {
308                Ok(self.filter.as_ref().map(|f| std::borrow::Cow::Borrowed(f)))
309            }
310        }
311
312        impl RestApiResponse<$name> for ResultsWrapper<QueryResultObject<$return_type>> {}
313
314        impl RestApiResponse<$name>
315            for ResultsWrapper<QueryResultObject<BTreeMap<String, serde_json::Value>>>
316        {
317        }
318    };
319}
320pub(crate) use query;