use std::time::SystemTime;
use protobuf::Enum;
use crate::{UAttributes, UMessageType, UPriority, UUri, UUID};
use crate::UAttributesError;
pub trait UAttributesValidator: Send {
    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
    fn validate_type(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let expected_type = self.message_type();
        match attributes.type_.enum_value() {
            Ok(mt) if mt == expected_type => Ok(()),
            Ok(mt) => Err(UAttributesError::validation_error(format!(
                "Wrong Message Type [{}]",
                mt.to_cloudevent_type()
            ))),
            Err(unknown_code) => Err(UAttributesError::validation_error(format!(
                "Unknown Message Type code [{}]",
                unknown_code
            ))),
        }
    }
    fn validate_id(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if attributes
            .id
            .as_ref()
            .map_or(false, |id| id.is_uprotocol_uuid())
        {
            Ok(())
        } else {
            Err(UAttributesError::validation_error(
                "Attributes must contain valid uProtocol UUID in id property",
            ))
        }
    }
    fn message_type(&self) -> UMessageType;
    fn is_expired(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let ttl = match attributes.ttl {
            Some(t) if t > 0 => u64::from(t),
            _ => return Ok(()),
        };
        if let Some(time) = attributes.id.as_ref().and_then(UUID::get_time) {
            let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
                Ok(duration) => {
                    if let Ok(duration) = u64::try_from(duration.as_millis()) {
                        duration - time
                    } else {
                        return Err(UAttributesError::validation_error("Invalid duration"));
                    }
                }
                Err(e) => return Err(UAttributesError::validation_error(e.to_string())),
            };
            if delta >= ttl {
                return Err(UAttributesError::validation_error("Payload is expired"));
            }
        }
        Ok(())
    }
    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
}
pub fn validate_rpc_priority(attributes: &UAttributes) -> Result<(), UAttributesError> {
    attributes
        .priority
        .enum_value()
        .map_err(|unknown_code| {
            UAttributesError::ValidationError(format!(
                "RPC message must have a valid priority [{}]",
                unknown_code
            ))
        })
        .and_then(|prio| {
            if prio.value() < UPriority::UPRIORITY_CS4.value() {
                Err(UAttributesError::ValidationError(
                    "RPC message must have a priority of at least CS4".to_string(),
                ))
            } else {
                Ok(())
            }
        })
}
pub enum UAttributesValidators {
    Publish,
    Notification,
    Request,
    Response,
}
impl UAttributesValidators {
    pub fn validator(&self) -> Box<dyn UAttributesValidator> {
        match self {
            UAttributesValidators::Publish => Box::new(PublishValidator),
            UAttributesValidators::Notification => Box::new(NotificationValidator),
            UAttributesValidators::Request => Box::new(RequestValidator),
            UAttributesValidators::Response => Box::new(ResponseValidator),
        }
    }
    pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
        Self::get_validator(attributes.type_.enum_value_or_default())
    }
    pub fn get_validator(message_type: UMessageType) -> Box<dyn UAttributesValidator> {
        match message_type {
            UMessageType::UMESSAGE_TYPE_REQUEST => Box::new(RequestValidator),
            UMessageType::UMESSAGE_TYPE_RESPONSE => Box::new(ResponseValidator),
            UMessageType::UMESSAGE_TYPE_NOTIFICATION => Box::new(NotificationValidator),
            _ => Box::new(PublishValidator),
        }
    }
}
pub struct PublishValidator;
impl UAttributesValidator for PublishValidator {
    fn message_type(&self) -> UMessageType {
        UMessageType::UMESSAGE_TYPE_PUBLISH
    }
    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let error_message = vec![
            self.validate_type(attributes),
            self.validate_id(attributes),
            self.validate_source(attributes),
            self.validate_sink(attributes),
        ]
        .into_iter()
        .filter_map(Result::err)
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("; ");
        if error_message.is_empty() {
            Ok(())
        } else {
            Err(UAttributesError::validation_error(error_message))
        }
    }
    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(source) = attributes.source.as_ref() {
            source.verify_event().map_err(|e| {
                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
            })
        } else {
            Err(UAttributesError::validation_error(
                "Attributes for a publish message must contain a source URI",
            ))
        }
    }
    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if attributes.sink.as_ref().is_some() {
            Err(UAttributesError::validation_error(
                "Attributes for a publish message must not contain a sink URI",
            ))
        } else {
            Ok(())
        }
    }
}
pub struct NotificationValidator;
impl UAttributesValidator for NotificationValidator {
    fn message_type(&self) -> UMessageType {
        UMessageType::UMESSAGE_TYPE_NOTIFICATION
    }
    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let error_message = vec![
            self.validate_type(attributes),
            self.validate_id(attributes),
            self.validate_source(attributes),
            self.validate_sink(attributes),
        ]
        .into_iter()
        .filter_map(Result::err)
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("; ");
        if error_message.is_empty() {
            Ok(())
        } else {
            Err(UAttributesError::validation_error(error_message))
        }
    }
    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(source) = attributes.source.as_ref() {
            if source.is_rpc_response() {
                Err(UAttributesError::validation_error(
                    "Origin must not be an RPC response URI",
                ))
            } else {
                source.verify_no_wildcards().map_err(|e| {
                    UAttributesError::validation_error(format!("Invalid source URI: {}", e))
                })
            }
        } else {
            Err(UAttributesError::validation_error(
                "Attributes must contain a source URI",
            ))
        }
    }
    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(sink) = attributes.sink.as_ref() {
            if !sink.is_notification_destination() {
                Err(UAttributesError::validation_error(
                    "Destination's resource ID must be 0",
                ))
            } else {
                sink.verify_no_wildcards().map_err(|e| {
                    UAttributesError::validation_error(format!("Invalid sink URI: {}", e))
                })
            }
        } else {
            Err(UAttributesError::validation_error(
                "Attributes for a notification message must contain a sink URI",
            ))
        }
    }
}
pub struct RequestValidator;
impl RequestValidator {
    pub fn validate_ttl(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        match attributes.ttl {
            Some(ttl) if ttl > 0 => Ok(()),
            Some(invalid_ttl) => Err(UAttributesError::validation_error(format!(
                "RPC request message's TTL must be a positive integer [{invalid_ttl}]"
            ))),
            None => Err(UAttributesError::validation_error(
                "RPC request message must contain a TTL",
            )),
        }
    }
}
impl UAttributesValidator for RequestValidator {
    fn message_type(&self) -> UMessageType {
        UMessageType::UMESSAGE_TYPE_REQUEST
    }
    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let error_message = vec![
            self.validate_type(attributes),
            self.validate_id(attributes),
            self.validate_ttl(attributes),
            self.validate_source(attributes),
            self.validate_sink(attributes),
            validate_rpc_priority(attributes),
        ]
        .into_iter()
        .filter_map(Result::err)
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("; ");
        if error_message.is_empty() {
            Ok(())
        } else {
            Err(UAttributesError::validation_error(error_message))
        }
    }
    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(source) = attributes.source.as_ref() {
            UUri::verify_rpc_response(source).map_err(|e| {
                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
            })
        } else {
            Err(UAttributesError::validation_error("Attributes for a request message must contain a reply-to address in the source property"))
        }
    }
    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(sink) = attributes.sink.as_ref() {
            UUri::verify_rpc_method(sink)
                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
        } else {
            Err(UAttributesError::validation_error("Attributes for a request message must contain a method-to-invoke in the sink property"))
        }
    }
}
pub struct ResponseValidator;
impl ResponseValidator {
    pub fn validate_reqid(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if !attributes
            .reqid
            .as_ref()
            .map_or(false, |id| id.is_uprotocol_uuid())
        {
            Err(UAttributesError::validation_error(
                "Request ID is not a valid uProtocol UUID",
            ))
        } else {
            Ok(())
        }
    }
    pub fn validate_commstatus(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(status) = attributes.commstatus {
            match status.enum_value() {
                Ok(_) => {
                    return Ok(());
                }
                Err(e) => {
                    return Err(UAttributesError::validation_error(format!(
                        "Invalid Communication Status code: {e}"
                    )));
                }
            }
        }
        Ok(())
    }
}
impl UAttributesValidator for ResponseValidator {
    fn message_type(&self) -> UMessageType {
        UMessageType::UMESSAGE_TYPE_RESPONSE
    }
    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        let error_message = vec![
            self.validate_type(attributes),
            self.validate_id(attributes),
            self.validate_source(attributes),
            self.validate_sink(attributes),
            self.validate_reqid(attributes),
            self.validate_commstatus(attributes),
            validate_rpc_priority(attributes),
        ]
        .into_iter()
        .filter_map(Result::err)
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("; ");
        if error_message.is_empty() {
            Ok(())
        } else {
            Err(UAttributesError::validation_error(error_message))
        }
    }
    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(source) = attributes.source.as_ref() {
            UUri::verify_rpc_method(source).map_err(|e| {
                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
            })
        } else {
            Err(UAttributesError::validation_error("Missing Source"))
        }
    }
    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
        if let Some(sink) = &attributes.sink.as_ref() {
            UUri::verify_rpc_response(sink)
                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
        } else {
            Err(UAttributesError::validation_error("Missing Sink"))
        }
    }
}
#[cfg(test)]
mod tests {
    use std::{
        ops::Sub,
        time::{Duration, UNIX_EPOCH},
    };
    use protobuf::EnumOrUnknown;
    use test_case::test_case;
    use super::*;
    use crate::{UCode, UPriority, UUri, UUID};
    fn build_n_ms_in_past(n_ms_in_past: u64) -> UUID {
        let duration_since_unix_epoch = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("current system time is set to a point in time before UNIX Epoch");
        UUID::build_for_timestamp(
            duration_since_unix_epoch.sub(Duration::from_millis(n_ms_in_past)),
        )
    }
    #[test]
    fn test_validate_type_fails_for_unknown_type_code() {
        let attributes = UAttributes {
            type_: EnumOrUnknown::from_i32(20),
            ..Default::default()
        };
        assert!(UAttributesValidators::Publish
            .validator()
            .validate_type(&attributes)
            .is_err());
        assert!(UAttributesValidators::Notification
            .validator()
            .validate_type(&attributes)
            .is_err());
        assert!(UAttributesValidators::Request
            .validator()
            .validate_type(&attributes)
            .is_err());
        assert!(UAttributesValidators::Response
            .validator()
            .validate_type(&attributes)
            .is_err());
    }
    #[test_case(UMessageType::UMESSAGE_TYPE_UNSPECIFIED, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Unspecified message")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Publish message")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, UMessageType::UMESSAGE_TYPE_NOTIFICATION; "succeeds for Notification message")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, UMessageType::UMESSAGE_TYPE_REQUEST; "succeeds for Request message")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, UMessageType::UMESSAGE_TYPE_RESPONSE; "succeeds for Response message")]
    fn test_get_validator_returns_matching_validator(
        message_type: UMessageType,
        expected_validator_type: UMessageType,
    ) {
        let validator: Box<dyn UAttributesValidator> =
            UAttributesValidators::get_validator(message_type);
        assert_eq!(validator.message_type(), expected_validator_type);
    }
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, None, false; "for Publish message without ID nor TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(0), false; "for Publish message without ID with TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(500), false; "for Publish message without ID with TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), None, false; "for Publish message with ID without TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(0), false; "for Publish message with ID and TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(500), true; "for Publish message with ID and expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Publish message with ID and non-expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, None, false; "for Notification message without ID nor TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(0), false; "for Notification message without ID with TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(500), false; "for Notification message without ID with TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), None, false; "for Notification message with ID without TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(0), false; "for Notification message with ID and TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(500), true; "for Notification message with ID and expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Notification message with ID and non-expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, None, false; "for Request message without ID nor TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(0), false; "for Request message without ID with TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(500), false; "for Request message without ID with TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), None, false; "for Request message with ID without TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(0), false; "for Request message with ID and TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(500), true; "for Request message with ID and expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Request message with ID and non-expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, None, false; "for Response message without ID nor TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(0), false; "for Response message without ID with TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(500), false; "for Response message without ID with TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), None, false; "for Response message with ID without TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(0), false; "for Response message with ID and TTL 0")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(500), true; "for Response message with ID and expired TTL")]
    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Response message with ID and non-expired TTL")]
    fn test_is_expired(
        message_type: UMessageType,
        id: Option<UUID>,
        ttl: Option<u32>,
        should_be_expired: bool,
    ) {
        let attributes = UAttributes {
            type_: message_type.into(),
            priority: UPriority::UPRIORITY_CS1.into(),
            id: id.into(),
            ttl,
            ..Default::default()
        };
        let validator = UAttributesValidators::get_validator(message_type);
        assert!(validator.is_expired(&attributes).is_err() == should_be_expired);
    }
    #[test_case(Some(UUID::build()), Some(publish_topic()), None, None, true; "succeeds for topic only")]
    #[test_case(Some(UUID::build()), Some(publish_topic()), Some(destination()), None, false; "fails for message containing destination")]
    #[test_case(Some(UUID::build()), Some(publish_topic()), None, Some(100), true; "succeeds for valid attributes")]
    #[test_case(Some(UUID::build()), None, None, None, false; "fails for missing topic")]
    #[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x54, ..Default::default()}), None, None, false; "fails for invalid topic")]
    #[test_case(None, Some(publish_topic()), None, None, false; "fails for missing message ID")]
    #[test_case(
        Some(UUID {
            msb: 0x000000000001C000u64,
            lsb: 0x8000000000000000u64,
            ..Default::default()
        }),
        Some(publish_topic()),
        None,
        None,
        false;
        "fails for invalid message id")]
    fn test_validate_attributes_for_publish_message(
        id: Option<UUID>,
        source: Option<UUri>,
        sink: Option<UUri>,
        ttl: Option<u32>,
        expected_result: bool,
    ) {
        let attributes = UAttributes {
            type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
            id: id.into(),
            priority: UPriority::UPRIORITY_CS1.into(),
            source: source.into(),
            sink: sink.into(),
            ttl,
            ..Default::default()
        };
        let validator = UAttributesValidators::Publish.validator();
        let status = validator.validate(&attributes);
        assert!(status.is_ok() == expected_result);
        if status.is_ok() {
            assert!(UAttributesValidators::Notification
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Request
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Response
                .validator()
                .validate(&attributes)
                .is_err());
        }
    }
    #[test_case(Some(UUID::build()), Some(origin()), None, None, false; "fails for missing destination")]
    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), None, true; "succeeds for both origin and destination")]
    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), Some(100), true; "succeeds for valid attributes")]
    #[test_case(Some(UUID::build()), None, Some(destination()), None, false; "fails for missing origin")]
    #[test_case(Some(UUID::build()), Some(UUri::default()), Some(destination()), None, false; "fails for invalid origin")]
    #[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")]
    #[test_case(Some(UUID::build()), None, None, None, false; "fails for neither origin nor destination")]
    #[test_case(None, Some(origin()), Some(destination()), None, false; "fails for missing message ID")]
    #[test_case(
        Some(UUID {
            msb: 0x000000000001C000u64,
            lsb: 0x8000000000000000u64,
            ..Default::default()
        }),
        Some(origin()),
        Some(destination()),
        None,
        false;
        "fails for invalid message id")]
    fn test_validate_attributes_for_notification_message(
        id: Option<UUID>,
        source: Option<UUri>,
        sink: Option<UUri>,
        ttl: Option<u32>,
        expected_result: bool,
    ) {
        let attributes = UAttributes {
            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
            id: id.into(),
            priority: UPriority::UPRIORITY_CS1.into(),
            source: source.into(),
            sink: sink.into(),
            ttl,
            ..Default::default()
        };
        let validator = UAttributesValidators::Notification.validator();
        let status = validator.validate(&attributes);
        assert!(status.is_ok() == expected_result);
        if status.is_ok() {
            assert!(UAttributesValidators::Publish
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Request
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Response
                .validator()
                .validate(&attributes)
                .is_err());
        }
    }
    #[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")]
    #[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")]
    #[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")]
    #[test_case(
        Some(UUID {
            msb: 0x000000000001C000u64,
            lsb: 0x8000000000000000u64,
            ..Default::default()
        }),
        Some(method_to_invoke()),
        Some(reply_to_address()),
        None,
        Some(2000),
        Some(UPriority::UPRIORITY_CS4),
        None,
        false;
        "fails for invalid message id")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), None, None, false; "fails for missing priority")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[allow(clippy::too_many_arguments)]
    fn test_validate_attributes_for_rpc_request_message(
        id: Option<UUID>,
        method_to_invoke: Option<UUri>,
        reply_to_address: Option<UUri>,
        perm_level: Option<u32>,
        ttl: Option<u32>,
        priority: Option<UPriority>,
        token: Option<String>,
        expected_result: bool,
    ) {
        let attributes = UAttributes {
            type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
            id: id.into(),
            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
            source: reply_to_address.into(),
            sink: method_to_invoke.into(),
            permission_level: perm_level,
            ttl,
            token,
            ..Default::default()
        };
        let status = UAttributesValidators::Request
            .validator()
            .validate(&attributes);
        assert!(status.is_ok() == expected_result);
        if status.is_ok() {
            assert!(UAttributesValidators::Publish
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Notification
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Response
                .validator()
                .validate(&attributes)
                .is_err());
        }
    }
    #[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")]
    #[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")]
    #[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")]
    #[test_case(
        Some(UUID {
            msb: 0x000000000001C000u64,
            lsb: 0x8000000000000000u64,
            ..Default::default()
        }),
        Some(reply_to_address()),
        Some(method_to_invoke()),
        Some(UUID::build()),
        None,
        None,
        Some(UPriority::UPRIORITY_CS4),
        false;
        "fails for invalid message id")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[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)")]
    #[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")]
    #[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")]
    #[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")]
    #[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")]
    #[test_case(
        Some(UUID::build()),
        Some(reply_to_address()),
        Some(method_to_invoke()),
        Some(UUID {
            msb: 0x000000000001C000u64,
            lsb: 0x8000000000000000u64,
            ..Default::default()
        }),
        None,
        None,
        Some(UPriority::UPRIORITY_CS4),
        false;
        "fails for invalid request id")]
    #[allow(clippy::too_many_arguments)]
    fn test_validate_attributes_for_rpc_response_message(
        id: Option<UUID>,
        reply_to_address: Option<UUri>,
        invoked_method: Option<UUri>,
        reqid: Option<UUID>,
        commstatus: Option<EnumOrUnknown<UCode>>,
        ttl: Option<u32>,
        priority: Option<UPriority>,
        expected_result: bool,
    ) {
        let attributes = UAttributes {
            type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
            id: id.into(),
            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
            reqid: reqid.into(),
            source: invoked_method.into(),
            sink: reply_to_address.into(),
            commstatus,
            ttl,
            ..Default::default()
        };
        let status = UAttributesValidators::Response
            .validator()
            .validate(&attributes);
        assert!(status.is_ok() == expected_result);
        if status.is_ok() {
            assert!(UAttributesValidators::Publish
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Notification
                .validator()
                .validate(&attributes)
                .is_err());
            assert!(UAttributesValidators::Request
                .validator()
                .validate(&attributes)
                .is_err());
        }
    }
    fn publish_topic() -> UUri {
        UUri {
            authority_name: String::from("vcu.someVin"),
            ue_id: 0x0000_5410,
            ue_version_major: 0x01,
            resource_id: 0xa010,
            ..Default::default()
        }
    }
    fn origin() -> UUri {
        UUri {
            authority_name: String::from("vcu.someVin"),
            ue_id: 0x0000_3c00,
            ue_version_major: 0x02,
            resource_id: 0x9a00,
            ..Default::default()
        }
    }
    fn destination() -> UUri {
        UUri {
            authority_name: String::from("vcu.someVin"),
            ue_id: 0x0000_3d07,
            ue_version_major: 0x01,
            resource_id: 0x0000,
            ..Default::default()
        }
    }
    fn reply_to_address() -> UUri {
        UUri {
            authority_name: String::from("vcu.someVin"),
            ue_id: 0x0000_010b,
            ue_version_major: 0x01,
            resource_id: 0x0000,
            ..Default::default()
        }
    }
    fn method_to_invoke() -> UUri {
        UUri {
            authority_name: String::from("vcu.someVin"),
            ue_id: 0x0000_03ae,
            ue_version_major: 0x01,
            resource_id: 0x00e2,
            ..Default::default()
        }
    }
}