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