1use std::borrow::Cow;
22
23use crate::mapping::invalid_filed;
24use crate::schema::{Mapping, MappingType};
25
26use super::{endpoint::Endpoint, InterfaceMapping, MappingError};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct PropertiesMapping {
31 pub(crate) endpoint: Endpoint<String>,
32 pub(crate) mapping_type: MappingType,
33 pub(crate) allow_unset: bool,
34 #[cfg(feature = "doc-fields")]
35 pub(crate) description: Option<String>,
36 #[cfg(feature = "doc-fields")]
37 pub(crate) doc: Option<String>,
38}
39
40impl PropertiesMapping {
41 #[must_use]
43 pub fn allow_unset(&self) -> bool {
44 self.allow_unset
45 }
46}
47
48impl InterfaceMapping for PropertiesMapping {
49 fn endpoint(&self) -> &Endpoint<String> {
50 &self.endpoint
51 }
52
53 fn mapping_type(&self) -> MappingType {
54 self.mapping_type
55 }
56
57 #[cfg(feature = "doc-fields")]
58 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
59 fn description(&self) -> Option<&str> {
60 self.description.as_deref()
61 }
62
63 #[cfg(feature = "doc-fields")]
64 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
65 fn doc(&self) -> Option<&str> {
66 self.doc.as_deref()
67 }
68}
69
70impl<T> TryFrom<Mapping<T>> for PropertiesMapping
71where
72 T: AsRef<str> + Into<String>,
73{
74 type Error = MappingError;
75
76 fn try_from(value: Mapping<T>) -> Result<Self, Self::Error> {
77 let endpoint = Endpoint::try_from(value.endpoint.as_ref())?;
78
79 if value.reliability.is_some() {
80 invalid_filed!(properties, "reliability");
81 }
82
83 if value.explicit_timestamp.is_some() {
84 invalid_filed!(properties, "explicit_timestamp");
85 }
86
87 if value.retention.is_some() {
88 invalid_filed!(properties, "retention");
89 }
90
91 if value.expiry.is_some() {
92 invalid_filed!(properties, "expiry");
93 }
94
95 if value.database_retention_policy.is_some() {
96 invalid_filed!(properties, "database_retention_policy");
97 }
98
99 if value.database_retention_ttl.is_some() {
100 invalid_filed!(properties, "database_retention_ttl");
101 }
102
103 if value.required.is_some() {
104 invalid_filed!(properties, "required");
105 }
106
107 Ok(Self {
108 endpoint,
109 mapping_type: value.mapping_type,
110 allow_unset: value.allow_unset.unwrap_or_default(),
111 #[cfg(feature = "doc-fields")]
112 description: value.description.map(T::into),
113 #[cfg(feature = "doc-fields")]
114 doc: value.doc.map(T::into),
115 })
116 }
117}
118
119impl<'a> From<&'a PropertiesMapping> for Mapping<Cow<'a, str>> {
120 fn from(value: &'a PropertiesMapping) -> Self {
121 Mapping {
122 endpoint: value.endpoint().to_string().into(),
123 mapping_type: value.mapping_type,
124 reliability: None,
125 explicit_timestamp: None,
126 retention: None,
127 expiry: None,
128 database_retention_policy: None,
129 database_retention_ttl: None,
130 allow_unset: Some(value.allow_unset),
131 required: None,
132 #[cfg(feature = "doc-fields")]
133 description: value.description().map(Cow::Borrowed),
134 #[cfg(feature = "doc-fields")]
135 doc: value.doc().map(Cow::Borrowed),
136 #[cfg(not(feature = "doc-fields"))]
137 description: None,
138 #[cfg(not(feature = "doc-fields"))]
139 doc: None,
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146
147 use super::*;
148
149 #[test]
150 fn getters_success() {
151 let mapping_type = MappingType::Boolean;
152 let description = Some("Property mapping description");
153 let doc = Some("Property mapping doc");
154 let mapping = Mapping {
155 endpoint: "/property/path",
156 mapping_type,
157 reliability: None,
158 explicit_timestamp: None,
159 retention: None,
160 expiry: None,
161 database_retention_policy: None,
162 database_retention_ttl: None,
163 allow_unset: Some(true),
164 required: None,
165 description,
166 doc,
167 };
168
169 let prop_mapping = PropertiesMapping::try_from(mapping).unwrap();
170 let exp_endpoint = Endpoint::try_from("/property/path").unwrap();
171 assert_eq!(*prop_mapping.endpoint(), exp_endpoint);
172 assert_eq!(prop_mapping.mapping_type(), mapping_type);
173 assert!(prop_mapping.allow_unset());
174 #[cfg(feature = "doc-fields")]
175 {
176 assert_eq!(prop_mapping.description(), description);
177 assert_eq!(prop_mapping.doc(), doc);
178 }
179 }
180
181 #[test]
182 fn from_and_into() {
183 let description = Some(Cow::Borrowed("Property mapping description"));
184 let doc = Some(Cow::Borrowed("Property mapping doc"));
185 let mapping = Mapping {
186 endpoint: Cow::Borrowed("/property/path"),
187 mapping_type: MappingType::Boolean,
188 reliability: None,
189 explicit_timestamp: None,
190 retention: None,
191 expiry: None,
192 database_retention_policy: None,
193 database_retention_ttl: None,
194 allow_unset: Some(true),
195 required: None,
196 description,
197 doc,
198 };
199
200 let prop_mapping = PropertiesMapping::try_from(mapping.clone()).unwrap();
201
202 let exp = PropertiesMapping {
203 endpoint: Endpoint::try_from("/property/path").unwrap(),
204 mapping_type: MappingType::Boolean,
205 allow_unset: true,
206 #[cfg(feature = "doc-fields")]
207 description: mapping.description.as_ref().map(|v| v.to_string()),
208 #[cfg(feature = "doc-fields")]
209 doc: mapping.doc.as_ref().map(|v| v.to_string()),
210 };
211 assert_eq!(prop_mapping, exp);
212
213 let cov_mapping: Mapping<Cow<str>> = (&prop_mapping).into();
214
215 #[cfg(not(feature = "doc-fields"))]
216 let mut mapping = mapping;
217 #[cfg(not(feature = "doc-fields"))]
218 {
219 mapping.description.take();
220 mapping.doc.take();
221 }
222
223 assert_eq!(cov_mapping, mapping);
224 }
225
226 #[cfg(feature = "strict")]
227 #[test]
228 fn mapping_error_invalid_fields() {
229 use crate::schema::{DatabaseRetentionPolicy, InterfaceType, Reliability, Retention};
230
231 let mapping = Mapping {
232 endpoint: "/property/path",
233 mapping_type: MappingType::Boolean,
234 reliability: Some(Reliability::Guaranteed),
235 explicit_timestamp: None,
236 retention: None,
237 expiry: None,
238 database_retention_policy: None,
239 database_retention_ttl: None,
240 allow_unset: None,
241 required: None,
242 description: None,
243 doc: None,
244 };
245
246 let err = PropertiesMapping::try_from(mapping).unwrap_err();
247 assert!(matches!(
248 err,
249 MappingError::InvalidField {
250 field: "reliability",
251 interface_type: InterfaceType::Properties
252 }
253 ));
254
255 let mapping = Mapping {
256 endpoint: "/property/path",
257 mapping_type: MappingType::Boolean,
258 reliability: None,
259 explicit_timestamp: Some(true),
260 retention: None,
261 expiry: None,
262 database_retention_policy: None,
263 database_retention_ttl: None,
264 allow_unset: None,
265 required: None,
266 description: None,
267 doc: None,
268 };
269
270 let err = PropertiesMapping::try_from(mapping).unwrap_err();
271 assert!(matches!(
272 err,
273 MappingError::InvalidField {
274 field: "explicit_timestamp",
275 interface_type: InterfaceType::Properties
276 }
277 ));
278
279 let mapping = Mapping {
280 endpoint: "/property/path",
281 mapping_type: MappingType::Boolean,
282 reliability: None,
283 explicit_timestamp: None,
284 retention: Some(Retention::Stored),
285 expiry: None,
286 database_retention_policy: None,
287 database_retention_ttl: None,
288 allow_unset: None,
289 required: None,
290 description: None,
291 doc: None,
292 };
293
294 let err = PropertiesMapping::try_from(mapping).unwrap_err();
295 assert!(matches!(
296 err,
297 MappingError::InvalidField {
298 field: "retention",
299 interface_type: InterfaceType::Properties
300 }
301 ));
302
303 let mapping = Mapping {
304 endpoint: "/property/path",
305 mapping_type: MappingType::Boolean,
306 reliability: None,
307 explicit_timestamp: None,
308 retention: None,
309 expiry: Some(420),
310 database_retention_policy: None,
311 database_retention_ttl: None,
312 allow_unset: None,
313 required: None,
314 description: None,
315 doc: None,
316 };
317
318 let err = PropertiesMapping::try_from(mapping).unwrap_err();
319 assert!(matches!(
320 err,
321 MappingError::InvalidField {
322 field: "expiry",
323 interface_type: InterfaceType::Properties
324 }
325 ));
326
327 let mapping = Mapping {
328 endpoint: "/property/path",
329 mapping_type: MappingType::Boolean,
330 reliability: None,
331 explicit_timestamp: None,
332 retention: None,
333 expiry: None,
334 database_retention_policy: Some(DatabaseRetentionPolicy::NoTtl),
335 database_retention_ttl: None,
336 allow_unset: None,
337 required: None,
338 description: None,
339 doc: None,
340 };
341
342 let err = PropertiesMapping::try_from(mapping).unwrap_err();
343 assert!(matches!(
344 err,
345 MappingError::InvalidField {
346 field: "database_retention_policy",
347 interface_type: InterfaceType::Properties
348 }
349 ));
350
351 let mapping = Mapping {
352 endpoint: "/property/path",
353 mapping_type: MappingType::Boolean,
354 reliability: None,
355 explicit_timestamp: None,
356 retention: None,
357 expiry: None,
358 database_retention_policy: None,
359 database_retention_ttl: Some(420),
360 allow_unset: None,
361 required: None,
362 description: None,
363 doc: None,
364 };
365
366 let err = PropertiesMapping::try_from(mapping).unwrap_err();
367 assert!(matches!(
368 err,
369 MappingError::InvalidField {
370 field: "database_retention_ttl",
371 interface_type: InterfaceType::Properties
372 }
373 ));
374
375 let mapping = Mapping {
376 endpoint: "/property/path",
377 mapping_type: MappingType::Boolean,
378 reliability: None,
379 explicit_timestamp: None,
380 retention: None,
381 expiry: None,
382 database_retention_policy: None,
383 database_retention_ttl: None,
384 allow_unset: None,
385 required: Some(true),
386 description: None,
387 doc: None,
388 };
389
390 let err = PropertiesMapping::try_from(mapping).unwrap_err();
391 assert!(matches!(
392 err,
393 MappingError::InvalidField {
394 field: "required",
395 interface_type: InterfaceType::Properties
396 }
397 ));
398 }
399}