diff --git a/Cargo.toml b/Cargo.toml index 73c9a97..39f3ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,3 +127,12 @@ harness = false # allows Cucumber to print output instead of libtest # this prevents cargo from complaining about code blocks # excluded from tarpaulin coverage checks unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } + +[lints.clippy] +# see https://corrode.dev/blog/defensive-programming/#clippy-lints-for-defensive-programming +indexing_slicing = "deny" +fallible_impl_from = "deny" +wildcard_enum_match_arm = "deny" +unneeded_field_pattern = "deny" +fn_params_excessive_bools = "deny" +must_use_candidate = "allow" diff --git a/src/cloudevents.rs b/src/cloudevents.rs index 36585d5..cb22cd7 100644 --- a/src/cloudevents.rs +++ b/src/cloudevents.rs @@ -318,8 +318,12 @@ impl TryFrom for CloudEvent { })?; event.set_text_data(data); } - _ => { - event.set_binary_data(payload.into()); + UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED + | UPayloadFormat::UPAYLOAD_FORMAT_RAW + | UPayloadFormat::UPAYLOAD_FORMAT_SHM + | UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP + | UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP_TLV => { + event.set_binary_data(payload.to_vec()); } } } diff --git a/src/communication/udiscovery_client.rs b/src/communication/udiscovery_client.rs index e2a1c4d..22f83ee 100644 --- a/src/communication/udiscovery_client.rs +++ b/src/communication/udiscovery_client.rs @@ -163,7 +163,9 @@ mod tests { assert!(udiscovery_client .find_services(service_pattern_uri.clone(), false) .await - .is_ok_and(|result| result.len() == 1 && service_pattern_uri.matches(&result[0]))); + .is_ok_and( + |result| result.len() == 1 && service_pattern_uri.matches(result.first().unwrap()) + )); } #[tokio::test] @@ -227,6 +229,7 @@ mod tests { .get_service_topics(topic_pattern_uri.clone(), false) .await .is_ok_and(|result| result.len() == 1 - && topic_pattern_uri.matches(result[0].topic.as_ref().unwrap()))); + && topic_pattern_uri + .matches(result.first().and_then(|r| r.topic.as_ref()).unwrap()))); } } diff --git a/src/symphony.rs b/src/symphony.rs index 3aa868a..70b700e 100644 --- a/src/symphony.rs +++ b/src/symphony.rs @@ -174,25 +174,49 @@ impl RequestHandler for GetOperation { serde_json::to_string_pretty(&request_data).expect("failed to serialize Value") ); } - let deployment_spec: DeploymentSpec = - serde_json::from_value(request_data["deployment"].clone()).map_err(|err| { + let deployment_spec: DeploymentSpec = request_data + .get("deployment") + .ok_or_else(|| { debug!( source = source_uri, - "request does not contain DeploymentSpec: {err}" + "request does not contain DeploymentSpec" ); ServiceInvocationError::InvalidArgument( "request does not contain DeploymentSpec".to_string(), ) + }) + .and_then(|deployment| { + serde_json::from_value(deployment.clone()).map_err(|err| { + debug!( + source = source_uri, + "request contains invalid DeploymentSpec: {err}" + ); + ServiceInvocationError::InvalidArgument( + "request contains invalid DeploymentSpec".to_string(), + ) + }) })?; - let component_specs: Vec = - serde_json::from_value(request_data["components"].clone()).map_err(|err| { + let component_specs: Vec = request_data + .get("components") + .ok_or_else(|| { debug!( source = source_uri, - "request does not contain ComponentSpec array: {err}" + "request does not contain ComponentSpec array" ); ServiceInvocationError::InvalidArgument( "request does not contain ComponentSpec array".to_string(), ) + }) + .and_then(|components| { + serde_json::from_value(components.clone()).map_err(|err| { + debug!( + source = source_uri, + "request contains invalid ComponentSpec array: {err}" + ); + ServiceInvocationError::InvalidArgument( + "request contains invalid ComponentSpec array".to_string(), + ) + }) })?; let result = self @@ -249,28 +273,54 @@ impl RequestHandler for ApplyOperation { trace!("payload: {}", json); } - let deployment_spec: DeploymentSpec = - serde_json::from_value(request_data["deployment"].clone()).map_err(|err| { + let deployment_spec: DeploymentSpec = request_data + .get("deployment") + .ok_or_else(|| { debug!( source = source_uri, method = sink_uri, - "request does not contain DeploymentSpec: {err}" + "request does not contain DeploymentSpec" ); ServiceInvocationError::InvalidArgument( "request does not contain DeploymentSpec".to_string(), ) + }) + .and_then(|deployment| { + serde_json::from_value(deployment.clone()).map_err(|err| { + debug!( + source = source_uri, + method = sink_uri, + "request contains invalid DeploymentSpec: {err}" + ); + ServiceInvocationError::InvalidArgument( + "request contains invalid DeploymentSpec".to_string(), + ) + }) })?; - let affected_components: Vec = - serde_json::from_value(request_data["components"].clone()).map_err(|err| { + let affected_components: Vec = request_data + .get("components") + .ok_or_else(|| { debug!( source = source_uri, method = sink_uri, - "request does not contain ComponentSpec array: {err}" + "request does not contain ComponentSpec array" ); ServiceInvocationError::InvalidArgument( "request does not contain ComponentSpec array".to_string(), ) + }) + .and_then(|components| { + serde_json::from_value(components.clone()).map_err(|err| { + debug!( + source = source_uri, + method = sink_uri, + "request contains invalid ComponentSpec array: {err}" + ); + ServiceInvocationError::InvalidArgument( + "request contains invalid ComponentSpec array".to_string(), + ) + }) })?; let result = match resource_id { diff --git a/src/uattributes/uattributesvalidator.rs b/src/uattributes/uattributesvalidator.rs index 9fcb1df..9b06785 100644 --- a/src/uattributes/uattributesvalidator.rs +++ b/src/uattributes/uattributesvalidator.rs @@ -198,7 +198,9 @@ impl UAttributesValidators { UMessageType::UMESSAGE_TYPE_REQUEST => Box::new(RequestValidator), UMessageType::UMESSAGE_TYPE_RESPONSE => Box::new(ResponseValidator), UMessageType::UMESSAGE_TYPE_NOTIFICATION => Box::new(NotificationValidator), - _ => Box::new(PublishValidator), + UMessageType::UMESSAGE_TYPE_UNSPECIFIED | UMessageType::UMESSAGE_TYPE_PUBLISH => { + Box::new(PublishValidator) + } } } } diff --git a/src/umessage.rs b/src/umessage.rs index 5f13b7b..a3b210d 100644 --- a/src/umessage.rs +++ b/src/umessage.rs @@ -374,7 +374,13 @@ pub(crate) fn deserialize_protobuf_bytes( Err(e) => Err(UMessageError::DataSerializationError(e)), }) } - _ => { + UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED + | UPayloadFormat::UPAYLOAD_FORMAT_JSON + | UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP + | UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP_TLV + | UPayloadFormat::UPAYLOAD_FORMAT_RAW + | UPayloadFormat::UPAYLOAD_FORMAT_TEXT + | UPayloadFormat::UPAYLOAD_FORMAT_SHM => { let detail_msg = payload_format.to_media_type().map_or_else( || format!("Unknown payload format: {}", payload_format.value()), |mt| format!("Invalid/unsupported payload format: {mt}"), diff --git a/src/uri.rs b/src/uri.rs index 4122921..1ec8550 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -170,45 +170,45 @@ impl FromStr for UUri { .map_or(Ok(String::default()), Self::verify_parsed_authority)?; let path_segments = parsed_uri.path().segments(); - if path_segments.len() != 3 { - return Err(UUriError::serialization_error( + match path_segments { + [entity, version, resource] => { + if entity.is_empty() { + return Err(UUriError::serialization_error( + "URI must contain non-empty entity ID", + )); + } + let ue_id = u32::from_str_radix(entity, 16).map_err(|e| { + UUriError::serialization_error(format!("Cannot parse entity ID: {e}")) + })?; + if version.is_empty() { + return Err(UUriError::serialization_error( + "URI must contain non-empty entity version", + )); + } + let ue_version_major = u8::from_str_radix(version, 16).map_err(|e| { + UUriError::serialization_error(format!("Cannot parse entity version: {e}")) + })?; + if resource.is_empty() { + return Err(UUriError::serialization_error( + "URI must contain non-empty resource ID", + )); + } + let resource_id = u16::from_str_radix(resource, 16).map_err(|e| { + UUriError::serialization_error(format!("Cannot parse resource ID: {e}")) + })?; + + Ok(UUri { + authority_name, + ue_id, + ue_version_major: ue_version_major as u32, + resource_id: resource_id as u32, + ..Default::default() + }) + } + _ => Err(UUriError::serialization_error( "uProtocol URI must contain entity ID, entity version and resource ID", - )); + )), } - let entity = path_segments[0].as_str(); - if entity.is_empty() { - return Err(UUriError::serialization_error( - "URI must contain non-empty entity ID", - )); - } - let ue_id = u32::from_str_radix(entity, 16) - .map_err(|e| UUriError::serialization_error(format!("Cannot parse entity ID: {e}")))?; - let version = path_segments[1].as_str(); - if version.is_empty() { - return Err(UUriError::serialization_error( - "URI must contain non-empty entity version", - )); - } - let ue_version_major = u8::from_str_radix(version, 16).map_err(|e| { - UUriError::serialization_error(format!("Cannot parse entity version: {e}")) - })?; - let resource = path_segments[2].as_str(); - if resource.is_empty() { - return Err(UUriError::serialization_error( - "URI must contain non-empty resource ID", - )); - } - let resource_id = u16::from_str_radix(resource, 16).map_err(|e| { - UUriError::serialization_error(format!("Cannot parse resource ID: {e}")) - })?; - - Ok(UUri { - authority_name, - ue_id, - ue_version_major: ue_version_major as u32, - resource_id: resource_id as u32, - ..Default::default() - }) } }