Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

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 crate::{PayloadEncoding, UAttributes, UMessageType, UPayloadFormat, UPriority, UUri};
15
16use crate::UAttributesError;
17
18/// *Role: standalone utility validating message attributes per message kind; used by transports at boundaries — see the [trait map](crate::guide::trait_map).*
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        if expected_type == attributes.type_() {
44            Ok(())
45        } else {
46            Err(UAttributesError::validation_error(format!(
47                "Wrong Message Type [expected {}, got {}]",
48                expected_type.to_cloudevent_type(),
49                attributes.type_().to_cloudevent_type()
50            )))
51        }
52    }
53
54    /// Returns the type of message that this validator can be used with.
55    fn message_type(&self) -> UMessageType;
56
57    /// Verifies that a set of attributes contains a valid source URI.
58    ///
59    /// # Errors
60    ///
61    /// If the [`UAttributes::source`] property does not contain a valid URI as required by the type of message, an error is returned.
62    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
63
64    /// Verifies that a set of attributes contains a valid sink URI.
65    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
66}
67
68/// Verifies that a set of attributes contains a priority that is appropriate for an RPC request message.
69///
70/// # Errors
71///
72/// If [`UAttributes::priority`] contains a value that is less [`UPriority::UPRIORITY_CS4`].
73pub fn validate_rpc_priority(attributes: &UAttributes) -> Result<(), UAttributesError> {
74    attributes
75        .priority()
76        .ok_or_else(|| {
77            UAttributesError::ValidationError("RPC message must have a priority".to_string())
78        })
79        .and_then(|prio| {
80            if prio < UPriority::CS4 {
81                Err(UAttributesError::ValidationError(
82                    "RPC message must have a priority of at least CS4".to_string(),
83                ))
84            } else {
85                Ok(())
86            }
87        })
88}
89
90fn validate_payload_encoding(attributes: &UAttributes) -> Result<(), UAttributesError> {
91    let legacy_present = matches!(
92        attributes.payload_format(),
93        Some(format) if format != UPayloadFormat::Unspecified
94    );
95    let open_present = attributes.open_payload_encoding_parts() != (None, None, None);
96
97    if legacy_present && open_present {
98        return Err(UAttributesError::validation_error(
99            "payload encoding must use exactly one mechanism: payload_format or open payload-encoding fields",
100        ));
101    }
102
103    if open_present {
104        let (registry_id, literal_id, content_type) = attributes.open_payload_encoding_parts();
105        let encoding = PayloadEncoding::from_parts(
106            registry_id,
107            literal_id.map(ToOwned::to_owned),
108            content_type.map(ToOwned::to_owned),
109        )
110        .map_err(|error| UAttributesError::validation_error(error.to_string()))?;
111        if encoding.to_legacy_format().is_some() {
112            return Err(UAttributesError::validation_error(
113                "registered-range payload encodings must use payload_format, not open payload-encoding fields",
114            ));
115        }
116    }
117
118    Ok(())
119}
120
121/// Enum that hold the implementations of uattributesValidator according to type.
122#[derive(Debug)]
123pub enum UAttributesValidators {
124    /// Validates publish-message attributes.
125    Publish,
126    /// Validates notification-message attributes.
127    Notification,
128    /// Validates request-message attributes.
129    Request,
130    /// Validates response-message attributes.
131    Response,
132}
133
134impl UAttributesValidators {
135    /// Gets the validator corresponding to this enum value.
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use up_rust::{UAttributesValidators, UMessageBuilder, UUri};
141    ///
142    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
143    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
144    /// let msg = UMessageBuilder::publish(topic).build()?;
145    /// let validator = UAttributesValidators::Publish.validator();
146    /// assert!(validator.validate(msg.attributes()).is_ok());
147    /// # Ok(())
148    /// # }
149    /// ```
150    #[must_use]
151    pub fn validator(&self) -> Box<dyn UAttributesValidator> {
152        match self {
153            UAttributesValidators::Publish => Box::new(PublishValidator),
154            UAttributesValidators::Notification => Box::new(NotificationValidator),
155            UAttributesValidators::Request => Box::new(RequestValidator),
156            UAttributesValidators::Response => Box::new(ResponseValidator),
157        }
158    }
159
160    /// Gets a validator that can be used to check a given set of attributes.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// use up_rust::{UAttributesValidators, UMessageBuilder, UMessageType, UUri};
166    ///
167    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
169    /// let msg = UMessageBuilder::publish(topic).build()?;
170    /// let validator = UAttributesValidators::validator_for_attributes(msg.attributes());
171    /// assert!(validator.validate(msg.attributes()).is_ok());
172    /// # Ok(())
173    /// # }
174    /// ```
175    #[must_use]
176    pub fn validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
177        Self::validator_for(attributes.type_())
178    }
179
180    /// Gets a validator that can be used to check attributes of a given type of message.
181    ///
182    /// # Examples
183    ///
184    /// ```rust
185    /// use up_rust::{UAttributesValidators, UMessageBuilder, UMessageType, UUri};
186    ///
187    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
188    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
189    /// let msg = UMessageBuilder::publish(topic).build()?;
190    /// let validator = UAttributesValidators::validator_for(UMessageType::Publish);
191    /// assert!(validator.validate(msg.attributes()).is_ok());
192    /// # Ok(())
193    /// # }
194    /// ```
195    #[must_use]
196    pub fn validator_for(message_type: UMessageType) -> Box<dyn UAttributesValidator> {
197        match message_type {
198            UMessageType::Publish => Box::new(PublishValidator),
199            UMessageType::Notification => Box::new(NotificationValidator),
200            UMessageType::Request => Box::new(RequestValidator),
201            UMessageType::Response => Box::new(ResponseValidator),
202        }
203    }
204    /// Deprecated alias for [`Self::validator_for`].
205    #[deprecated(since = "0.11.0", note = "renamed to `validator_for`")]
206    pub fn get_validator(message_type: crate::UMessageType) -> Box<dyn UAttributesValidator> {
207        Self::validator_for(message_type)
208    }
209
210    /// Deprecated alias for [`Self::validator_for_attributes`].
211    #[deprecated(since = "0.11.0", note = "renamed to `validator_for_attributes`")]
212    pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
213        Self::validator_for_attributes(attributes)
214    }
215}
216
217/// Validates attributes describing a Publish message.
218#[derive(Debug)]
219pub struct PublishValidator;
220
221impl UAttributesValidator for PublishValidator {
222    fn message_type(&self) -> UMessageType {
223        UMessageType::Publish
224    }
225
226    /// Checks if a given set of attributes complies with the rules specified for
227    /// publish messages.
228    ///
229    /// # Errors
230    ///
231    /// Returns an error if any of the following checks fail for the given attributes:
232    ///
233    /// * [`UAttributesValidator::validate_type`]
234    /// * [`UAttributesValidator::validate_source`]
235    /// * [`UAttributesValidator::validate_sink`]
236    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
237        let error_message = vec![
238            self.validate_type(attributes),
239            self.validate_source(attributes),
240            self.validate_sink(attributes),
241            validate_payload_encoding(attributes),
242        ]
243        .into_iter()
244        .filter_map(Result::err)
245        .map(|e| e.to_string())
246        .collect::<Vec<_>>()
247        .join("; ");
248
249        if error_message.is_empty() {
250            Ok(())
251        } else {
252            Err(UAttributesError::validation_error(error_message))
253        }
254    }
255
256    /// Verifies that attributes for a publish message contain a valid source URI.
257    ///
258    /// # Errors
259    ///
260    /// Returns an error
261    ///
262    /// * if the source URI contains any wildcards, or
263    /// * if the source URI has a resource ID of 0.
264    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
265        // [impl->dsn~up-attributes-publish-source~1]
266        attributes
267            .source()
268            .verify_event()
269            .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
270    }
271
272    /// Verifies that attributes for a publish message do not contain a sink URI.
273    ///
274    /// # Errors
275    ///
276    /// If the [`UAttributes::sink`] property contains any URI, an error is returned.
277    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
278        // [impl->dsn~up-attributes-publish-sink~1]
279        if attributes.sink.as_ref().is_some() {
280            Err(UAttributesError::validation_error(
281                "Attributes for a publish message must not contain a sink URI",
282            ))
283        } else {
284            Ok(())
285        }
286    }
287}
288
289/// Validates attributes describing a Notification message.
290#[derive(Debug)]
291pub struct NotificationValidator;
292
293impl UAttributesValidator for NotificationValidator {
294    fn message_type(&self) -> UMessageType {
295        UMessageType::Notification
296    }
297
298    /// Checks if a given set of attributes complies with the rules specified for
299    /// notification messages.
300    ///
301    /// # Errors
302    ///
303    /// Returns an error if any of the following checks fail for the given attributes:
304    ///
305    /// * [`UAttributesValidator::validate_type`]
306    /// * [`UAttributesValidator::validate_source`]
307    /// * [`UAttributesValidator::validate_sink`]
308    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
309        let error_message = vec![
310            self.validate_type(attributes),
311            self.validate_source(attributes),
312            self.validate_sink(attributes),
313            validate_payload_encoding(attributes),
314        ]
315        .into_iter()
316        .filter_map(Result::err)
317        .map(|e| e.to_string())
318        .collect::<Vec<_>>()
319        .join("; ");
320
321        if error_message.is_empty() {
322            Ok(())
323        } else {
324            Err(UAttributesError::validation_error(error_message))
325        }
326    }
327
328    /// Verifies that attributes for a notification message contain a source URI.
329    ///
330    /// # Errors
331    ///
332    /// Returns an error
333    ///
334    /// * if the attributes do not contain a source URI, or
335    /// * if the source URI is an RPC response URI, or
336    /// * if the source URI contains any wildcards.
337    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
338        // [impl->dsn~up-attributes-notification-source~1]
339        let source = attributes.source();
340        if source.is_rpc_response() {
341            Err(UAttributesError::validation_error(
342                "Origin must not be an RPC response URI",
343            ))
344        } else {
345            source
346                .verify_no_wildcards()
347                .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
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`
381#[derive(Debug)]
382pub struct RequestValidator;
383
384impl RequestValidator {
385    /// Verifies that a set of attributes representing an RPC request contain a valid time-to-live.
386    ///
387    /// # Errors
388    ///
389    /// Returns an error if [`UAttributes::ttl`] (time-to-live) is empty or contains a value less than 1.
390    pub fn validate_ttl(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
391        // [impl->dsn~up-attributes-request-ttl~1]
392        match attributes.ttl {
393            Some(ttl) if ttl > 0 => Ok(()),
394            Some(invalid_ttl) => Err(UAttributesError::validation_error(format!(
395                "RPC request message's TTL must be a positive integer [{invalid_ttl}]"
396            ))),
397            None => Err(UAttributesError::validation_error(
398                "RPC request message must contain a TTL",
399            )),
400        }
401    }
402}
403
404impl UAttributesValidator for RequestValidator {
405    fn message_type(&self) -> UMessageType {
406        UMessageType::Request
407    }
408
409    /// Checks if a given set of attributes complies with the rules specified for
410    /// RPC request messages.
411    ///
412    /// # Errors
413    ///
414    /// Returns an error if any of the following checks fail for the given attributes:
415    ///
416    /// * [`UAttributesValidator::validate_type`]
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_ttl(attributes),
425            self.validate_source(attributes),
426            self.validate_sink(attributes),
427            validate_rpc_priority(attributes),
428            validate_payload_encoding(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        attributes
452            .source()
453            .verify_rpc_response()
454            .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
455    }
456
457    /// Verifies that attributes for a message representing an RPC request indicate the method to invoke.
458    ///
459    /// # Errors
460    ///
461    /// Returns an erro if the [`UAttributes::sink`] property does not contain a URI representing a method according to
462    /// [`UUri::verify_rpc_method`].
463    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
464        // [impl->dsn~up-attributes-request-sink~1]
465        if let Some(sink) = attributes.sink.as_ref() {
466            UUri::verify_rpc_method(sink)
467                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {e}")))
468        } else {
469            Err(UAttributesError::validation_error("Attributes for a request message must contain a method-to-invoke in the sink property"))
470        }
471    }
472}
473
474/// Validate `UAttributes` with type `UMessageType::Response`
475#[derive(Debug)]
476pub struct ResponseValidator;
477
478impl ResponseValidator {
479    /// Verifies that the attributes contain a request ID.
480    ///
481    /// # Errors
482    ///
483    /// Returns an error if the attributes do not contain a request ID.
484    pub fn validate_reqid(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
485        if attributes.reqid.as_ref().is_none() {
486            Err(UAttributesError::validation_error("Request ID is missing"))
487        } else {
488            Ok(())
489        }
490    }
491}
492
493impl UAttributesValidator for ResponseValidator {
494    fn message_type(&self) -> UMessageType {
495        UMessageType::Response
496    }
497
498    /// Checks if a given set of attributes complies with the rules specified for
499    /// RPC response messages.
500    ///
501    /// # Errors
502    ///
503    /// Returns an error if any of the following checks fail for the given attributes:
504    ///
505    /// * [`UAttributesValidator::validate_type`]
506    /// * [`UAttributesValidator::validate_source`]
507    /// * [`UAttributesValidator::validate_sink`]
508    /// * [`ResponseValidator::validate_reqid`]
509    /// * `validate_rpc_priority`
510    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
511        let error_message = vec![
512            self.validate_type(attributes),
513            self.validate_source(attributes),
514            self.validate_sink(attributes),
515            self.validate_reqid(attributes),
516            validate_rpc_priority(attributes),
517            validate_payload_encoding(attributes),
518        ]
519        .into_iter()
520        .filter_map(Result::err)
521        .map(|e| e.to_string())
522        .collect::<Vec<_>>()
523        .join("; ");
524
525        if error_message.is_empty() {
526            Ok(())
527        } else {
528            Err(UAttributesError::validation_error(error_message))
529        }
530    }
531
532    /// Verifies that attributes for a message representing an RPC response indicate the method that has
533    /// been invoked.
534    ///  
535    /// # Errors
536    ///
537    /// Returns an error if the [`UAttributes::source`] property does not contain a URI representing a method according to
538    /// [`UUri::verify_rpc_method`].
539    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
540        // [impl->dsn~up-attributes-response-source~1]
541        attributes
542            .source()
543            .verify_rpc_method()
544            .map_err(|e| UAttributesError::validation_error(format!("Invalid source URI: {e}")))
545    }
546
547    /// Verifies that attributes for a message representing an RPC response contain a valid
548    /// reply-to-address.
549    ///
550    /// # Errors
551    ///
552    /// Returns an error if the [`UAttributes::sink`] property does not contain a valid reply-to-address according to
553    /// [`UUri::verify_rpc_response`].
554    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
555        // [impl->dsn~up-attributes-response-sink~1]
556        if let Some(sink) = &attributes.sink.as_ref() {
557            UUri::verify_rpc_response(sink)
558                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {e}")))
559        } else {
560            Err(UAttributesError::validation_error("Missing Sink"))
561        }
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use test_case::test_case;
568
569    use super::*;
570    use crate::{uattributes::TokenString, UCode, UPriority, UUri, UUID};
571
572    #[test_case(UMessageType::Publish, UMessageType::Publish; "succeeds for Publish message")]
573    #[test_case(UMessageType::Notification, UMessageType::Notification; "succeeds for Notification message")]
574    #[test_case(UMessageType::Request, UMessageType::Request; "succeeds for Request message")]
575    #[test_case(UMessageType::Response, UMessageType::Response; "succeeds for Response message")]
576    fn test_get_validator_returns_matching_validator(
577        message_type: UMessageType,
578        expected_validator_type: UMessageType,
579    ) {
580        let validator: Box<dyn UAttributesValidator> =
581            UAttributesValidators::validator_for(message_type);
582        assert_eq!(validator.message_type(), expected_validator_type);
583    }
584
585    #[test_case(publish_topic(), None, None, true; "succeeds for topic only")]
586    // [utest->dsn~up-attributes-publish-sink~1]
587    #[test_case(publish_topic(), Some(destination()), None, false; "fails for message containing destination")]
588    #[test_case(publish_topic(), None, Some(100), true; "succeeds for valid attributes")]
589    // [utest->dsn~up-attributes-publish-source~1]
590    #[test_case(method_to_invoke(), None, None, false; "fails for invalid topic")]
591    fn test_validate_attributes_for_publish_message(
592        source: UUri,
593        sink: Option<UUri>,
594        ttl: Option<u32>,
595        expected_result: bool,
596    ) {
597        let attributes = UAttributes {
598            commstatus: None,
599            id: UUID::build(),
600            payload_format: None,
601            payload_encoding_registry_id: None,
602            payload_encoding: None,
603            payload_content_type: None,
604            permission_level: None,
605            priority: UPriority::CS1.into(),
606            reqid: None,
607            sink,
608            source,
609            token: None,
610            traceparent: None,
611            ttl,
612            type_: UMessageType::Publish,
613        };
614        let validator = UAttributesValidators::Publish.validator();
615        let status = validator.validate(&attributes);
616        assert!(status.is_ok() == expected_result);
617        if status.is_ok() {
618            assert!(UAttributesValidators::Notification
619                .validator()
620                .validate(&attributes)
621                .is_err());
622            assert!(UAttributesValidators::Request
623                .validator()
624                .validate(&attributes)
625                .is_err());
626            assert!(UAttributesValidators::Response
627                .validator()
628                .validate(&attributes)
629                .is_err());
630        }
631    }
632
633    // [utest->dsn~up-attributes-notification-sink~1]
634    #[test_case(origin(), None, None, false; "fails for missing destination")]
635    #[test_case(origin(), Some(destination()), None, true; "succeeds for both origin and destination")]
636    #[test_case(origin(), Some(destination()), Some(100), true; "succeeds for valid attributes")]
637    // [utest->dsn~up-attributes-notification-source~1]
638    #[test_case(reply_to_address(), Some(destination()), None, false; "fails for invalid origin")]
639    // [utest->dsn~up-attributes-notification-sink~1]
640    #[test_case(origin(), Some(method_to_invoke()), None, false; "fails for invalid destination")]
641    fn test_validate_attributes_for_notification_message(
642        source: UUri,
643        sink: Option<UUri>,
644        ttl: Option<u32>,
645        expected_result: bool,
646    ) {
647        let attributes = UAttributes {
648            commstatus: None,
649            id: UUID::build(),
650            payload_format: None,
651            payload_encoding_registry_id: None,
652            payload_encoding: None,
653            payload_content_type: None,
654            permission_level: None,
655            priority: UPriority::CS1.into(),
656            reqid: None,
657            sink,
658            source,
659            token: None,
660            traceparent: None,
661            ttl,
662            type_: UMessageType::Notification,
663        };
664        let validator = UAttributesValidators::Notification.validator();
665        let status = validator.validate(&attributes);
666        assert!(status.is_ok() == expected_result);
667        if status.is_ok() {
668            assert!(UAttributesValidators::Publish
669                .validator()
670                .validate(&attributes)
671                .is_err());
672            assert!(UAttributesValidators::Request
673                .validator()
674                .validate(&attributes)
675                .is_err());
676            assert!(UAttributesValidators::Response
677                .validator()
678                .validate(&attributes)
679                .is_err());
680        }
681    }
682
683    #[test_case(Some(method_to_invoke()), reply_to_address(), None, Some(2000), Some(UPriority::CS4), None, true; "succeeds for mandatory attributes")]
684    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), Some(2000), Some(UPriority::CS4), Some("token"), true; "succeeds for valid attributes")]
685    // [utest->dsn~up-attributes-request-source~1]
686    #[test_case(Some(method_to_invoke()), origin(), None, Some(2000), Some(UPriority::CS4), None, false; "fails for invalid reply-to-address")]
687    // [utest->dsn~up-attributes-request-sink~1]
688    #[test_case(None, reply_to_address(), None, Some(2000), Some(UPriority::CS4), None, false; "fails for missing method-to-invoke")]
689    // [utest->dsn~up-attributes-request-sink~1]
690    #[test_case(Some(destination()), reply_to_address(), None, Some(2000), Some(UPriority::CS4), None, false; "fails for invalid method-to-invoke")]
691    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), Some(2000), None, None, false; "fails for missing priority")]
692    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), Some(2000), Some(UPriority::CS3), None, false; "fails for invalid priority")]
693    // [utest->dsn~up-attributes-request-ttl~1]
694    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), None, Some(UPriority::CS4), None, false; "fails for missing ttl")]
695    // [utest->dsn~up-attributes-request-ttl~1]
696    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), Some(0), Some(UPriority::CS4), None, false; "fails for ttl = 0")]
697    #[test_case(Some(method_to_invoke()), reply_to_address(), Some(1), Some(2000), Some(UPriority::CS4), None, true; "succeeds for valid permission level")]
698    #[allow(clippy::too_many_arguments)]
699    fn test_validate_attributes_for_rpc_request_message(
700        method_to_invoke: Option<UUri>,
701        reply_to_address: UUri,
702        perm_level: Option<u32>,
703        ttl: Option<u32>,
704        priority: Option<UPriority>,
705        token: Option<&str>,
706        expected_result: bool,
707    ) {
708        let attributes = UAttributes {
709            commstatus: None,
710            id: UUID::build(),
711            payload_format: None,
712            payload_encoding_registry_id: None,
713            payload_encoding: None,
714            payload_content_type: None,
715            permission_level: perm_level,
716            priority,
717            reqid: None,
718            sink: method_to_invoke,
719            source: reply_to_address,
720            token: token.map(TokenString::from),
721            traceparent: None,
722            ttl,
723            type_: UMessageType::Request,
724        };
725        let status = UAttributesValidators::Request
726            .validator()
727            .validate(&attributes);
728        assert!(status.is_ok() == expected_result);
729        if status.is_ok() {
730            assert!(UAttributesValidators::Publish
731                .validator()
732                .validate(&attributes)
733                .is_err());
734            assert!(UAttributesValidators::Notification
735                .validator()
736                .validate(&attributes)
737                .is_err());
738            assert!(UAttributesValidators::Response
739                .validator()
740                .validate(&attributes)
741                .is_err());
742        }
743    }
744
745    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), None, None, Some(UPriority::CS4), true; "succeeds for mandatory attributes")]
746    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), Some(UCode::Cancelled), Some(100), Some(UPriority::CS4), true; "succeeds for valid attributes")]
747    // [utest->dsn~up-attributes-response-sink~1]
748    #[test_case(None, method_to_invoke(), Some(UUID::build()), None, None, Some(UPriority::CS4), false; "fails for missing reply-to-address")]
749    // [utest->dsn~up-attributes-response-sink~1]
750    #[test_case(Some(origin()), method_to_invoke(), Some(UUID::build()), None, None, Some(UPriority::CS4), false; "fails for invalid reply-to-address")]
751    // [utest->dsn~up-attributes-response-source~1]
752    #[test_case(Some(reply_to_address()), origin(), Some(UUID::build()), None, None, Some(UPriority::CS4), false; "fails for invalid invoked-method")]
753    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), Some(UCode::Cancelled), None, Some(UPriority::CS4), true; "succeeds for valid commstatus")]
754    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), None, Some(100), Some(UPriority::CS4), true; "succeeds for ttl > 0)")]
755    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), None, Some(0), Some(UPriority::CS4), true; "succeeds for ttl = 0")]
756    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), Some(UCode::Cancelled), Some(100), None, false; "fails for missing priority")]
757    #[test_case(Some(reply_to_address()), method_to_invoke(), Some(UUID::build()), Some(UCode::Cancelled), Some(100), Some(UPriority::CS3), false; "fails for invalid priority")]
758    #[test_case(Some(reply_to_address()), method_to_invoke(), None, None, None, Some(UPriority::CS4), false; "fails for missing request id")]
759    #[allow(clippy::too_many_arguments)]
760    fn test_validate_attributes_for_rpc_response_message(
761        reply_to_address: Option<UUri>,
762        invoked_method: UUri,
763        reqid: Option<UUID>,
764        commstatus: Option<UCode>,
765        ttl: Option<u32>,
766        priority: Option<UPriority>,
767        expected_result: bool,
768    ) {
769        let attributes = UAttributes {
770            commstatus,
771            id: UUID::build(),
772            payload_format: None,
773            payload_encoding_registry_id: None,
774            payload_encoding: None,
775            payload_content_type: None,
776            permission_level: None,
777            priority,
778            reqid,
779            sink: reply_to_address,
780            source: invoked_method,
781            token: None,
782            traceparent: None,
783            ttl,
784            type_: UMessageType::Response,
785        };
786        let status = UAttributesValidators::Response
787            .validator()
788            .validate(&attributes);
789        assert!(status.is_ok() == expected_result);
790        if status.is_ok() {
791            assert!(UAttributesValidators::Publish
792                .validator()
793                .validate(&attributes)
794                .is_err());
795            assert!(UAttributesValidators::Notification
796                .validator()
797                .validate(&attributes)
798                .is_err());
799            assert!(UAttributesValidators::Request
800                .validator()
801                .validate(&attributes)
802                .is_err());
803        }
804    }
805
806    fn publish_topic() -> UUri {
807        UUri::try_from_parts("vcu.somevin", 0x0000_5410, 0x01, 0xa010)
808            .expect("failed to create publish topic URI")
809    }
810
811    fn origin() -> UUri {
812        UUri::try_from_parts("vcu.somevin", 0x0000_3c00, 0x02, 0x9a00)
813            .expect("failed to create origin URI")
814    }
815
816    fn destination() -> UUri {
817        UUri::try_from_parts("vcu.somevin", 0x0000_3d07, 0x01, 0x0000)
818            .expect("failed to create destination URI")
819    }
820
821    fn reply_to_address() -> UUri {
822        UUri::try_from_parts("vcu.somevin", 0x0000_010b, 0x01, 0x0000)
823            .expect("failed to create reply-to-address URI")
824    }
825
826    fn method_to_invoke() -> UUri {
827        UUri::try_from_parts("vcu.somevin", 0x0000_03ae, 0x01, 0x00e2)
828            .expect("failed to create method-to-invoke URI")
829    }
830}