up_rust/uattributes/
uattributesvalidator.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
14use protobuf::Enum;
15
16use crate::{UAttributes, UMessageType, UPriority, UUri};
17
18use crate::UAttributesError;
19
20/// `UAttributes` is the struct that defines the Payload. It serves as the configuration for various aspects
21/// like time to live, priority, security tokens, and more. Each variant of `UAttributes` defines a different
22/// type of message payload. The payload could represent a simple published payload with some state change,
23/// an RPC request payload, or an RPC response payload.
24///
25/// `UAttributesValidator` is a trait implemented by all validators for `UAttributes`. It provides functionality
26/// to help validate that a given `UAttributes` instance is correctly configured to define the Payload.
27pub trait UAttributesValidator: Send {
28    /// Checks if a given set of attributes complies with the rules specified for
29    /// the type of message they describe.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if the attributes are not consistent with the rules specified for the message type.
34    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
35
36    /// Verifies that this validator is appropriate for a set of attributes.
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if [`UAttributes::type_`] does not match the type returned by [`UAttributesValidator::message_type`].
41    fn validate_type(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
42        let expected_type = self.message_type();
43        match attributes.type_.enum_value() {
44            Ok(mt) if mt == expected_type => Ok(()),
45            Ok(mt) => Err(UAttributesError::validation_error(format!(
46                "Wrong Message Type [{}]",
47                mt.to_cloudevent_type()
48            ))),
49            Err(unknown_code) => Err(UAttributesError::validation_error(format!(
50                "Unknown Message Type code [{unknown_code}]"
51            ))),
52        }
53    }
54
55    /// Verifies that a set of attributes contains a valid message ID.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if [`UAttributes::id`] does not contain a [valid uProtocol UUID](`crate::UUID::is_uprotocol_uuid`).
60    fn validate_id(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
61        // [impl->dsn~up-attributes-id~1]
62        if attributes
63            .id
64            .as_ref()
65            .is_some_and(|id| id.is_uprotocol_uuid())
66        {
67            Ok(())
68        } else {
69            Err(UAttributesError::validation_error(
70                "Attributes must contain valid uProtocol UUID in id property",
71            ))
72        }
73    }
74
75    /// Returns the type of message that this validator can be used with.
76    fn message_type(&self) -> UMessageType;
77
78    /// Verifies that a set of attributes contains a valid source URI.
79    ///
80    /// # Errors
81    ///
82    /// If the [`UAttributes::source`] property does not contain a valid URI as required by the type of message, an error is returned.
83    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
84
85    /// Verifies that a set of attributes contains a valid sink URI.
86    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
87}
88
89/// Verifies that a set of attributes contains a priority that is appropriate for an RPC request message.
90///
91/// # Errors
92///
93/// If [`UAttributes::priority`] contains a value that is less [`UPriority::UPRIORITY_CS4`].
94pub fn validate_rpc_priority(attributes: &UAttributes) -> Result<(), UAttributesError> {
95    attributes
96        .priority
97        .enum_value()
98        .map_err(|unknown_code| {
99            UAttributesError::ValidationError(format!(
100                "RPC message must have a valid priority [{unknown_code}]"
101            ))
102        })
103        .and_then(|prio| {
104            if prio.value() < UPriority::UPRIORITY_CS4.value() {
105                Err(UAttributesError::ValidationError(
106                    "RPC message must have a priority of at least CS4".to_string(),
107                ))
108            } else {
109                Ok(())
110            }
111        })
112}
113
114/// Enum that hold the implementations of uattributesValidator according to type.
115pub enum UAttributesValidators {
116    Publish,
117    Notification,
118    Request,
119    Response,
120}
121
122impl UAttributesValidators {
123    /// Gets the validator corresponding to this enum value.
124    ///
125    /// # Examples
126    ///
127    /// ```rust
128    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
129    ///
130    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
131    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
132    /// let attributes = UAttributes {
133    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
134    ///    id: Some(UUID::build()).into(),
135    ///    source: Some(topic).into(),
136    ///    ..Default::default()
137    /// };
138    /// let validator = UAttributesValidators::Publish.validator();
139    /// assert!(validator.validate(&attributes).is_ok());
140    /// # Ok(())
141    /// # }
142    /// ```
143    pub fn validator(&self) -> Box<dyn UAttributesValidator> {
144        match self {
145            UAttributesValidators::Publish => Box::new(PublishValidator),
146            UAttributesValidators::Notification => Box::new(NotificationValidator),
147            UAttributesValidators::Request => Box::new(RequestValidator),
148            UAttributesValidators::Response => Box::new(ResponseValidator),
149        }
150    }
151
152    /// Gets a validator that can be used to check a given set of attributes.
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
158    ///
159    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
160    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
161    /// let attributes = UAttributes {
162    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
163    ///    id: Some(UUID::build()).into(),
164    ///    source: Some(topic).into(),
165    ///    ..Default::default()
166    /// };
167    /// let validator = UAttributesValidators::get_validator_for_attributes(&attributes);
168    /// assert!(validator.validate(&attributes).is_ok());
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
173        Self::get_validator(attributes.type_.enum_value_or_default())
174    }
175
176    /// Gets a validator that can be used to check attributes of a given type of message.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
182    ///
183    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
184    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
185    /// let attributes = UAttributes {
186    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
187    ///    id: Some(UUID::build()).into(),
188    ///    source: Some(topic).into(),
189    ///    ..Default::default()
190    /// };
191    /// let validator = UAttributesValidators::get_validator(UMessageType::UMESSAGE_TYPE_PUBLISH);
192    /// assert!(validator.validate(&attributes).is_ok());
193    /// # Ok(())
194    /// # }
195    /// ```
196    pub fn get_validator(message_type: UMessageType) -> Box<dyn UAttributesValidator> {
197        match message_type {
198            UMessageType::UMESSAGE_TYPE_REQUEST => Box::new(RequestValidator),
199            UMessageType::UMESSAGE_TYPE_RESPONSE => Box::new(ResponseValidator),
200            UMessageType::UMESSAGE_TYPE_NOTIFICATION => Box::new(NotificationValidator),
201            _ => Box::new(PublishValidator),
202        }
203    }
204}
205
206/// Validates attributes describing a Publish message.
207pub struct PublishValidator;
208
209impl UAttributesValidator for PublishValidator {
210    fn message_type(&self) -> UMessageType {
211        UMessageType::UMESSAGE_TYPE_PUBLISH
212    }
213
214    /// Checks if a given set of attributes complies with the rules specified for
215    /// publish messages.
216    ///
217    /// # Errors
218    ///
219    /// Returns an error if any of the following checks fail for the given attributes:
220    ///
221    /// * [`UAttributesValidator::validate_type`]
222    /// * [`UAttributesValidator::validate_id`]
223    /// * [`UAttributesValidator::validate_source`]
224    /// * [`UAttributesValidator::validate_sink`]
225    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
226        let error_message = vec![
227            self.validate_type(attributes),
228            self.validate_id(attributes),
229            self.validate_source(attributes),
230            self.validate_sink(attributes),
231        ]
232        .into_iter()
233        .filter_map(Result::err)
234        .map(|e| e.to_string())
235        .collect::<Vec<_>>()
236        .join("; ");
237
238        if error_message.is_empty() {
239            Ok(())
240        } else {
241            Err(UAttributesError::validation_error(error_message))
242        }
243    }
244
245    /// Verifies that attributes for a publish message contain a valid source URI.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error
250    ///
251    /// * if the attributes do not contain a source URI, or
252    /// * if the source URI contains any wildcards, or
253    /// * if the source URI has a resource ID of 0.
254    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
255        // [impl->dsn~up-attributes-publish-source~1]
256        if let Some(source) = attributes.source.as_ref() {
257            source
258                .verify_event()
259                .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
260        } else {
261            Err(UAttributesError::validation_error(
262                "Attributes for a publish message must contain a source URI",
263            ))
264        }
265    }
266
267    /// Verifies that attributes for a publish message do not contain a sink URI.
268    ///
269    /// # Errors
270    ///
271    /// If the [`UAttributes::sink`] property contains any URI, an error is returned.
272    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
273        // [impl->dsn~up-attributes-publish-sink~1]
274        if attributes.sink.as_ref().is_some() {
275            Err(UAttributesError::validation_error(
276                "Attributes for a publish message must not contain a sink URI",
277            ))
278        } else {
279            Ok(())
280        }
281    }
282}
283
284/// Validates attributes describing a Notification message.
285pub struct NotificationValidator;
286
287impl UAttributesValidator for NotificationValidator {
288    fn message_type(&self) -> UMessageType {
289        UMessageType::UMESSAGE_TYPE_NOTIFICATION
290    }
291
292    /// Checks if a given set of attributes complies with the rules specified for
293    /// notification messages.
294    ///
295    /// # Errors
296    ///
297    /// Returns an error if any of the following checks fail for the given attributes:
298    ///
299    /// * [`UAttributesValidator::validate_type`]
300    /// * [`UAttributesValidator::validate_id`]
301    /// * [`UAttributesValidator::validate_source`]
302    /// * [`UAttributesValidator::validate_sink`]
303    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
304        let error_message = vec![
305            self.validate_type(attributes),
306            self.validate_id(attributes),
307            self.validate_source(attributes),
308            self.validate_sink(attributes),
309        ]
310        .into_iter()
311        .filter_map(Result::err)
312        .map(|e| e.to_string())
313        .collect::<Vec<_>>()
314        .join("; ");
315
316        if error_message.is_empty() {
317            Ok(())
318        } else {
319            Err(UAttributesError::validation_error(error_message))
320        }
321    }
322
323    /// Verifies that attributes for a notification message contain a source URI.
324    ///
325    /// # Errors
326    ///
327    /// Returns an error
328    ///
329    /// * if the attributes do not contain a source URI, or
330    /// * if the source URI is an RPC response URI, or
331    /// * if the source URI contains any wildcards.
332    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
333        // [impl->dsn~up-attributes-notification-source~1]
334        if let Some(source) = attributes.source.as_ref() {
335            if source.is_rpc_response() {
336                Err(UAttributesError::validation_error(
337                    "Origin must not be an RPC response URI",
338                ))
339            } else {
340                source.verify_no_wildcards().map_err(|e| {
341                    UAttributesError::validation_error(format!("Invalid source URI: {e}"))
342                })
343            }
344        } else {
345            Err(UAttributesError::validation_error(
346                "Attributes must contain a source URI",
347            ))
348        }
349    }
350
351    /// Verifies that attributes for a notification message contain a sink URI.
352    ///
353    /// # Errors
354    ///
355    /// Returns an error
356    ///
357    /// * if the attributes do not contain a sink URI, or
358    /// * if the sink URI's resource ID is != 0, or
359    /// * if the sink URI contains any wildcards.
360    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
361        // [impl->dsn~up-attributes-notification-sink~1]
362        if let Some(sink) = attributes.sink.as_ref() {
363            if !sink.is_notification_destination() {
364                Err(UAttributesError::validation_error(
365                    "Destination's resource ID must be 0",
366                ))
367            } else {
368                sink.verify_no_wildcards().map_err(|e| {
369                    UAttributesError::validation_error(format!("Invalid sink URI: {e}"))
370                })
371            }
372        } else {
373            Err(UAttributesError::validation_error(
374                "Attributes for a notification message must contain a sink URI",
375            ))
376        }
377    }
378}
379
380/// Validate `UAttributes` with type `UMessageType::Request`
381pub struct RequestValidator;
382
383impl RequestValidator {
384    /// Verifies that a set of attributes representing an RPC request contain a valid time-to-live.
385    ///
386    /// # Errors
387    ///
388    /// Returns an error if [`UAttributes::ttl`] (time-to-live) is empty or contains a value less than 1.
389    pub fn validate_ttl(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
390        // [impl->dsn~up-attributes-request-ttl~1]
391        match attributes.ttl {
392            Some(ttl) if ttl > 0 => Ok(()),
393            Some(invalid_ttl) => Err(UAttributesError::validation_error(format!(
394                "RPC request message's TTL must be a positive integer [{invalid_ttl}]"
395            ))),
396            None => Err(UAttributesError::validation_error(
397                "RPC request message must contain a TTL",
398            )),
399        }
400    }
401}
402
403impl UAttributesValidator for RequestValidator {
404    fn message_type(&self) -> UMessageType {
405        UMessageType::UMESSAGE_TYPE_REQUEST
406    }
407
408    /// Checks if a given set of attributes complies with the rules specified for
409    /// RPC request messages.
410    ///
411    /// # Errors
412    ///
413    /// Returns an error if any of the following checks fail for the given attributes:
414    ///
415    /// * [`UAttributesValidator::validate_type`]
416    /// * [`UAttributesValidator::validate_id`]
417    /// * [`RequestValidator::validate_ttl`]
418    /// * [`UAttributesValidator::validate_source`]
419    /// * [`UAttributesValidator::validate_sink`]
420    /// * `validate_rpc_priority`
421    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
422        let error_message = vec![
423            self.validate_type(attributes),
424            self.validate_id(attributes),
425            self.validate_ttl(attributes),
426            self.validate_source(attributes),
427            self.validate_sink(attributes),
428            validate_rpc_priority(attributes),
429        ]
430        .into_iter()
431        .filter_map(Result::err)
432        .map(|e| e.to_string())
433        .collect::<Vec<_>>()
434        .join("; ");
435
436        if error_message.is_empty() {
437            Ok(())
438        } else {
439            Err(UAttributesError::validation_error(error_message))
440        }
441    }
442
443    /// Verifies that attributes for a message representing an RPC request contain a reply-to-address.
444    ///
445    /// # Errors
446    ///
447    /// Returns an error if the [`UAttributes::source`] property does not contain a valid reply-to-address according to
448    /// [`UUri::verify_rpc_response`].
449    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
450        // [impl->dsn~up-attributes-request-source~1]
451        if let Some(source) = attributes.source.as_ref() {
452            UUri::verify_rpc_response(source)
453                .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
454        } else {
455            Err(UAttributesError::validation_error("Attributes for a request message must contain a reply-to address in the source property"))
456        }
457    }
458
459    /// Verifies that attributes for a message representing an RPC request indicate the method to invoke.
460    ///
461    /// # Errors
462    ///
463    /// Returns an erro if the [`UAttributes::sink`] property does not contain a URI representing a method according to
464    /// [`UUri::verify_rpc_method`].
465    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
466        // [impl->dsn~up-attributes-request-sink~1]
467        if let Some(sink) = attributes.sink.as_ref() {
468            UUri::verify_rpc_method(sink)
469                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {e}")))
470        } else {
471            Err(UAttributesError::validation_error("Attributes for a request message must contain a method-to-invoke in the sink property"))
472        }
473    }
474}
475
476/// Validate `UAttributes` with type `UMessageType::Response`
477pub struct ResponseValidator;
478
479impl ResponseValidator {
480    /// Verifies that the attributes contain a valid request ID.
481    ///
482    /// # Errors
483    ///
484    /// Returns an error if [`UAttributes::reqid`] is empty or contains a value which is not
485    /// a [valid uProtocol UUID](`crate::UUID::is_uprotocol_uuid`).
486    pub fn validate_reqid(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
487        if !attributes
488            .reqid
489            .as_ref()
490            .is_some_and(|id| id.is_uprotocol_uuid())
491        {
492            Err(UAttributesError::validation_error(
493                "Request ID is not a valid uProtocol UUID",
494            ))
495        } else {
496            Ok(())
497        }
498    }
499
500    /// Verifies that a set of attributes contains a valid communication status.
501    ///
502    /// # Errors
503    ///
504    /// Returns an error if [`UAttributes::commstatus`] does not contain a value that is a `UCode`.
505    pub fn validate_commstatus(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
506        if let Some(status) = attributes.commstatus {
507            match status.enum_value() {
508                Ok(_) => {
509                    return Ok(());
510                }
511                Err(e) => {
512                    return Err(UAttributesError::validation_error(format!(
513                        "Invalid Communication Status code: {e}"
514                    )));
515                }
516            }
517        }
518        Ok(())
519    }
520}
521
522impl UAttributesValidator for ResponseValidator {
523    fn message_type(&self) -> UMessageType {
524        UMessageType::UMESSAGE_TYPE_RESPONSE
525    }
526
527    /// Checks if a given set of attributes complies with the rules specified for
528    /// RPC response messages.
529    ///
530    /// # Errors
531    ///
532    /// Returns an error if any of the following checks fail for the given attributes:
533    ///
534    /// * [`UAttributesValidator::validate_type`]
535    /// * [`UAttributesValidator::validate_id`]
536    /// * [`UAttributesValidator::validate_source`]
537    /// * [`UAttributesValidator::validate_sink`]
538    /// * [`ResponseValidator::validate_reqid`]
539    /// * [`ResponseValidator::validate_commstatus`]
540    /// * `validate_rpc_priority`
541    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
542        let error_message = vec![
543            self.validate_type(attributes),
544            self.validate_id(attributes),
545            self.validate_source(attributes),
546            self.validate_sink(attributes),
547            self.validate_reqid(attributes),
548            self.validate_commstatus(attributes),
549            validate_rpc_priority(attributes),
550        ]
551        .into_iter()
552        .filter_map(Result::err)
553        .map(|e| e.to_string())
554        .collect::<Vec<_>>()
555        .join("; ");
556
557        if error_message.is_empty() {
558            Ok(())
559        } else {
560            Err(UAttributesError::validation_error(error_message))
561        }
562    }
563
564    /// Verifies that attributes for a message representing an RPC response indicate the method that has
565    /// been invoked.
566    ///  
567    /// # Errors
568    ///
569    /// Returns an error if the [`UAttributes::source`] property does not contain a URI representing a method according to
570    /// [`UUri::verify_rpc_method`].
571    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
572        // [impl->dsn~up-attributes-response-source~1]
573        if let Some(source) = attributes.source.as_ref() {
574            UUri::verify_rpc_method(source)
575                .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
576        } else {
577            Err(UAttributesError::validation_error("Missing Source"))
578        }
579    }
580
581    /// Verifies that attributes for a message representing an RPC response contain a valid
582    /// reply-to-address.
583    ///
584    /// # Errors
585    ///
586    /// Returns an error if the [`UAttributes::sink`] property does not contain a valid reply-to-address according to
587    /// [`UUri::verify_rpc_response`].
588    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
589        // [impl->dsn~up-attributes-response-sink~1]
590        if let Some(sink) = &attributes.sink.as_ref() {
591            UUri::verify_rpc_response(sink)
592                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {e}")))
593        } else {
594            Err(UAttributesError::validation_error("Missing Sink"))
595        }
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use protobuf::EnumOrUnknown;
602    use test_case::test_case;
603
604    use super::*;
605    use crate::{UCode, UPriority, UUri, UUID};
606
607    #[test]
608    fn test_validate_type_fails_for_unknown_type_code() {
609        let attributes = UAttributes {
610            type_: EnumOrUnknown::from_i32(20),
611            ..Default::default()
612        };
613        assert!(UAttributesValidators::Publish
614            .validator()
615            .validate_type(&attributes)
616            .is_err());
617        assert!(UAttributesValidators::Notification
618            .validator()
619            .validate_type(&attributes)
620            .is_err());
621        assert!(UAttributesValidators::Request
622            .validator()
623            .validate_type(&attributes)
624            .is_err());
625        assert!(UAttributesValidators::Response
626            .validator()
627            .validate_type(&attributes)
628            .is_err());
629    }
630
631    #[test_case(UMessageType::UMESSAGE_TYPE_UNSPECIFIED, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Unspecified message")]
632    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Publish message")]
633    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, UMessageType::UMESSAGE_TYPE_NOTIFICATION; "succeeds for Notification message")]
634    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, UMessageType::UMESSAGE_TYPE_REQUEST; "succeeds for Request message")]
635    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, UMessageType::UMESSAGE_TYPE_RESPONSE; "succeeds for Response message")]
636    fn test_get_validator_returns_matching_validator(
637        message_type: UMessageType,
638        expected_validator_type: UMessageType,
639    ) {
640        let validator: Box<dyn UAttributesValidator> =
641            UAttributesValidators::get_validator(message_type);
642        assert_eq!(validator.message_type(), expected_validator_type);
643    }
644
645    #[test_case(Some(UUID::build()), Some(publish_topic()), None, None, true; "succeeds for topic only")]
646    // [utest->dsn~up-attributes-publish-sink~1]
647    #[test_case(Some(UUID::build()), Some(publish_topic()), Some(destination()), None, false; "fails for message containing destination")]
648    #[test_case(Some(UUID::build()), Some(publish_topic()), None, Some(100), true; "succeeds for valid attributes")]
649    // [utest->dsn~up-attributes-publish-source~1]
650    #[test_case(Some(UUID::build()), None, None, None, false; "fails for missing topic")]
651    // [utest->dsn~up-attributes-publish-source~1]
652    #[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x54, ..Default::default()}), None, None, false; "fails for invalid topic")]
653    // [utest->dsn~up-attributes-id~1]
654    #[test_case(None, Some(publish_topic()), None, None, false; "fails for missing message ID")]
655    // [utest->dsn~up-attributes-id~1]
656    #[test_case(
657        Some(UUID {
658            // invalid UUID version (not 0b1000 but 0b1010)
659            msb: 0x000000000001C000u64,
660            lsb: 0x8000000000000000u64,
661            ..Default::default()
662        }),
663        Some(publish_topic()),
664        None,
665        None,
666        false;
667        "fails for invalid message id")]
668    fn test_validate_attributes_for_publish_message(
669        id: Option<UUID>,
670        source: Option<UUri>,
671        sink: Option<UUri>,
672        ttl: Option<u32>,
673        expected_result: bool,
674    ) {
675        let attributes = UAttributes {
676            type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
677            id: id.into(),
678            priority: UPriority::UPRIORITY_CS1.into(),
679            source: source.into(),
680            sink: sink.into(),
681            ttl,
682            ..Default::default()
683        };
684        let validator = UAttributesValidators::Publish.validator();
685        let status = validator.validate(&attributes);
686        assert!(status.is_ok() == expected_result);
687        if status.is_ok() {
688            assert!(UAttributesValidators::Notification
689                .validator()
690                .validate(&attributes)
691                .is_err());
692            assert!(UAttributesValidators::Request
693                .validator()
694                .validate(&attributes)
695                .is_err());
696            assert!(UAttributesValidators::Response
697                .validator()
698                .validate(&attributes)
699                .is_err());
700        }
701    }
702
703    // [utest->dsn~up-attributes-notification-sink~1]
704    #[test_case(Some(UUID::build()), Some(origin()), None, None, false; "fails for missing destination")]
705    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), None, true; "succeeds for both origin and destination")]
706    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), Some(100), true; "succeeds for valid attributes")]
707    // [utest->dsn~up-attributes-notification-source~1]
708    #[test_case(Some(UUID::build()), None, Some(destination()), None, false; "fails for missing origin")]
709    // [utest->dsn~up-attributes-notification-source~1]
710    #[test_case(Some(UUID::build()), Some(UUri::default()), Some(destination()), None, false; "fails for invalid origin")]
711    // [utest->dsn~up-attributes-notification-sink~1]
712    #[test_case(Some(UUID::build()), Some(origin()), Some(UUri { ue_id: 0xabcd, ue_version_major: 0x01, resource_id: 0x0011, ..Default::default() }), None, false; "fails for invalid destination")]
713    #[test_case(Some(UUID::build()), None, None, None, false; "fails for neither origin nor destination")]
714    // [utest->dsn~up-attributes-id~1]
715    #[test_case(None, Some(origin()), Some(destination()), None, false; "fails for missing message ID")]
716    // [utest->dsn~up-attributes-id~1]
717    #[test_case(
718        Some(UUID {
719            // invalid UUID version (not 0b1000 but 0b1010)
720            msb: 0x000000000001C000u64,
721            lsb: 0x8000000000000000u64,
722            ..Default::default()
723        }),
724        Some(origin()),
725        Some(destination()),
726        None,
727        false;
728        "fails for invalid message id")]
729    fn test_validate_attributes_for_notification_message(
730        id: Option<UUID>,
731        source: Option<UUri>,
732        sink: Option<UUri>,
733        ttl: Option<u32>,
734        expected_result: bool,
735    ) {
736        let attributes = UAttributes {
737            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
738            id: id.into(),
739            priority: UPriority::UPRIORITY_CS1.into(),
740            source: source.into(),
741            sink: sink.into(),
742            ttl,
743            ..Default::default()
744        };
745        let validator = UAttributesValidators::Notification.validator();
746        let status = validator.validate(&attributes);
747        assert!(status.is_ok() == expected_result);
748        if status.is_ok() {
749            assert!(UAttributesValidators::Publish
750                .validator()
751                .validate(&attributes)
752                .is_err());
753            assert!(UAttributesValidators::Request
754                .validator()
755                .validate(&attributes)
756                .is_err());
757            assert!(UAttributesValidators::Response
758                .validator()
759                .validate(&attributes)
760                .is_err());
761        }
762    }
763
764    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, true; "succeeds for mandatory attributes")]
765    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), Some(String::from("token")), true; "succeeds for valid attributes")]
766    // [utest->dsn~up-attributes-id~1]
767    #[test_case(None, Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), Some(String::from("token")), false; "fails for missing message ID")]
768    // [utest->dsn~up-attributes-id~1]
769    #[test_case(
770        Some(UUID {
771            // invalid UUID version (not 0b1000 but 0b1010)
772            msb: 0x000000000001C000u64,
773            lsb: 0x8000000000000000u64,
774            ..Default::default()
775        }),
776        Some(method_to_invoke()),
777        Some(reply_to_address()),
778        None,
779        Some(2000),
780        Some(UPriority::UPRIORITY_CS4),
781        None,
782        false;
783        "fails for invalid message id")]
784    // [utest->dsn~up-attributes-request-source~1]
785    #[test_case(Some(UUID::build()), Some(method_to_invoke()), None, None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing reply-to-address")]
786    // [utest->dsn~up-attributes-request-source~1]
787    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(UUri { resource_id: 0x0001, ..Default::default()}), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for invalid reply-to-address")]
788    // [utest->dsn~up-attributes-request-sink~1]
789    #[test_case(Some(UUID::build()), None, Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing method-to-invoke")]
790    // [utest->dsn~up-attributes-request-sink~1]
791    #[test_case(Some(UUID::build()), Some(UUri::default()), Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for invalid method-to-invoke")]
792    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), None, None, false; "fails for missing priority")]
793    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS3), None, false; "fails for invalid priority")]
794    // [utest->dsn~up-attributes-request-ttl~1]
795    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, None, Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing ttl")]
796    // [utest->dsn~up-attributes-request-ttl~1]
797    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, Some(0), Some(UPriority::UPRIORITY_CS4), None, false; "fails for ttl = 0")]
798    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), None, true; "succeeds for valid permission level")]
799    #[allow(clippy::too_many_arguments)]
800    fn test_validate_attributes_for_rpc_request_message(
801        id: Option<UUID>,
802        method_to_invoke: Option<UUri>,
803        reply_to_address: Option<UUri>,
804        perm_level: Option<u32>,
805        ttl: Option<u32>,
806        priority: Option<UPriority>,
807        token: Option<String>,
808        expected_result: bool,
809    ) {
810        let attributes = UAttributes {
811            type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
812            id: id.into(),
813            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
814            source: reply_to_address.into(),
815            sink: method_to_invoke.into(),
816            permission_level: perm_level,
817            ttl,
818            token,
819            ..Default::default()
820        };
821        let status = UAttributesValidators::Request
822            .validator()
823            .validate(&attributes);
824        assert!(status.is_ok() == expected_result);
825        if status.is_ok() {
826            assert!(UAttributesValidators::Publish
827                .validator()
828                .validate(&attributes)
829                .is_err());
830            assert!(UAttributesValidators::Notification
831                .validator()
832                .validate(&attributes)
833                .is_err());
834            assert!(UAttributesValidators::Response
835                .validator()
836                .validate(&attributes)
837                .is_err());
838        }
839    }
840
841    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), true; "succeeds for mandatory attributes")]
842    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS4), true; "succeeds for valid attributes")]
843    // [utest->dsn~up-attributes-id~1]
844    #[test_case(None, Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS4), false; "fails for missing message ID")]
845    // [utest->dsn~up-attributes-id~1]
846    #[test_case(
847        Some(UUID {
848            // invalid UUID version (not 0b1000 but 0b1010)
849            msb: 0x000000000001C000u64,
850            lsb: 0x8000000000000000u64,
851            ..Default::default()
852        }),
853        Some(reply_to_address()),
854        Some(method_to_invoke()),
855        Some(UUID::build()),
856        None,
857        None,
858        Some(UPriority::UPRIORITY_CS4),
859        false;
860        "fails for invalid message id")]
861    // [utest->dsn~up-attributes-response-sink~1]
862    #[test_case(Some(UUID::build()), None, Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing reply-to-address")]
863    // [utest->dsn~up-attributes-response-sink~1]
864    #[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x0001, ..Default::default()}), Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid reply-to-address")]
865    // [utest->dsn~up-attributes-response-source~1]
866    #[test_case(Some(UUID::build()), Some(reply_to_address()), None, Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing invoked-method")]
867    // [utest->dsn~up-attributes-response-source~1]
868    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(UUri::default()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid invoked-method")]
869    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), None, Some(UPriority::UPRIORITY_CS4), true; "succeeds for valid commstatus")]
870    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from_i32(-42)), None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid commstatus")]
871    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, Some(100), Some(UPriority::UPRIORITY_CS4), true; "succeeds for ttl > 0)")]
872    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, Some(0), Some(UPriority::UPRIORITY_CS4), true; "succeeds for ttl = 0")]
873    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), None, false; "fails for missing priority")]
874    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS3), false; "fails for invalid priority")]
875    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), None, None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing request id")]
876    #[test_case(
877        Some(UUID::build()),
878        Some(reply_to_address()),
879        Some(method_to_invoke()),
880        Some(UUID {
881            // invalid UUID version (not 0b1000 but 0b1010)
882            msb: 0x000000000001C000u64,
883            lsb: 0x8000000000000000u64,
884            ..Default::default()
885        }),
886        None,
887        None,
888        Some(UPriority::UPRIORITY_CS4),
889        false;
890        "fails for invalid request id")]
891    #[allow(clippy::too_many_arguments)]
892    fn test_validate_attributes_for_rpc_response_message(
893        id: Option<UUID>,
894        reply_to_address: Option<UUri>,
895        invoked_method: Option<UUri>,
896        reqid: Option<UUID>,
897        commstatus: Option<EnumOrUnknown<UCode>>,
898        ttl: Option<u32>,
899        priority: Option<UPriority>,
900        expected_result: bool,
901    ) {
902        let attributes = UAttributes {
903            type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
904            id: id.into(),
905            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
906            reqid: reqid.into(),
907            source: invoked_method.into(),
908            sink: reply_to_address.into(),
909            commstatus,
910            ttl,
911            ..Default::default()
912        };
913        let status = UAttributesValidators::Response
914            .validator()
915            .validate(&attributes);
916        assert!(status.is_ok() == expected_result);
917        if status.is_ok() {
918            assert!(UAttributesValidators::Publish
919                .validator()
920                .validate(&attributes)
921                .is_err());
922            assert!(UAttributesValidators::Notification
923                .validator()
924                .validate(&attributes)
925                .is_err());
926            assert!(UAttributesValidators::Request
927                .validator()
928                .validate(&attributes)
929                .is_err());
930        }
931    }
932
933    fn publish_topic() -> UUri {
934        UUri {
935            authority_name: String::from("vcu.someVin"),
936            ue_id: 0x0000_5410,
937            ue_version_major: 0x01,
938            resource_id: 0xa010,
939            ..Default::default()
940        }
941    }
942
943    fn origin() -> UUri {
944        UUri {
945            authority_name: String::from("vcu.someVin"),
946            ue_id: 0x0000_3c00,
947            ue_version_major: 0x02,
948            resource_id: 0x9a00,
949            ..Default::default()
950        }
951    }
952
953    fn destination() -> UUri {
954        UUri {
955            authority_name: String::from("vcu.someVin"),
956            ue_id: 0x0000_3d07,
957            ue_version_major: 0x01,
958            resource_id: 0x0000,
959            ..Default::default()
960        }
961    }
962
963    fn reply_to_address() -> UUri {
964        UUri {
965            authority_name: String::from("vcu.someVin"),
966            ue_id: 0x0000_010b,
967            ue_version_major: 0x01,
968            resource_id: 0x0000,
969            ..Default::default()
970        }
971    }
972
973    fn method_to_invoke() -> UUri {
974        UUri {
975            authority_name: String::from("vcu.someVin"),
976            ue_id: 0x0000_03ae,
977            ue_version_major: 0x01,
978            resource_id: 0x00e2,
979            ..Default::default()
980        }
981    }
982}