-
Notifications
You must be signed in to change notification settings - Fork 16
Add validty check function to UUri #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sophokles73
merged 2 commits into
eclipse-uprotocol:main
from
SoftwareDefinedVehicle:add_uri_validity_checker
Nov 13, 2024
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,15 +64,16 @@ impl std::error::Error for UUriError {} | |
|
|
||
| // [impl->req~uri-serialization~1] | ||
| impl From<&UUri> for String { | ||
| /// Serializes a UUri to a URI string. | ||
| /// Serializes a uProtocol URI to a URI string. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `uri` - The UUri to serialize. | ||
| /// * `uri` - The URI to serialize. Note that the given URI is **not** validated before serialization. | ||
| /// In particular, the URI's version and resource ID length are not checked to be within limits. | ||
| /// | ||
| /// # Returns | ||
| /// | ||
| /// The output of [`UUri::to_uri`] without inlcuding the uProtocol scheme. | ||
| /// The output of [`UUri::to_uri`] without including the uProtocol scheme. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
|
|
@@ -161,26 +162,7 @@ impl FromStr for UUri { | |
| } | ||
| let authority_name = parsed_uri | ||
| .authority() | ||
| .map_or(Ok(String::default()), |auth| { | ||
| if auth.has_port() { | ||
| Err(UUriError::serialization_error( | ||
| "uProtocol URI's authority must not contain port", | ||
| )) | ||
| } else if auth.has_username() || auth.has_password() { | ||
| Err(UUriError::serialization_error( | ||
| "uProtocol URI's authority must not contain userinfo", | ||
| )) | ||
| } else { | ||
| let auth_name = auth.host().to_string(); | ||
| if auth_name.len() <= 128 { | ||
| Ok(auth_name) | ||
| } else { | ||
| Err(UUriError::serialization_error( | ||
| "URI's authority name must not exceed 128 characters", | ||
| )) | ||
| } | ||
| } | ||
| })?; | ||
| .map_or(Ok(String::default()), Self::verify_parsed_authority)?; | ||
|
|
||
| let path_segments = parsed_uri.path().segments(); | ||
| if path_segments.len() != 3 { | ||
|
|
@@ -334,6 +316,9 @@ impl UUri { | |
| /// let uri_string = uuri.to_uri(true); | ||
| /// assert_eq!(uri_string, "up://VIN.vehicles/800A/2/1A50"); | ||
| /// ```` | ||
| // [impl->dsn~uri-authority-mapping~1] | ||
| // [impl->dsn~uri-path-mapping~1] | ||
| // [impl->req~uri-serialization~1] | ||
| pub fn to_uri(&self, include_scheme: bool) -> String { | ||
| let mut output = String::default(); | ||
| if include_scheme { | ||
|
|
@@ -372,30 +357,9 @@ impl UUri { | |
| entity_version: u8, | ||
| resource_id: u16, | ||
| ) -> Result<Self, UUriError> { | ||
| let auth = Authority::try_from(authority) | ||
| .map_err(|e| UUriError::validation_error(format!("invalid authority: {}", e))) | ||
| .and_then(|auth| { | ||
| if auth.has_port() { | ||
| Err(UUriError::validation_error( | ||
| "uProtocol URI's authority must not contain port", | ||
| )) | ||
| } else if auth.has_username() || auth.has_password() { | ||
| Err(UUriError::validation_error( | ||
| "uProtocol URI's authority must not contain userinfo", | ||
| )) | ||
| } else { | ||
| let auth_name = auth.host().to_string(); | ||
| if auth_name.len() <= 128 { | ||
| Ok(auth) | ||
| } else { | ||
| Err(UUriError::validation_error( | ||
| "URI's authority name must not exceed 128 characters", | ||
| )) | ||
| } | ||
| } | ||
| })?; | ||
| let authority_name = Self::verify_authority(authority)?; | ||
| Ok(UUri { | ||
| authority_name: auth.host().to_string(), | ||
| authority_name, | ||
| ue_id: entity_id, | ||
| ue_version_major: entity_version as u32, | ||
| resource_id: resource_id as u32, | ||
|
|
@@ -419,6 +383,86 @@ impl UUri { | |
| } | ||
| } | ||
|
|
||
| // [impl->dsn~uri-authority-name-length~1] | ||
| // [impl->dsn~uri-host-only~2] | ||
| fn verify_authority(authority: &str) -> Result<String, UUriError> { | ||
| Authority::try_from(authority) | ||
| .map_err(|e| UUriError::validation_error(format!("invalid authority: {}", e))) | ||
| .and_then(|auth| Self::verify_parsed_authority(&auth)) | ||
| } | ||
|
|
||
| // [impl->dsn~uri-authority-name-length~1] | ||
| // [impl->dsn~uri-host-only~2] | ||
| fn verify_parsed_authority(auth: &Authority) -> Result<String, UUriError> { | ||
| if auth.has_port() { | ||
| Err(UUriError::validation_error( | ||
| "uProtocol URI's authority must not contain port", | ||
| )) | ||
| } else if auth.has_username() || auth.has_password() { | ||
| Err(UUriError::validation_error( | ||
| "uProtocol URI's authority must not contain userinfo", | ||
| )) | ||
| } else { | ||
| let auth_name = auth.host().to_string(); | ||
| if auth_name.len() <= 128 { | ||
| Ok(auth_name) | ||
| } else { | ||
| Err(UUriError::validation_error( | ||
| "URI's authority name must not exceed 128 characters", | ||
| )) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn verify_major_version(major_version: u32) -> Result<u8, UUriError> { | ||
| u8::try_from(major_version).map_err(|_e| { | ||
| UUriError::ValidationError( | ||
| "uProtocol URI's major version must be an 8 bit unsigned integer".to_string(), | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| fn verify_resource_id(resource_id: u32) -> Result<u16, UUriError> { | ||
| u16::try_from(resource_id).map_err(|_e| { | ||
| UUriError::ValidationError( | ||
| "uProtocol URI's resource ID must be a 16 bit unsigned integer".to_string(), | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| /// Verifies that this UUri is indeed a valid uProtocol URI. | ||
| /// | ||
| /// This check is not necessary, if any of UUri's constructors functions has been used | ||
| /// to create the URI. However, if the origin of a UUri is unknown, e.g. when it has | ||
| /// been deserialized from a protobuf, then this function can be used to check if all | ||
| /// properties are compliant with the uProtocol specification. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if this UUri is not a valid uProtocol URI. The returned error may | ||
| /// contain details regarding the cause of the validation to have failed. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ```rust | ||
| /// use up_rust::UUri; | ||
| /// | ||
| /// let uuri = UUri { | ||
| /// authority_name: "valid_name".into(), | ||
| /// ue_id: 0x1000, | ||
| /// ue_version_major: 0x01, | ||
| /// resource_id: 0x8100, | ||
| /// ..Default::default() | ||
| /// }; | ||
| /// assert!(uuri.check_validity().is_ok()); | ||
| /// ``` | ||
| pub fn check_validity(&self) -> Result<(), UUriError> { | ||
| Self::verify_authority(self.authority_name.as_str())?; | ||
| Self::verify_major_version(self.ue_version_major)?; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, absolutely. Don't know how that slipped through. Good that we do the reviews ... ;-) |
||
| Self::verify_resource_id(self.resource_id)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Checks if this URI is empty. | ||
| /// | ||
| /// # Returns | ||
|
|
@@ -813,6 +857,46 @@ mod tests { | |
| use protobuf::Message; | ||
| use test_case::test_case; | ||
|
|
||
| // [utest->dsn~uri-authority-name-length~1] | ||
| // [utest->dsn~uri-host-only~2] | ||
| #[test_case(UUri { | ||
| authority_name: "invalid:5671".into(), | ||
| ue_id: 0x0000_8000, | ||
| ue_version_major: 0x01, | ||
| resource_id: 0x0002, | ||
| ..Default::default() | ||
| }; | ||
| "for authority including port")] | ||
| #[test_case(UUri { | ||
| authority_name: ['a'; 129].iter().collect::<String>(), | ||
| ue_id: 0x0000_8000, | ||
| ue_version_major: 0x01, | ||
| resource_id: 0x0002, | ||
| ..Default::default() | ||
| }; | ||
| "for authority exceeding max length")] | ||
| // additional test cases covering all sorts of invalid authority are | ||
| // included in [`test_from_string_fails`] | ||
| #[test_case(UUri { | ||
| authority_name: "valid".into(), | ||
| ue_id: 0x0000_8000, | ||
| ue_version_major: 0x0101, | ||
| resource_id: 0x0002, | ||
| ..Default::default() | ||
| }; | ||
| "for invalid major version")] | ||
| #[test_case(UUri { | ||
| authority_name: "valid".into(), | ||
| ue_id: 0x0000_8000, | ||
| ue_version_major: 0x01, | ||
| resource_id: 0x10002, | ||
| ..Default::default() | ||
| }; | ||
| "for invalid resource ID")] | ||
| fn test_check_validity_fails(uuri: UUri) { | ||
| assert!(uuri.check_validity().is_err()); | ||
| } | ||
|
|
||
| // [utest->req~uri-serialization~1] | ||
| // [utest->dsn~uri-scheme~1] | ||
| // [utest->dsn~uri-host-only~2] | ||
|
|
@@ -830,13 +914,15 @@ mod tests { | |
| #[test_case("up://MYVIN/1A23/1/a13#foobar"; "for URI with fragement")] | ||
| #[test_case("up://MYVIN:1000/1A23/1/A13"; "for authority with port")] | ||
| #[test_case("up://user:pwd@MYVIN/1A23/1/A13"; "for authority with userinfo")] | ||
| #[test_case("5up://MYVIN/55A1/1/1"; "for invalid scheme")] | ||
| #[test_case("up://MY#VIN/55A1/1/1"; "for invalid authority")] | ||
| #[test_case("up://MYVIN/55T1/1/1"; "for non-hex entity ID")] | ||
| #[test_case("up://MYVIN/123456789/1/1"; "for entity ID exceeding max length")] | ||
| #[test_case("up://MYVIN/55A1//1"; "for empty version")] | ||
| #[test_case("up://MYVIN/55A1/T/1"; "for non-hex version")] | ||
| #[test_case("up://MYVIN/55A1/123/1"; "for version exceeding max length")] | ||
| #[test_case("up://MYVIN/55A1/1/"; "for empty resource ID")] | ||
| #[test_case("up://MYVIN/55A1/1/1T"; "for non-hex resource ID")] | ||
| #[test_case("up://MYVIN/55A1/1/10001"; "for resource ID exceeding max length")] | ||
| fn test_from_string_fails(string: &str) { | ||
| let parsing_result = UUri::from_str(string); | ||
| assert!(parsing_result.is_err()); | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Yup, this is definitely one of the functions that'd be useful to have.