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}