1use serde::{Deserialize, Serialize};
4
5use super::{enums::object_type::IcingaObjectType, metadata::IcingaMetadata};
6
7#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
9pub struct ResultsWrapper<T> {
10 pub results: Vec<T>,
12}
13
14#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
16pub struct QueryResultObjectWithJoins<Obj, ObjJoins> {
17 pub attrs: Obj,
19 pub joins: ObjJoins,
21 pub meta: IcingaMetadata,
23 pub name: String,
25 #[serde(rename = "type")]
27 pub object_type: IcingaObjectType,
28}
29
30#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
32pub struct QueryResultObject<Obj> {
33 pub attrs: Obj,
35 pub meta: IcingaMetadata,
37 pub name: String,
39 #[serde(rename = "type")]
41 pub object_type: IcingaObjectType,
42}
43
44pub trait QueryableObject {
46 type ListEndpoint;
48
49 fn default_query_endpoint() -> Result<Self::ListEndpoint, crate::error::Error>;
56}
57
58macro_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 #[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 #[builder(default, setter(strip_option, into))]
91 joins: Option<IcingaJoins<'static, $join_types>>,
92 #[builder(default, setter(strip_option, into))]
94 meta: Option<Vec<IcingaMetadataType>>,
95 #[builder(default, setter(strip_option, into))]
97 filter: Option<IcingaFilter>,
98 }
99
100 impl $name {
101 #[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 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
210macro_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 #[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 #[builder(default, setter(strip_option, into))]
238 meta: Option<Vec<IcingaMetadataType>>,
239 #[builder(default, setter(strip_option, into))]
241 filter: Option<IcingaFilter>,
242 }
243
244 impl $name {
245 #[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 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;