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