azure_iot_operations_protocol/common/
user_properties.rsuse std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
pub(crate) const PARTITION_KEY: &str = "$partition";
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum UserProperty {
Timestamp,
Status,
StatusMessage,
IsApplicationError,
SourceId,
InvalidPropertyName,
InvalidPropertyValue,
ProtocolVersion,
SupportedMajorVersions,
RequestProtocolVersion,
}
impl Display for UserProperty {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
UserProperty::Timestamp => write!(f, "__ts"),
UserProperty::Status => write!(f, "__stat"),
UserProperty::StatusMessage => write!(f, "__stMsg"),
UserProperty::IsApplicationError => write!(f, "__apErr"),
UserProperty::SourceId => write!(f, "__srcId"),
UserProperty::InvalidPropertyName => write!(f, "__propName"),
UserProperty::InvalidPropertyValue => write!(f, "__propVal"),
UserProperty::ProtocolVersion => write!(f, "__protVer"),
UserProperty::SupportedMajorVersions => write!(f, "__supProtMajVer"),
UserProperty::RequestProtocolVersion => write!(f, "__requestProtVer"),
}
}
}
impl FromStr for UserProperty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"__ts" => Ok(UserProperty::Timestamp),
"__stat" => Ok(UserProperty::Status),
"__stMsg" => Ok(UserProperty::StatusMessage),
"__apErr" => Ok(UserProperty::IsApplicationError),
"__srcId" => Ok(UserProperty::SourceId),
"__propName" => Ok(UserProperty::InvalidPropertyName),
"__propVal" => Ok(UserProperty::InvalidPropertyValue),
"__protVer" => Ok(UserProperty::ProtocolVersion),
"__supProtMajVer" => Ok(UserProperty::SupportedMajorVersions),
"__requestProtVer" => Ok(UserProperty::RequestProtocolVersion),
_ => Err(()),
}
}
}
pub(crate) fn validate_invoker_user_properties(
property_list: &[(String, String)],
) -> Result<(), String> {
for (key, value) in property_list {
if super::is_invalid_utf8(key) || super::is_invalid_utf8(value) {
return Err(format!(
"Invalid user data key '{key}' or value '{value}' isn't valid utf-8"
));
}
if key == PARTITION_KEY {
return Err(format!("User data key '{PARTITION_KEY}'"));
}
}
Ok(())
}
pub(crate) fn validate_user_properties(property_list: &[(String, String)]) -> Result<(), String> {
for (key, value) in property_list {
if super::is_invalid_utf8(key) || super::is_invalid_utf8(value) {
return Err(format!(
"Invalid user data key '{key}' or value '{value}' isn't valid utf-8"
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use test_case::test_case;
use super::*;
use crate::common::user_properties::UserProperty;
#[test_case(UserProperty::Timestamp; "timestamp")]
#[test_case(UserProperty::Status; "status")]
#[test_case(UserProperty::StatusMessage; "status_message")]
#[test_case(UserProperty::IsApplicationError; "is_application_error")]
#[test_case(UserProperty::SourceId; "source_id")]
#[test_case(UserProperty::InvalidPropertyName; "invalid_property_name")]
#[test_case(UserProperty::InvalidPropertyValue; "invalid_property_value")]
#[test_case(UserProperty::ProtocolVersion; "protocol_version")]
#[test_case(UserProperty::SupportedMajorVersions; "supported_major_versions")]
#[test_case(UserProperty::RequestProtocolVersion; "request_protocol_version")]
fn test_to_from_string(prop: UserProperty) {
assert_eq!(prop, UserProperty::from_str(&prop.to_string()).unwrap());
}
#[test_case(&[("abc\ndef".to_string(),"abcdef".to_string())]; "custom_user_data_malformed_key")]
#[test_case(&[("abcdef".to_string(),"abc\ndef".to_string())]; "custom_user_data_malformed_value")]
fn test_validate_user_properties_invalid_value(custom_user_data: &[(String, String)]) {
assert!(validate_user_properties(custom_user_data).is_err());
assert!(validate_invoker_user_properties(custom_user_data).is_err());
}
#[test_case(&[("__abcdef".to_string(),"abcdef".to_string())]; "custom_user_data_reserved_prefix")]
#[test_case(&[("abcdef".to_string(),"abcdef".to_string())]; "custom_user_data_valid")]
fn test_validate_user_properties_valid_value(custom_user_data: &[(String, String)]) {
assert!(validate_user_properties(custom_user_data).is_ok());
assert!(validate_invoker_user_properties(custom_user_data).is_ok());
}
#[test]
fn test_partition_key_user_property() {
let partition_key_user_properties = vec![(PARTITION_KEY.to_string(), "abcdef".to_string())];
assert!(validate_user_properties(&partition_key_user_properties).is_ok());
assert!(validate_invoker_user_properties(&partition_key_user_properties).is_err());
}
}