up_rust/uattributes.rs
1/********************************************************************************
2 * Copyright (c) 2023 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14mod uattributesvalidator;
15mod upayloadformat;
16mod upriority;
17
18use std::time::SystemTime;
19
20pub use uattributesvalidator::*;
21pub use upriority::*;
22
23pub use crate::up_core_api::uattributes::*;
24use crate::{UCode, UUri, UUID};
25
26pub(crate) const UPRIORITY_DEFAULT: UPriority = UPriority::UPRIORITY_CS1;
27
28#[derive(Debug)]
29pub enum UAttributesError {
30    ValidationError(String),
31    ParsingError(String),
32}
33
34impl UAttributesError {
35    pub fn validation_error<T>(message: T) -> UAttributesError
36    where
37        T: Into<String>,
38    {
39        Self::ValidationError(message.into())
40    }
41
42    pub fn parsing_error<T>(message: T) -> UAttributesError
43    where
44        T: Into<String>,
45    {
46        Self::ParsingError(message.into())
47    }
48}
49
50impl std::fmt::Display for UAttributesError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            Self::ValidationError(e) => f.write_fmt(format_args!("Validation failure: {e}")),
54            Self::ParsingError(e) => f.write_fmt(format_args!("Parsing error: {e}")),
55        }
56    }
57}
58
59impl std::error::Error for UAttributesError {}
60
61impl UAttributes {
62    /// Gets the type of message these are the attributes of.
63    ///
64    /// # Example
65    ///
66    /// ```rust
67    /// use up_rust::{UAttributes, UMessageType};
68    ///
69    /// let attribs = UAttributes {
70    ///   type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
71    ///   ..Default::default()
72    /// };
73    /// assert_eq!(attribs.type_(), Some(UMessageType::UMESSAGE_TYPE_PUBLISH));
74    /// ```
75    pub fn type_(&self) -> Option<UMessageType> {
76        self.type_.enum_value().ok()
77    }
78
79    /// Gets the type of message these are the attributes of.
80    ///
81    /// # Panics
82    ///
83    /// if the property has no value.
84    ///
85    /// # Example
86    ///
87    /// ```rust
88    /// use up_rust::{UAttributes, UMessageType};
89    ///
90    /// let attribs = UAttributes {
91    ///   type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
92    ///   ..Default::default()
93    /// };
94    /// assert_eq!(attribs.type_unchecked(), UMessageType::UMESSAGE_TYPE_PUBLISH);
95    /// ```
96    pub fn type_unchecked(&self) -> UMessageType {
97        self.type_().expect("message has no type")
98    }
99
100    /// Gets the identifier of the message these attributes belong to.
101    ///
102    /// # Example
103    ///
104    /// ```rust
105    /// use up_rust::{UAttributes, UUID};
106    ///
107    /// let msg_id = UUID::build();
108    /// let attribs = UAttributes {
109    ///   id: Some(msg_id.clone()).into(),
110    ///   ..Default::default()
111    /// };
112    /// assert_eq!(attribs.id(), Some(&msg_id));
113    /// ```
114    pub fn id(&self) -> Option<&UUID> {
115        self.id.as_ref()
116    }
117
118    /// Gets the identifier of the message these attributes belong to.
119    ///
120    /// # Panics
121    ///
122    /// if the property has no value.
123    ///
124    /// # Example
125    ///
126    /// ```rust
127    /// use up_rust::{UAttributes, UUID};
128    ///
129    /// let msg_id = UUID::build();
130    /// let attribs = UAttributes {
131    ///   id: Some(msg_id.clone()).into(),
132    ///   ..Default::default()
133    /// };
134    /// assert_eq!(attribs.id_unchecked(), &msg_id);
135    /// ```
136    pub fn id_unchecked(&self) -> &UUID {
137        self.id().expect("message has no ID")
138    }
139
140    /// Gets the source address of the message these attributes belong to.
141    ///
142    /// # Example
143    ///
144    /// ```rust
145    /// use up_rust::{UAttributes, UUri};
146    ///
147    /// let src = UUri::try_from_parts("vehicle", 0xaabb, 0x01, 0x9000).unwrap();
148    /// let attribs = UAttributes {
149    ///   source: Some(src.clone()).into(),
150    ///   ..Default::default()
151    /// };
152    /// assert_eq!(attribs.source(), Some(&src));
153    /// ```
154    pub fn source(&self) -> Option<&UUri> {
155        self.source.as_ref()
156    }
157
158    /// Gets the source address of the message these attributes belong to.
159    ///
160    /// # Panics
161    ///
162    /// if the property has no value.
163    ///
164    /// # Example
165    ///
166    /// ```rust
167    /// use up_rust::{UAttributes, UUri};
168    ///
169    /// let src = UUri::try_from_parts("vehicle", 0xaabb, 0x01, 0x9000).unwrap();
170    /// let attribs = UAttributes {
171    ///   source: Some(src.clone()).into(),
172    ///   ..Default::default()
173    /// };
174    /// assert_eq!(attribs.source_unchecked(), &src);
175    /// ```
176    pub fn source_unchecked(&self) -> &UUri {
177        self.source().expect("message has no source")
178    }
179
180    /// Gets the sink address of the message these attributes belong to.
181    ///
182    /// # Example
183    ///
184    /// ```rust
185    /// use up_rust::{UAttributes, UUri};
186    ///
187    /// let sink = UUri::try_from_parts("vehicle", 0xaabb, 0x01, 0x9000).unwrap();
188    /// let attribs = UAttributes {
189    ///   sink: Some(sink.clone()).into(),
190    ///   ..Default::default()
191    /// };
192    /// assert_eq!(attribs.sink(), Some(&sink));
193    /// ```
194    pub fn sink(&self) -> Option<&UUri> {
195        self.sink.as_ref()
196    }
197
198    /// Gets the sink address of the message these attributes belong to.
199    ///
200    /// # Panics
201    ///
202    /// if the property has no value.
203    ///
204    /// # Example
205    ///
206    /// ```rust
207    /// use up_rust::{UAttributes, UUri};
208    ///
209    /// let sink = UUri::try_from_parts("vehicle", 0xaabb, 0x01, 0x9000).unwrap();
210    /// let attribs = UAttributes {
211    ///   sink: Some(sink.clone()).into(),
212    ///   ..Default::default()
213    /// };
214    /// assert_eq!(attribs.sink_unchecked(), &sink);
215    /// ```
216    pub fn sink_unchecked(&self) -> &UUri {
217        self.sink().expect("message has no sink")
218    }
219
220    /// Gets the priority of the message these attributes belong to.
221    ///
222    /// # Example
223    ///
224    /// ```rust
225    /// use up_rust::{UAttributes, UPriority};
226    ///
227    /// let attribs = UAttributes {
228    ///   priority: UPriority::UPRIORITY_CS2.into(),
229    ///   ..Default::default()
230    /// };
231    /// assert_eq!(attribs.priority(), Some(UPriority::UPRIORITY_CS2));
232    /// ```
233    pub fn priority(&self) -> Option<UPriority> {
234        self.priority.enum_value().ok().map(|prio| {
235            if prio == UPriority::UPRIORITY_UNSPECIFIED {
236                crate::uattributes::UPRIORITY_DEFAULT
237            } else {
238                prio
239            }
240        })
241    }
242
243    /// Gets the priority of the message these attributes belong to.
244    ///
245    /// # Panics
246    ///
247    /// if the property has no value.
248    ///
249    /// # Example
250    ///
251    /// ```rust
252    /// use up_rust::{UAttributes, UPriority};
253    ///
254    /// let attribs = UAttributes {
255    ///   priority: UPriority::UPRIORITY_CS2.into(),
256    ///   ..Default::default()
257    /// };
258    /// assert_eq!(attribs.priority_unchecked(), UPriority::UPRIORITY_CS2);
259    /// ```
260    pub fn priority_unchecked(&self) -> UPriority {
261        self.priority().expect("message has no priority")
262    }
263
264    /// Gets the commstatus of the message these attributes belong to.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// use up_rust::{UAttributes, UCode};
270    ///
271    /// let attribs = UAttributes {
272    ///   commstatus: Some(UCode::OK.into()),
273    ///   ..Default::default()
274    /// };
275    /// assert_eq!(attribs.commstatus(), Some(UCode::OK));
276    /// ```
277    pub fn commstatus(&self) -> Option<UCode> {
278        self.commstatus.and_then(|v| v.enum_value().ok())
279    }
280
281    /// Gets the commstatus of the message these attributes belong to.
282    ///
283    /// # Panics
284    ///
285    /// if the property has no value.
286    ///
287    /// # Example
288    ///
289    /// ```rust
290    /// use up_rust::{UAttributes, UCode};
291    ///
292    /// let attribs = UAttributes {
293    ///   commstatus: Some(UCode::OK.into()),
294    ///   ..Default::default()
295    /// };
296    /// assert_eq!(attribs.commstatus_unchecked(), UCode::OK);
297    /// ```
298    pub fn commstatus_unchecked(&self) -> UCode {
299        self.commstatus().expect("message has no commstatus")
300    }
301
302    /// Gets the time-to-live of the message these attributes belong to.
303    ///
304    /// # Returns
305    ///
306    /// the time-to-live in milliseconds.
307    ///
308    /// # Example
309    ///
310    /// ```rust
311    /// use up_rust::{UAttributes};
312    ///
313    /// let attribs = UAttributes {
314    ///   ttl: Some(10_000),
315    ///   ..Default::default()
316    /// };
317    /// assert_eq!(attribs.ttl(), Some(10_000));
318    /// ```
319    pub fn ttl(&self) -> Option<u32> {
320        self.ttl
321    }
322
323    /// Gets the time-to-live of the message these attributes belong to.
324    ///
325    /// # Returns
326    ///
327    /// the time-to-live in milliseconds.
328    ///
329    /// # Panics
330    ///
331    /// if the property has no value.
332    ///
333    /// # Example
334    ///
335    /// ```rust
336    /// use up_rust::{UAttributes};
337    ///
338    /// let attribs = UAttributes {
339    ///   ttl: Some(10_000),
340    ///   ..Default::default()
341    /// };
342    /// assert_eq!(attribs.ttl_unchecked(), 10_000);
343    /// ```
344    pub fn ttl_unchecked(&self) -> u32 {
345        self.ttl().expect("message has no time-to-live")
346    }
347
348    /// Gets the permission level of the message these attributes belong to.
349    ///
350    /// # Example
351    ///
352    /// ```rust
353    /// use up_rust::{UAttributes};
354    ///
355    /// let attribs = UAttributes {
356    ///   permission_level: Some(10),
357    ///   ..Default::default()
358    /// };
359    /// assert_eq!(attribs.permission_level(), Some(10));
360    /// ```
361    pub fn permission_level(&self) -> Option<u32> {
362        self.permission_level
363    }
364
365    /// Gets the token of the message these attributes belong to.
366    ///
367    /// # Example
368    ///
369    /// ```rust
370    /// use up_rust::{UAttributes};
371    ///
372    /// let token = "my_token".to_string();
373    /// let attribs = UAttributes {
374    ///   token: Some(token.clone()),
375    ///   ..Default::default()
376    /// };
377    /// assert_eq!(attribs.token(), Some(&token));
378    /// ```
379    pub fn token(&self) -> Option<&String> {
380        self.token.as_ref()
381    }
382
383    /// Gets the traceparent of the message these attributes belong to.
384    ///
385    /// # Example
386    ///
387    /// ```rust
388    /// use up_rust::{UAttributes};
389    ///
390    /// let traceparent = "my_traceparent".to_string();
391    /// let attribs = UAttributes {
392    ///   traceparent: Some(traceparent.clone()),
393    ///   ..Default::default()
394    /// };
395    /// assert_eq!(attribs.traceparent(), Some(&traceparent));
396    /// ```
397    pub fn traceparent(&self) -> Option<&String> {
398        self.traceparent.as_ref()
399    }
400
401    /// Gets the request identifier of the message these attributes belong to.
402    ///
403    /// # Example
404    ///
405    /// ```rust
406    /// use up_rust::{UAttributes, UUID};
407    ///
408    /// let req_id = UUID::build();
409    /// let attribs = UAttributes {
410    ///   reqid: Some(req_id.clone()).into(),
411    ///   ..Default::default()
412    /// };
413    /// assert_eq!(attribs.request_id(), Some(&req_id));
414    /// ```
415    pub fn request_id(&self) -> Option<&UUID> {
416        self.reqid.as_ref()
417    }
418
419    /// Gets the request identifier of the message these attributes belong to.
420    ///
421    /// # Panics
422    ///
423    /// if the property has no value.
424    ///
425    /// # Example
426    ///
427    /// ```rust
428    /// use up_rust::{UAttributes, UUID};
429    ///
430    /// let req_id = UUID::build();
431    /// let attribs = UAttributes {
432    ///   reqid: Some(req_id.clone()).into(),
433    ///   ..Default::default()
434    /// };
435    /// assert_eq!(attribs.request_id_unchecked(), &req_id);
436    /// ```
437    pub fn request_id_unchecked(&self) -> &UUID {
438        self.request_id().expect("message has no request ID")
439    }
440
441    /// Gets the payload format of the message these attributes belong to.
442    ///
443    /// # Example
444    ///
445    /// ```rust
446    /// use up_rust::{UAttributes, UPayloadFormat};
447    ///
448    /// let attribs = UAttributes {
449    ///   payload_format: UPayloadFormat::UPAYLOAD_FORMAT_JSON.into(),
450    ///   ..Default::default()
451    /// };
452    /// assert_eq!(attribs.payload_format(), Some(UPayloadFormat::UPAYLOAD_FORMAT_JSON));
453    /// ```
454    pub fn payload_format(&self) -> Option<UPayloadFormat> {
455        self.payload_format.enum_value().ok()
456    }
457
458    /// Gets the payload format of the message these attributes belong to.
459    ///
460    /// # Panics
461    ///
462    /// if the property has no value.
463    ///
464    /// # Example
465    ///
466    /// ```rust
467    /// use up_rust::{UAttributes, UPayloadFormat};
468    ///
469    /// let attribs = UAttributes {
470    ///   payload_format: UPayloadFormat::UPAYLOAD_FORMAT_JSON.into(),
471    ///   ..Default::default()
472    /// };
473    /// assert_eq!(attribs.payload_format_unchecked(), UPayloadFormat::UPAYLOAD_FORMAT_JSON);
474    /// ```
475    pub fn payload_format_unchecked(&self) -> UPayloadFormat {
476        self.payload_format()
477            .expect("message has no payload format")
478    }
479
480    /// Checks if a given priority class is the default priority class.
481    ///
482    /// Messages that do not have a priority class set explicity, are assigned to
483    /// the default priority class.
484    pub(crate) fn is_default_priority(prio: UPriority) -> bool {
485        prio == UPRIORITY_DEFAULT
486    }
487
488    /// Checks if these are the attributes for a Publish message.
489    ///
490    /// # Examples
491    ///
492    /// ```rust
493    /// use up_rust::{UAttributes, UMessageType};
494    ///
495    /// let attribs = UAttributes {
496    ///   type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
497    ///   ..Default::default()
498    /// };
499    /// assert!(attribs.is_publish());
500    /// ```
501    pub fn is_publish(&self) -> bool {
502        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_PUBLISH)
503    }
504
505    /// Checks if these are the attributes for an RPC Request message.
506    ///
507    /// # Examples
508    ///
509    /// ```rust
510    /// use up_rust::{UAttributes, UMessageType};
511    ///
512    /// let attribs = UAttributes {
513    ///   type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
514    ///   ..Default::default()
515    /// };
516    /// assert!(attribs.is_request());
517    /// ```
518    pub fn is_request(&self) -> bool {
519        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_REQUEST)
520    }
521
522    /// Checks if these are the attributes for an RPC Response message.
523    ///
524    /// # Examples
525    ///
526    /// ```rust
527    /// use up_rust::{UAttributes, UMessageType};
528    ///
529    /// let attribs = UAttributes {
530    ///   type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
531    ///   ..Default::default()
532    /// };
533    /// assert!(attribs.is_response());
534    /// ```
535    pub fn is_response(&self) -> bool {
536        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_RESPONSE)
537    }
538
539    /// Checks if these are the attributes for a Notification message.
540    ///
541    /// # Examples
542    ///
543    /// ```rust
544    /// use up_rust::{UAttributes, UMessageType};
545    ///
546    /// let attribs = UAttributes {
547    ///   type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
548    ///   ..Default::default()
549    /// };
550    /// assert!(attribs.is_notification());
551    /// ```
552    pub fn is_notification(&self) -> bool {
553        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_NOTIFICATION)
554    }
555
556    /// Checks if the message that is described by these attributes should be considered expired.
557    ///
558    /// # Errors
559    ///
560    /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
561    /// * the current system time cannot be determined, or
562    /// * the message has expired according to the timestamp extracted from [`Self::id`] and the time-to-live value.
563    pub fn check_expired(&self) -> Result<(), UAttributesError> {
564        if let Some(ttl) = self.ttl {
565            if ttl == 0 {
566                return Ok(());
567            }
568        }
569        SystemTime::now()
570            .duration_since(SystemTime::UNIX_EPOCH)
571            .map_err(|_e| {
572                UAttributesError::validation_error("Cannot determine current system time")
573            })
574            .and_then(|duration_since_epoch| {
575                self.check_expired_for_reference(duration_since_epoch.as_millis())
576            })
577    }
578
579    /// Checks if the message that is described by these attributes should be considered expired.
580    ///
581    /// # Arguments
582    /// * `reference_time` - The reference time as a `Duration` since UNIX epoch. The check will be performed in relation to this point in time.
583    ///
584    /// # Errors
585    ///
586    /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
587    /// the message has expired according to the timestamp extracted from [`Self::id`], the
588    /// time-to-live value and the provided reference time.
589    pub fn check_expired_for_reference(
590        &self,
591        reference_time: u128,
592    ) -> Result<(), UAttributesError> {
593        let ttl = match self.ttl {
594            Some(t) if t > 0 => u128::from(t),
595            _ => return Ok(()),
596        };
597
598        if let Some(creation_time) = self.id.as_ref().and_then(UUID::get_time) {
599            if (creation_time as u128).saturating_add(ttl) <= reference_time {
600                return Err(UAttributesError::validation_error("Message has expired"));
601            }
602        }
603        Ok(())
604    }
605}
606
607#[cfg(test)]
608mod tests {
609    use std::time::UNIX_EPOCH;
610
611    use super::*;
612    use test_case::test_case;
613
614    /// Creates a UUID for a given creation time offset.
615    ///
616    /// # Note
617    ///
618    /// For internal testing purposes only. For end-users, please use [`UUID::build()`]
619    fn build_for_time_offset(offset_millis: i64) -> UUID {
620        let duration_since_unix_epoch = SystemTime::now()
621            .duration_since(UNIX_EPOCH)
622            .expect("current system time is set to a point in time before UNIX Epoch");
623        let now_as_millis_since_epoch: u64 = u64::try_from(duration_since_unix_epoch.as_millis())
624            .expect("current system time is too far in the future");
625        let creation_timestamp = now_as_millis_since_epoch
626            .checked_add_signed(offset_millis)
627            .unwrap();
628        UUID::build_for_timestamp_millis(creation_timestamp)
629    }
630
631    #[test_case(None, None, false; "for message without ID nor TTL")]
632    #[test_case(None, Some(0), false; "for message without ID with TTL 0")]
633    #[test_case(None, Some(500), false; "for message without ID with TTL")]
634    #[test_case(Some(build_for_time_offset(-1000)), None, false; "for past message without TTL")]
635    #[test_case(Some(build_for_time_offset(-1000)), Some(0), false; "for past message with TTL 0")]
636    #[test_case(Some(build_for_time_offset(-1000)), Some(500), true; "for past message with expired TTL")]
637    #[test_case(Some(build_for_time_offset(-1000)), Some(2000), false; "for past message with non-expired TTL")]
638    #[test_case(Some(build_for_time_offset(1000)), Some(2000), false; "for future message with TTL")]
639    #[test_case(Some(build_for_time_offset(1000)), None, false; "for future message without TTL")]
640    fn test_is_expired(id: Option<UUID>, ttl: Option<u32>, should_be_expired: bool) {
641        let attributes = UAttributes {
642            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
643            priority: UPriority::UPRIORITY_CS1.into(),
644            id: id.into(),
645            ttl,
646            ..Default::default()
647        };
648
649        assert!(attributes.check_expired().is_err() == should_be_expired);
650    }
651}