From 2e00c5603e34c56048a6e6547179b173a038ce26 Mon Sep 17 00:00:00 2001 From: Stan Date: Mon, 3 Jun 2024 16:19:58 -0700 Subject: [PATCH] revert formatting and logging changes --- src/mock_server.rs | 439 ++-- src/protobuf.rs | 5374 ++++++++++++++++++------------------------- src/utils.rs | 1538 ++++++------- src/verification.rs | 626 +++-- 4 files changed, 3473 insertions(+), 4504 deletions(-) diff --git a/src/mock_server.rs b/src/mock_server.rs index ac070f9..50a9433 100644 --- a/src/mock_server.rs +++ b/src/mock_server.rs @@ -9,12 +9,12 @@ use std::task::{Context, Poll}; use std::thread; use anyhow::anyhow; -use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; use bytes::Bytes; use http::Method; -use hyper::server::accept; use hyper::{http, Request, Response}; +use hyper::server::accept; use lazy_static::lazy_static; use maplit::hashmap; use pact_matching::BodyMatchResult; @@ -29,267 +29,196 @@ use serde_json::{json, Value}; use tokio::net::TcpListener; use tokio::runtime::Handle; use tokio::sync::oneshot::{channel, Sender}; -use tonic::body::{empty_body, BoxBody}; +use tonic::body::{BoxBody, empty_body}; use tonic::metadata::MetadataMap; use tower::make::Shared; use tower::ServiceBuilder; use tower_http::ServiceBuilderExt; use tower_service::Service; -use tracing::{debug, error, instrument, trace, trace_span, Instrument}; +use tracing::{debug, error, Instrument, instrument, trace, trace_span}; use uuid::Uuid; use crate::dynamic_message::PactCodec; use crate::metadata::MetadataMatchResult; use crate::mock_service::MockService; use crate::tcp::TcpIncoming; -use crate::utils::{find_message_descriptor, last_name, split_name}; +use crate::utils::{last_name, split_name, find_message_descriptor}; lazy_static! { - pub static ref MOCK_SERVER_STATE: Mutex< - HashMap< - String, - ( - Sender<()>, - HashMap)> - ), - >, - > = Mutex::new(hashmap! {}); + pub static ref MOCK_SERVER_STATE: Mutex, HashMap)>)>> = Mutex::new(hashmap!{}); } /// Main mock server that will use the provided Pact to provide behaviour #[derive(Debug, Clone)] pub struct GrpcMockServer { - pact: V4Pact, - plugin_config: PluginData, - descriptors: HashMap, - routes: HashMap< - String, - ( - FileDescriptorSet, - FileDescriptorProto, - MethodDescriptorProto, - SynchronousMessage, - ), - >, - /// Server key for this mock server - pub server_key: String, - /// test context pass in from the test framework - pub test_context: HashMap, + pact: V4Pact, + plugin_config: PluginData, + descriptors: HashMap, + routes: HashMap, + /// Server key for this mock server + pub server_key: String, + /// test context pass in from the test framework + pub test_context: HashMap, } -impl GrpcMockServer { - /// Create a new mock server - pub fn new( - pact: V4Pact, - plugin_config: &PluginData, - test_context: HashMap, - ) -> Self { - GrpcMockServer { - pact, - plugin_config: plugin_config.clone(), - descriptors: Default::default(), - routes: Default::default(), - server_key: Uuid::new_v4().to_string(), - test_context, - } +impl GrpcMockServer +{ + /// Create a new mock server + pub fn new(pact: V4Pact, plugin_config: &PluginData, test_context: HashMap) -> Self { + GrpcMockServer { + pact, + plugin_config: plugin_config.clone(), + descriptors: Default::default(), + routes: Default::default(), + server_key: Uuid::new_v4().to_string(), + test_context } - - /// Start the mock server, consuming this instance and returning the connection details - #[instrument(skip(self))] - pub async fn start_server( - mut self, - host_interface: &str, - port: u32, - tls: bool, - ) -> anyhow::Result { - // Get all the descriptors from the Pact file and parse them - for (key, value) in &self.plugin_config.configuration { - if let Value::Object(map) = value { - if let Some(descriptor) = map.get("protoDescriptors") { - let bytes = BASE64.decode(json_to_string(descriptor))?; - let buffer = Bytes::from(bytes); - let fds = FileDescriptorSet::decode(buffer)?; - self.descriptors.insert(key.clone(), fds); - } - } - } - - if self.descriptors.is_empty() { - return Err(anyhow!( - "Pact file does not contain any Protobuf descriptors" - )); - } - - // Build a map of routes using the interactions in the Pact file - self.routes = self - .pact - .interactions - .iter() - .filter_map(|i| i.as_v4_sync_message()) - .filter_map(|i| { - i.plugin_config - .get("protobuf") - .map(|p| (p.clone(), i.clone())) - }) - .filter_map(|(c, i)| { - if let Some(key) = c.get("descriptorKey") { - if let Some(descriptors) = self.descriptors.get(json_to_string(key).as_str()) { - if let Some(service) = c.get("service") { - if let Some((service_name, method_name)) = - json_to_string(service).split_once('/') - { - return descriptors - .file - .iter() - .find_map(|fd| { - fd.service - .iter() - .find(|s| { - s.name.clone().unwrap_or_default() == service_name - }) - .map(|s| { - s.method - .iter() - .find(|m| { - m.name.clone().unwrap_or_default() - == method_name - }) - .map(|m| { - ( - format!("{service_name}/{method_name}"), - ( - descriptors.clone(), - fd.clone(), - m.clone(), - i.clone(), - ), - ) - }) - }) - }) - .unwrap(); - } else { - // protobuf service was not properly formed / - None - } - } else { - // protobuf plugin configuration section did not have a service defined - None - } - } else { - // protobuf plugin configuration section did not have a matching key to the descriptors - None - } - } else { - // Interaction did not have a protobuf plugin configuration section - None - } - }) - .collect(); - - // Bind to a OS provided port and create a TCP listener - let interface = if host_interface.is_empty() { - "[::1]" - } else { - host_interface - }; - let addr: SocketAddr = format!("{interface}:{port}").parse()?; - trace!("setting up mock server {addr}"); - - let (snd, rcr) = channel::<()>(); - { - let mut guard = MOCK_SERVER_STATE.lock().unwrap(); - // Initialise all the routes with an initial state of not received - let initial_state = self - .routes - .keys() - .map(|k| (k.clone(), (0, vec![]))) - .collect(); - guard.insert(self.server_key.clone(), (snd, initial_state)); + } + + /// Start the mock server, consuming this instance and returning the connection details + #[instrument(skip(self))] + pub async fn start_server(mut self, host_interface: &str, port: u32, tls: bool) -> anyhow::Result { + // Get all the descriptors from the Pact file and parse them + for (key, value) in &self.plugin_config.configuration { + if let Value::Object(map) = value { + if let Some(descriptor) = map.get("protoDescriptors") { + let bytes = BASE64.decode(json_to_string(descriptor))?; + let buffer = Bytes::from(bytes); + let fds = FileDescriptorSet::decode(buffer)?; + self.descriptors.insert(key.clone(), fds); } + } + } - let listener = TcpListener::bind(addr).await?; - let address = listener.local_addr()?; - - self.update_mock_server_address(&address); - - let handle = Handle::current(); - // because Rust - let key = self.server_key.clone(); - let key2 = self.server_key.clone(); - let result = thread::spawn(move || { - let incoming_stream = TcpIncoming { inner: listener }; - let incoming = accept::from_stream(incoming_stream); - - trace!("setting up middleware"); - let service = ServiceBuilder::new() - // High level logging of requests and responses - .trace_for_grpc() - // Wrap a `Service` in our middleware stack - .service(self); - - trace!("setting up HTTP server"); - let server = hyper::Server::builder(incoming) - .http2_only(true) - // // // .http2_initial_connection_window_size(init_connection_window_size) - // // // .http2_initial_stream_window_size(init_stream_window_size) - // // // .http2_max_concurrent_streams(max_concurrent_streams) - // // // .http2_keep_alive_interval(http2_keepalive_interval) - // // // .http2_keep_alive_timeout(http2_keepalive_timeout) - // // // .http2_max_frame_size(max_frame_size) - .serve(Shared::new(service)) - .with_graceful_shutdown(async move { - let _ = rcr.await; - trace!("Received shutdown signal for server {}", key); - }) - .instrument(tracing::trace_span!( - "mock server", - key = key2.as_str(), - port = address.port() - )); - - trace!("spawning server onto runtime"); - handle.spawn(server); - trace!("spawning server onto runtime - done"); - }) - .join(); + if self.descriptors.is_empty() { + return Err(anyhow!("Pact file does not contain any Protobuf descriptors")); + } - if result.is_err() { - Err(anyhow!("Failed to start mock server thread")) + // Build a map of routes using the interactions in the Pact file + self.routes = self.pact.interactions.iter() + .filter_map(|i| i.as_v4_sync_message()) + .filter_map(|i| i.plugin_config.get("protobuf").map(|p| (p.clone(), i.clone()))) + .filter_map(|(c, i)| { + if let Some(key) = c.get("descriptorKey") { + if let Some(descriptors) = self.descriptors.get(json_to_string(key).as_str()) { + if let Some(service) = c.get("service") { + if let Some((service_name, method_name)) = json_to_string(service).split_once('/') { + return descriptors.file.iter().find_map(|fd| fd.service.iter().find(|s| s.name.clone().unwrap_or_default() == service_name).map( |s| s.method.iter(). + find(|m| m.name.clone().unwrap_or_default() == method_name). + map(|m| (format!("{service_name}/{method_name}"), (descriptors.clone(), fd.clone(), m.clone(), i.clone()))) + ) + ).unwrap(); + } else { + // protobuf service was not properly formed / + None + } + } else { + // protobuf plugin configuration section did not have a service defined + None + } + } else { + // protobuf plugin configuration section did not have a matching key to the descriptors + None + } } else { - trace!("Mock server setup OK"); - Ok(address) + // Interaction did not have a protobuf plugin configuration section + None } + }).collect(); + + // Bind to a OS provided port and create a TCP listener + let interface = if host_interface.is_empty() { + "[::1]" + } else { + host_interface + }; + let addr: SocketAddr = format!("{interface}:{port}").parse()?; + trace!("setting up mock server {addr}"); + + let (snd, rcr) = channel::<()>(); + { + let mut guard = MOCK_SERVER_STATE.lock().unwrap(); + // Initialise all the routes with an initial state of not received + let initial_state = self.routes.keys() + .map(|k| (k.clone(), (0, vec![]))) + .collect(); + guard.insert(self.server_key.clone(), (snd, initial_state)); } - fn update_mock_server_address(&mut self, address: &SocketAddr) { - self.test_context.insert( - "mockServer".to_string(), - json!({ - "href": format!("http://{}:{}", address.ip(), address.port()), - "port": address.port() - }), - ); + let listener = TcpListener::bind(addr).await?; + let address = listener.local_addr()?; + + self.update_mock_server_address(&address); + + let handle = Handle::current(); + // because Rust + let key = self.server_key.clone(); + let key2 = self.server_key.clone(); + let result = thread::spawn(move || { + let incoming_stream = TcpIncoming { inner: listener }; + let incoming = accept::from_stream(incoming_stream); + + trace!("setting up middleware"); + let service = ServiceBuilder::new() + // High level logging of requests and responses + .trace_for_grpc() + // Wrap a `Service` in our middleware stack + .service(self); + + trace!("setting up HTTP server"); + let server = hyper::Server::builder(incoming) + .http2_only(true) + // // // .http2_initial_connection_window_size(init_connection_window_size) + // // // .http2_initial_stream_window_size(init_stream_window_size) + // // // .http2_max_concurrent_streams(max_concurrent_streams) + // // // .http2_keep_alive_interval(http2_keepalive_interval) + // // // .http2_keep_alive_timeout(http2_keepalive_timeout) + // // // .http2_max_frame_size(max_frame_size) + .serve(Shared::new(service)) + .with_graceful_shutdown(async move { + let _ = rcr.await; + trace!("Received shutdown signal for server {}", key); + }) + .instrument(tracing::trace_span!("mock server", key = key2.as_str(), port = address.port())); + + trace!("spawning server onto runtime"); + handle.spawn(server); + trace!("spawning server onto runtime - done"); + }).join(); + + if result.is_err() { + Err(anyhow!("Failed to start mock server thread")) + } else { + trace!("Mock server setup OK"); + Ok(address) } + } + + fn update_mock_server_address(&mut self, address: &SocketAddr) { + self.test_context.insert("mockServer".to_string(), json!({ + "href": format!("http://{}:{}", address.ip(), address.port()), + "port": address.port() + })); + } } -impl Service> for GrpcMockServer { - type Response = Response; - type Error = hyper::Error; - type Future = Pin> + Send>>; +impl Service> for GrpcMockServer { + type Response = Response; + type Error = hyper::Error; + type Future = Pin> + Send>>; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } - #[instrument(skip(self), level = "trace")] - fn call(&mut self, req: Request) -> Self::Future { - let routes = self.routes.clone(); - let server_key = self.server_key.clone(); - let pact = self.pact.clone(); + #[instrument(skip(self), level = "trace")] + fn call(&mut self, req: Request) -> Self::Future { + let routes = self.routes.clone(); + let server_key = self.server_key.clone(); + let pact = self.pact.clone(); - Box::pin(async move { - debug!("Got request {req:?}"); + Box::pin(async move { + trace!("Got request {req:?}"); let headers = req.headers(); let metadata = MetadataMap::from_headers(headers.clone()); @@ -313,10 +242,10 @@ impl Service> for GrpcMockServer { let service_name = last_name(service); let lookup = format!("{service_name}/{method}"); if let Some((file, file_descriptor, method_descriptor, message)) = routes.get(lookup.as_str()) { - debug!(message = message.description.as_str(), "Found route for service call"); + trace!(message = message.description.as_str(), "Found route for service call"); let file_descriptors: HashMap = file.file.iter().map( |des| (des.name.clone().unwrap_or_default(), des)).collect(); - let input_name: &String = method_descriptor.input_type.as_ref().expect(format!( + let input_name = method_descriptor.input_type.as_ref().expect(format!( "Input message name is empty for service {}/{}", service_name, method).as_str()); let (input_message_name, input_package_name) = split_name(input_name); let input_message = find_message_descriptor( @@ -327,8 +256,6 @@ impl Service> for GrpcMockServer { let (output_message_name, output_package_name) = split_name(output_name); let output_message = find_message_descriptor( output_message_name, output_package_name, file_descriptor, &file_descriptors); - - debug!(?input_message, ?output_message, "Found input and output messages"); if let Ok(input_message) = input_message { if let Ok(output_message) = output_message { @@ -338,8 +265,8 @@ impl Service> for GrpcMockServer { pact ); let mut grpc = tonic::server::Grpc::new(codec); - let response: Response> = grpc.unary(mock_service, req).await; - debug!(?response, ">> sending response"); + let response = grpc.unary(mock_service, req).await; + trace!(?response, ">> sending response"); Ok(response) } else { error!("Did not find the descriptor for the output message {}", output_message_name); @@ -367,37 +294,37 @@ impl Service> for GrpcMockServer { } } }.instrument(trace_span!("mock_server_handler", key = self.server_key.as_str()))) - } + } } fn invalid_media() -> Response { - http::Response::builder() - .status(415) - .body(empty_body()) - .unwrap() + http::Response::builder() + .status(415) + .body(empty_body()) + .unwrap() } fn invalid_method() -> Response { - http::Response::builder() - .status(405) - .body(empty_body()) - .unwrap() + http::Response::builder() + .status(405) + .body(empty_body()) + .unwrap() } fn invalid_path() -> Response { - http::Response::builder() - .status(200) - .header("grpc-status", "12") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap() + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap() } fn failed_precondition() -> Response { - http::Response::builder() - .status(200) - .header("grpc-status", "9") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap() + http::Response::builder() + .status(200) + .header("grpc-status", "9") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap() } diff --git a/src/protobuf.rs b/src/protobuf.rs index 42f76c7..18d1ac2 100644 --- a/src/protobuf.rs +++ b/src/protobuf.rs @@ -1,372 +1,269 @@ //! Module for processing and comparing protobuf messages -use core::panic; use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::Path; use anyhow::anyhow; -use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; use itertools::{Either, Itertools}; use maplit::{btreemap, hashmap}; use num::ToPrimitive; use pact_models::generators::Generator; use pact_models::json_utils::json_to_string; use pact_models::matchingrules; -use pact_models::matchingrules::expressions::{ - is_matcher_def, parse_matcher_def, MatchingRuleDefinition, ValueType, -}; +use pact_models::matchingrules::expressions::{is_matcher_def, MatchingRuleDefinition, parse_matcher_def, ValueType}; use pact_models::matchingrules::MatchingRuleCategory; use pact_models::path_exp::DocPath; use pact_models::prelude::RuleLogic; -use pact_plugin_driver::proto::body::ContentTypeHint; -use pact_plugin_driver::proto::interaction_response::MarkupType; use pact_plugin_driver::proto::{ - Body, InteractionResponse, MatchingRule, MatchingRules, PluginConfiguration, + Body, + InteractionResponse, + MatchingRule, + MatchingRules, + PluginConfiguration }; +use pact_plugin_driver::proto::body::ContentTypeHint; +use pact_plugin_driver::proto::interaction_response::MarkupType; use pact_plugin_driver::utils::{proto_value_to_json, proto_value_to_string, to_proto_struct}; +use prost_types::{DescriptorProto, FieldDescriptorProto, FileDescriptorProto, ServiceDescriptorProto, Struct}; use prost_types::field_descriptor_proto::Type; use prost_types::value::Kind; -use prost_types::{ - DescriptorProto, FieldDescriptorProto, FileDescriptorProto, ServiceDescriptorProto, Struct, -}; use serde_json::{json, Value}; use tokio::fs::File; use tokio::io::AsyncReadExt; -use tracing::{debug, error, info, instrument, trace, warn}; +use tracing::{debug, error, instrument, trace, warn}; use tracing_core::LevelFilter; use crate::message_builder::{MessageBuilder, MessageFieldValue, MessageFieldValueType, RType}; -use crate::metadata::{process_metadata, MessageMetadata}; +use crate::metadata::{MessageMetadata, process_metadata}; use crate::protoc::Protoc; use crate::utils::{ - find_enum_value_by_name, find_enum_value_by_name_in_message, find_message_descriptor, - find_nested_type, is_map_field, is_repeated_field, prost_string, split_name, + find_enum_value_by_name, + find_enum_value_by_name_in_message, + find_message_descriptor, + find_nested_type, + is_map_field, + is_repeated_field, + prost_string, + split_name }; /// Process the provided protobuf file and configure the interaction pub(crate) async fn process_proto( - proto_file: String, - protoc: &Protoc, - config: &BTreeMap, + proto_file: String, + protoc: &Protoc, + config: &BTreeMap ) -> anyhow::Result<(Vec, PluginConfiguration)> { - debug!("Parsing proto file '{}'", proto_file); - trace!(">> process_proto({proto_file}, {config:?})"); - - let proto_file = Path::new(proto_file.as_str()); - let (descriptors, digest, descriptor_bytes) = protoc.parse_proto_file(proto_file).await?; - debug!( - "Parsed proto file OK, file descriptors = {:?}", - descriptors - .file - .iter() - .map(|file| file.name.as_ref()) - .collect_vec() - ); - trace!("Descriptor bytes {:?}", descriptor_bytes.as_slice()); - - let file_descriptors: HashMap = descriptors - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - let file_name = &*proto_file.file_name().unwrap_or_default().to_string_lossy(); - let descriptor = match file_descriptors.get(file_name) { - None => { - return Err(anyhow!( - "Did not find a file proto descriptor for the provided proto file '{}'", - file_name - )) - } - Some(des) => *des, - }; - - if LevelFilter::current() >= LevelFilter::TRACE { - trace!("All message types in proto descriptor"); - for message_type in &descriptor.message_type { - trace!(" {:?}", message_type.name); - } + debug!("Parsing proto file '{}'", proto_file); + trace!(">> process_proto({proto_file}, {config:?})"); + + let proto_file = Path::new(proto_file.as_str()); + let (descriptors, digest, descriptor_bytes) = protoc.parse_proto_file(proto_file).await?; + debug!("Parsed proto file OK, file descriptors = {:?}", descriptors.file.iter().map(|file| file.name.as_ref()).collect_vec()); + trace!("Descriptor bytes {:?}", descriptor_bytes.as_slice()); + + let file_descriptors: HashMap = descriptors.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + let file_name = &*proto_file.file_name().unwrap_or_default().to_string_lossy(); + let descriptor = match file_descriptors.get(file_name) { + None => return Err(anyhow!("Did not find a file proto descriptor for the provided proto file '{}'", file_name)), + Some(des) => *des + }; + + if LevelFilter::current() >= LevelFilter::TRACE { + trace!("All message types in proto descriptor"); + for message_type in &descriptor.message_type { + trace!(" {:?}", message_type.name); } + } - let descriptor_encoded = BASE64.encode(&descriptor_bytes); - let descriptor_hash = format!("{:x}", md5::compute(&descriptor_bytes)); - let mut interactions = vec![]; - - if let Some(message_type) = config.get("pact:message-type") { - let message = proto_value_to_string(message_type).ok_or_else(|| { - anyhow!("Did not get a valid value for 'pact:message-type'. It should be a string") - })?; - debug!("Configuring a Protobuf message {}", message); - let result = configure_protobuf_message( - message.as_str(), - config, - descriptor, - descriptor_hash.as_str(), - &file_descriptors, - )?; - interactions.push(result); - } else if let Some(service_name) = config.get("pact:proto-service") { - let service_name = proto_value_to_string(service_name).ok_or_else(|| { - anyhow!("Did not get a valid value for 'pact:proto-service'. It should be a string") - })?; - debug!("Configuring a Protobuf service {}", service_name); - let (request_part, response_part) = configure_protobuf_service( - service_name.as_str(), - config, - descriptor, - &file_descriptors, - descriptor_hash.as_str(), - )?; - if let Some(request_part) = request_part { - interactions.push(request_part); - } - interactions.extend_from_slice(&response_part); + let descriptor_encoded = BASE64.encode(&descriptor_bytes); + let descriptor_hash = format!("{:x}", md5::compute(&descriptor_bytes)); + let mut interactions = vec![]; + + if let Some(message_type) = config.get("pact:message-type") { + let message = proto_value_to_string(message_type) + .ok_or_else(|| anyhow!("Did not get a valid value for 'pact:message-type'. It should be a string"))?; + debug!("Configuring a Protobuf message {}", message); + let result = configure_protobuf_message(message.as_str(), config, descriptor, + descriptor_hash.as_str(), &file_descriptors)?; + interactions.push(result); + } else if let Some(service_name) = config.get("pact:proto-service") { + let service_name = proto_value_to_string(service_name) + .ok_or_else(|| anyhow!("Did not get a valid value for 'pact:proto-service'. It should be a string"))?; + debug!("Configuring a Protobuf service {}", service_name); + let (request_part, response_part) = configure_protobuf_service(service_name.as_str(), config, descriptor, + &file_descriptors, descriptor_hash.as_str())?; + if let Some(request_part) = request_part { + interactions.push(request_part); } + interactions.extend_from_slice(&response_part); + } - let mut f = File::open(proto_file).await?; - let mut file_contents = String::new(); - f.read_to_string(&mut file_contents).await?; - - let digest_str = format!("{:x}", digest); - let plugin_config = PluginConfiguration { - interaction_configuration: None, - pact_configuration: Some(to_proto_struct(&hashmap! { - digest_str => json!({ - "protoFile": file_contents, - "protoDescriptors": descriptor_encoded - }) - })), - }; + let mut f = File::open(proto_file).await?; + let mut file_contents = String::new(); + f.read_to_string(&mut file_contents).await?; + + let digest_str = format!("{:x}", digest); + let plugin_config = PluginConfiguration { + interaction_configuration: None, + pact_configuration: Some(to_proto_struct(&hashmap!{ + digest_str => json!({ + "protoFile": file_contents, + "protoDescriptors": descriptor_encoded + }) + })) + }; - Ok((interactions, plugin_config)) + Ok((interactions, plugin_config)) } /// Configure the interaction for a Protobuf service method, which has an input and output message fn configure_protobuf_service( - service_name: &str, - config: &BTreeMap, - descriptor: &FileDescriptorProto, - all_descriptors: &HashMap, - descriptor_hash: &str, + service_name: &str, + config: &BTreeMap, + descriptor: &FileDescriptorProto, + all_descriptors: &HashMap, + descriptor_hash: &str ) -> anyhow::Result<(Option, Vec)> { - debug!(">> configure_protobuf_service({service_name}, {config:?}, {descriptor_hash})"); - - debug!( - "Looking for service and method with name '{}'", - service_name - ); - let (service, proc_name) = service_name.split_once('/').ok_or_else(|| { - anyhow!( - "Service name '{}' is not valid, it should be of the form /", - service_name - ) - })?; - let service_descriptor = descriptor - .service - .iter() - .find(|p| p.name.clone().unwrap_or_default() == service) - .ok_or_else(|| anyhow!("Did not find a descriptor for service '{}'", service_name))?; - debug!("service_descriptor = {:?}", service_descriptor); - construct_protobuf_interaction_for_service( - service_descriptor, - config, - service, - proc_name, - all_descriptors, - descriptor, - ) + trace!(">> configure_protobuf_service({service_name}, {config:?}, {descriptor_hash})"); + + debug!("Looking for service and method with name '{}'", service_name); + let (service, proc_name) = service_name.split_once('/') + .ok_or_else(|| anyhow!("Service name '{}' is not valid, it should be of the form /", service_name))?; + let service_descriptor = descriptor.service + .iter().find(|p| p.name.clone().unwrap_or_default() == service) + .ok_or_else(|| anyhow!("Did not find a descriptor for service '{}'", service_name))?; + trace!("service_descriptor = {:?}", service_descriptor); + construct_protobuf_interaction_for_service(service_descriptor, config, service, + proc_name, all_descriptors, descriptor) .map(|(request, response)| { - let plugin_configuration = Some(PluginConfiguration { - interaction_configuration: Some(to_proto_struct(&hashmap! { - "service".to_string() => Value::String( - service_name.split_once(':').map(|(s, _)| s).unwrap_or(service_name).to_string() - ), - "descriptorKey".to_string() => Value::String(descriptor_hash.to_string()) - })), - pact_configuration: None, - }); - trace!("request = {request:?}"); - trace!("response = {response:?}"); - ( - request.map(|r| InteractionResponse { - plugin_configuration: plugin_configuration.clone(), - ..r - }), - response - .iter() - .map(|r| InteractionResponse { - plugin_configuration: plugin_configuration.clone(), - ..r.clone() - }) - .collect(), - ) + let plugin_configuration = Some(PluginConfiguration { + interaction_configuration: Some(to_proto_struct(&hashmap! { + "service".to_string() => Value::String( + service_name.split_once(':').map(|(s, _)| s).unwrap_or(service_name).to_string() + ), + "descriptorKey".to_string() => Value::String(descriptor_hash.to_string()) + })), + pact_configuration: None + }); + trace!("request = {request:?}"); + trace!("response = {response:?}"); + ( + request.map(|r| InteractionResponse { plugin_configuration: plugin_configuration.clone(), .. r }), + response.iter().map(|r| InteractionResponse { plugin_configuration: plugin_configuration.clone(), .. r.clone() }).collect() + ) }) } /// Constructs an interaction for the given Protobuf service descriptor fn construct_protobuf_interaction_for_service( - descriptor: &ServiceDescriptorProto, - config: &BTreeMap, - service_name: &str, - method_name: &str, - all_descriptors: &HashMap, - file_descriptor: &FileDescriptorProto, + descriptor: &ServiceDescriptorProto, + config: &BTreeMap, + service_name: &str, + method_name: &str, + all_descriptors: &HashMap, + file_descriptor: &FileDescriptorProto ) -> anyhow::Result<(Option, Vec)> { - debug!( - ">> construct_protobuf_interaction_for_service({config:?}, {service_name}, {method_name})" - ); - - let (method_name, service_part) = if method_name.contains(':') { - method_name.split_once(':').unwrap_or((method_name, "")) - } else { - (method_name, "") - }; - debug!(method_name, service_part, "looking up method descriptor"); - let method_descriptor = descriptor - .method - .iter() - .find(|m| m.name.clone().unwrap_or_default() == method_name) - .ok_or_else(|| { - anyhow!( - "Did not find a method descriptor for method '{}' in service '{}'", - method_name, - service_name - ) - })?; - - let input_name = method_descriptor.input_type.as_ref().ok_or_else(|| { - anyhow!( - "Input message name is empty for service {}/{}", - service_name, - method_name - ) - })?; - let output_name = method_descriptor.output_type.as_ref().ok_or_else(|| { - anyhow!( - "Input message name is empty for service {}/{}", - service_name, - method_name - ) - })?; - let (input_message_name, input_package) = split_name(input_name.as_str()); - let (output_message_name, output_package) = split_name(output_name.as_str()); - - info!(%input_name, ?input_package, input_message_name, "Input message"); - info!(%output_name, ?output_package, output_message_name, "Output message"); - - let request_descriptor = find_message_descriptor( - input_message_name, - input_package, - file_descriptor, - all_descriptors, - )?; - let response_descriptor = find_message_descriptor( - output_message_name, - output_package, - file_descriptor, - all_descriptors, - )?; - - info!("request_descriptor = {:?}", request_descriptor); - info!("response_descriptor = {:?}", response_descriptor); - - let request_part_config: BTreeMap = - request_part(config, service_part)?; - info!(config = ?request_part_config, service_part, "Processing request part config"); - let request_metadata = process_metadata(config.get("requestMetadata"))?; + trace!(">> construct_protobuf_interaction_for_service({config:?}, {service_name}, {method_name})"); + + let (method_name, service_part) = if method_name.contains(':') { + method_name.split_once(':').unwrap_or((method_name, "")) + } else { + (method_name, "") + }; + trace!(method_name, service_part, "looking up method descriptor"); + let method_descriptor = descriptor.method.iter() + .find(|m| m.name.clone().unwrap_or_default() == method_name) + .ok_or_else(|| anyhow!("Did not find a method descriptor for method '{}' in service '{}'", method_name, service_name))?; + + let input_name = method_descriptor.input_type.as_ref() + .ok_or_else(|| anyhow!("Input message name is empty for service {}/{}", service_name, method_name))?; + let output_name = method_descriptor.output_type.as_ref() + .ok_or_else(|| anyhow!("Input message name is empty for service {}/{}", service_name, method_name))?; + let (input_message_name, input_package) = split_name(input_name.as_str()); + let (output_message_name, output_package) = split_name(output_name.as_str()); + + trace!(%input_name, ?input_package, input_message_name, "Input message"); + trace!(%output_name, ?output_package, output_message_name, "Output message"); + + let request_descriptor = find_message_descriptor(input_message_name, input_package, file_descriptor, all_descriptors)?; + let response_descriptor = find_message_descriptor(output_message_name, output_package, file_descriptor, all_descriptors)?; + + trace!("request_descriptor = {:?}", request_descriptor); + trace!("response_descriptor = {:?}", response_descriptor); + + let request_part_config = request_part(config, service_part)?; + trace!(config = ?request_part_config, service_part, "Processing request part config"); + let request_metadata = process_metadata(config.get("requestMetadata"))?; + let interaction = construct_protobuf_interaction_for_message(&request_descriptor, + &request_part_config, input_message_name, "", file_descriptor, all_descriptors, + request_metadata.as_ref() + )?; + let request_part = Some(InteractionResponse { + part_name: "request".into(), + .. interaction + }); + + let response_part_config = response_part(config, service_part)?; + trace!(config = ?response_part_config, service_part, "Processing response part config"); + let mut response_part = vec![]; + for (config, md_config) in response_part_config { + let response_metadata = process_metadata(md_config)?; let interaction = construct_protobuf_interaction_for_message( - &request_descriptor, - &request_part_config, - input_message_name, - "", - file_descriptor, - all_descriptors, - request_metadata.as_ref(), + &response_descriptor, &config, output_message_name, "", + file_descriptor, all_descriptors, response_metadata.as_ref() )?; - let request_part = Some(InteractionResponse { - part_name: "request".into(), - ..interaction - }); - - let response_part_config = response_part(config, service_part)?; - info!(config = ?response_part_config, service_part, "Processing response part config"); - let mut response_part = vec![]; - for (config, md_config) in response_part_config { - let response_metadata = process_metadata(md_config)?; - let interaction = construct_protobuf_interaction_for_message( - &response_descriptor, - &config, - output_message_name, - "", - file_descriptor, - all_descriptors, - response_metadata.as_ref(), - )?; - response_part.push(InteractionResponse { - part_name: "response".into(), - ..interaction - }); - } + response_part.push(InteractionResponse { part_name: "response".into(), .. interaction }); + } - Ok((request_part, response_part)) + Ok((request_part, response_part)) } fn response_part<'a>( - config: &'a BTreeMap, - service_part: &str, -) -> anyhow::Result< - Vec<( - BTreeMap, - Option<&'a prost_types::Value>, - )>, -> { - trace!(?config, ?service_part, "response_part"); - if service_part == "response" { - Ok(vec![(config.clone(), None)]) - } else if let Some(response_config) = config.get("response") { - Ok(response_config - .kind - .as_ref() - .map(|kind| match kind { - Kind::StructValue(s) => { - let metadata = config.get("responseMetadata"); - vec![(s.fields.clone(), metadata)] - } - Kind::ListValue(l) => l - .values - .iter() - .filter_map(|v| { - v.kind.as_ref().and_then(|k| match k { - Kind::StructValue(s) => Some((s.fields.clone(), None)), - Kind::StringValue(_) => { - Some((btreemap! { "value".to_string() => v.clone() }, None)) - } - _ => None, - }) - }) - .collect(), - Kind::StringValue(_) => vec![( - btreemap! { "value".to_string() => response_config.clone() }, - None, - )], - _ => vec![], + config: &'a BTreeMap, + service_part: &str +) -> anyhow::Result, Option<&'a prost_types::Value>)>> { + trace!(?config, ?service_part, "response_part"); + if service_part == "response" { + Ok(vec![(config.clone(), None)]) + } else if let Some(response_config) = config.get("response") { + Ok(response_config.kind.as_ref() + .map(|kind| { + match kind { + Kind::StructValue(s) => { + let metadata = config.get("responseMetadata"); + vec![(s.fields.clone(), metadata)] + }, + Kind::ListValue(l) => l.values.iter().filter_map(|v| { + v.kind.as_ref().and_then(|k| match k { + Kind::StructValue(s) => Some((s.fields.clone(), None)), + Kind::StringValue(_) => Some((btreemap! { "value".to_string() => v.clone() }, None)), + _ => None }) - .unwrap_or_default()) - } else if let Some(response_md_config) = config.get("responseMetadata") { - Ok(vec![(btreemap! {}, Some(response_md_config))]) - } else { - Ok(vec![]) - } + }) + .collect(), + Kind::StringValue(_) => vec![(btreemap! { "value".to_string() => response_config.clone() }, None)], + _ => vec![] + } + }).unwrap_or_default()) + } else if let Some(response_md_config) = config.get("responseMetadata") { + Ok(vec![(btreemap!{}, Some(response_md_config))]) + } else { + Ok(vec![]) + } } fn request_part( - config: &BTreeMap, - service_part: &str, + config: &BTreeMap, + service_part: &str ) -> anyhow::Result> { - if service_part == "request" { - Ok(config.clone()) - } else { - let config = config.get("request").and_then(|request_config| { + if service_part == "request" { + Ok(config.clone()) + } else { + let config = config.get("request").and_then(|request_config| { request_config.kind.as_ref().map(|kind| { match kind { Kind::StructValue(s) => Ok(s.fields.clone()), @@ -378,204 +275,145 @@ fn request_part( } }) }); - match config { - None => Ok(btreemap! {}), - Some(result) => result, - } + match config { + None => Ok(btreemap!{}), + Some(result) => result } + } } /// Configure the interaction for a single Protobuf message fn configure_protobuf_message( - message_name: &str, - config: &BTreeMap, - descriptor: &FileDescriptorProto, - descriptor_hash: &str, - all_descriptors: &HashMap, + message_name: &str, + config: &BTreeMap, + descriptor: &FileDescriptorProto, + descriptor_hash: &str, + all_descriptors: &HashMap ) -> anyhow::Result { - trace!( - ">> configure_protobuf_message({}, {:?})", - message_name, - descriptor_hash - ); - debug!("Looking for message of type '{}'", message_name); - let message_descriptor = descriptor - .message_type - .iter() - .find(|p| p.name.clone().unwrap_or_default() == message_name) - .ok_or_else(|| anyhow!("Did not find a descriptor for message '{}'", message_name))?; - construct_protobuf_interaction_for_message( - message_descriptor, - config, - message_name, - "", - descriptor, - all_descriptors, - None, - ) - .map(|interaction| InteractionResponse { + trace!(">> configure_protobuf_message({}, {:?})", message_name, descriptor_hash); + debug!("Looking for message of type '{}'", message_name); + let message_descriptor = descriptor.message_type + .iter().find(|p| p.name.clone().unwrap_or_default() == message_name) + .ok_or_else(|| anyhow!("Did not find a descriptor for message '{}'", message_name))?; + construct_protobuf_interaction_for_message(message_descriptor, config, message_name, "", descriptor, all_descriptors, None) + .map(|interaction| { + InteractionResponse { plugin_configuration: Some(PluginConfiguration { - interaction_configuration: Some(to_proto_struct(&hashmap! { - "message".to_string() => Value::String(message_name.to_string()), - "descriptorKey".to_string() => Value::String(descriptor_hash.to_string()) - })), - pact_configuration: None, + interaction_configuration: Some(to_proto_struct(&hashmap!{ + "message".to_string() => Value::String(message_name.to_string()), + "descriptorKey".to_string() => Value::String(descriptor_hash.to_string()) + })), + pact_configuration: None }), - ..interaction + .. interaction + } }) } /// Constructs an interaction for the given Protobuf message descriptor #[instrument(ret, skip(message_descriptor, file_descriptor, all_descriptors))] fn construct_protobuf_interaction_for_message( - message_descriptor: &DescriptorProto, - config: &BTreeMap, - message_name: &str, - message_part: &str, - file_descriptor: &FileDescriptorProto, - all_descriptors: &HashMap, - metadata: Option<&MessageMetadata>, + message_descriptor: &DescriptorProto, + config: &BTreeMap, + message_name: &str, + message_part: &str, + file_descriptor: &FileDescriptorProto, + all_descriptors: &HashMap, + metadata: Option<&MessageMetadata> ) -> anyhow::Result { - debug!( - ">> construct_protobuf_interaction_for_message({}, {}, {:?}, {:?}, {:?})", - message_name, - message_part, - file_descriptor.name, - config.keys(), - metadata - ); - debug!("message_descriptor = {:?}", message_descriptor); - - let mut message_builder = - MessageBuilder::new(message_descriptor, message_name, file_descriptor); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; + trace!(">> construct_protobuf_interaction_for_message({}, {}, {:?}, {:?}, {:?})", message_name, + message_part, file_descriptor.name, config.keys(), metadata); + trace!("message_descriptor = {:?}", message_descriptor); + + let mut message_builder = MessageBuilder::new(message_descriptor, message_name, file_descriptor); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + + debug!("Building message {} from Protobuf descriptor", message_name); + let mut path = DocPath::root(); + if !message_part.is_empty() { + path.push_field(message_part); + } - debug!("Building message {} from Protobuf descriptor", message_name); - let mut path = DocPath::root(); - if !message_part.is_empty() { - path.push_field(message_part); + for (key, value) in config { + if !key.starts_with("pact:") { + let field_path = path.join(key); + debug!(?field_path, "Building field for key '{}'", key); + construct_message_field(&mut message_builder, &mut matching_rules, &mut generators, + key, &proto_value_to_json(value), &field_path, all_descriptors)?; } + } - for (key, value) in config { - if !key.starts_with("pact:") { - let field_path = path.join(key); - debug!(?field_path, "Building field for key '{}'", key); - construct_message_field( - &mut message_builder, - &mut matching_rules, - &mut generators, - key, - &proto_value_to_json(value), - &field_path, - all_descriptors, - )?; - } + debug!("Constructing response to return"); + trace!("Final message builder: {:?}", message_builder); + trace!("matching rules: {:?}", matching_rules); + trace!("generators: {:?}", generators); + + let rules = extract_rules(&matching_rules); + let generators = extract_generators(&generators); + + let content_type = format!("application/protobuf;message={}", message_name); + let mut metadata_fields = btreemap! { + "contentType".to_string() => prost_string(&content_type) + }; + if let Some(metadata) = metadata { + for (k, v) in &metadata.values { + metadata_fields.insert(k.clone(), prost_string(v)); } + } - debug!("Constructing response to return"); - trace!("Final message builder: {:?}", message_builder); - trace!("matching rules: {:?}", matching_rules); - trace!("generators: {:?}", generators); - - let rules = extract_rules(&matching_rules); - let generators = extract_generators(&generators); + Ok(InteractionResponse { + contents: Some(Body { + content_type: content_type.clone(), + content: Some(message_builder.encode_message()?.to_vec()), + content_type_hint: ContentTypeHint::Binary as i32, + }), + message_metadata: Some(Struct { + fields: metadata_fields + }), + rules, + generators, + interaction_markup: message_builder.generate_markup("")?, + interaction_markup_type: MarkupType::CommonMark as i32, + part_name: message_part.to_string(), + metadata_rules: metadata.map(|md| extract_rules(&md.matching_rules)).unwrap_or_default(), + metadata_generators: metadata.map(|md| extract_generators(&md.generators)).unwrap_or_default(), + .. InteractionResponse::default() + }) +} - let content_type = format!("application/protobuf;message={}", message_name); - let mut metadata_fields = btreemap! { - "contentType".to_string() => prost_string(&content_type) +fn extract_generators(generators: &HashMap) -> HashMap { + generators.iter().map(|(path, generator)| { + let gen_values = generator.values(); + let values = if gen_values.is_empty() { + None + } else { + Some(to_proto_struct(&gen_values.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())) }; - if let Some(metadata) = metadata { - for (k, v) in &metadata.values { - metadata_fields.insert(k.clone(), prost_string(v)); - } - } - - Ok(InteractionResponse { - contents: Some(Body { - content_type: content_type.clone(), - content: Some(message_builder.encode_message()?.to_vec()), - content_type_hint: ContentTypeHint::Binary as i32, - }), - message_metadata: Some(Struct { - fields: metadata_fields, - }), - rules, - generators, - interaction_markup: message_builder.generate_markup("")?, - interaction_markup_type: MarkupType::CommonMark as i32, - part_name: message_part.to_string(), - metadata_rules: metadata - .map(|md| extract_rules(&md.matching_rules)) - .unwrap_or_default(), - metadata_generators: metadata - .map(|md| extract_generators(&md.generators)) - .unwrap_or_default(), - ..InteractionResponse::default() + (path.to_string(), pact_plugin_driver::proto::Generator { + r#type: generator.name(), + values }) -} - -fn extract_generators( - generators: &HashMap, -) -> HashMap { - generators - .iter() - .map(|(path, generator)| { - let gen_values = generator.values(); - let values = if gen_values.is_empty() { - None - } else { - Some(to_proto_struct( - &gen_values - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect(), - )) - }; - ( - path.to_string(), - pact_plugin_driver::proto::Generator { - r#type: generator.name(), - values, - }, - ) - }) - .collect() + }).collect() } fn extract_rules(matching_rules: &MatchingRuleCategory) -> HashMap { - matching_rules - .rules - .iter() - .map(|(path, rule_list)| { - ( - path.to_string(), - MatchingRules { - rule: rule_list - .rules - .iter() - .map(|rule| { - let rule_values = rule.values(); - let values = if rule_values.is_empty() { - None - } else { - Some(to_proto_struct( - &rule_values - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect(), - )) - }; - MatchingRule { - r#type: rule.name(), - values, - } - }) - .collect(), - }, - ) - }) - .collect() + matching_rules.rules.iter().map(|(path, rule_list)| { + (path.to_string(), MatchingRules { + rule: rule_list.rules.iter().map(|rule| { + let rule_values = rule.values(); + let values = if rule_values.is_empty() { + None + } else { + Some(to_proto_struct(&rule_values.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())) + }; + MatchingRule { + r#type: rule.name(), + values + } + }).collect() + }) + }).collect() } /// Construct a single field for a message from the provided config @@ -584,85 +422,47 @@ fn extract_rules(matching_rules: &MatchingRuleCategory) -> HashMap, - field_name: &str, - value: &Value, - path: &DocPath, - all_descriptors: &HashMap, + message_builder: &mut MessageBuilder, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + field_name: &str, + value: &Value, + path: &DocPath, + all_descriptors: &HashMap ) -> anyhow::Result<()> { - debug!( - ">> construct_message_field({}, {:?}, {:?})", - field_name, value, path - ); - if !field_name.starts_with("pact:") { - if let Some(field) = message_builder.field_by_name(field_name) { - debug!(?field_name, descriptor = ?field, "Found a descriptor for field"); - match field.r#type { - Some(r#type) => { - if r#type == Type::Message as i32 { - // Embedded message - debug!(?field_name, "Field is for an embedded message"); - build_embedded_message_field_value( - message_builder, - path, - &field, - field_name, - value, - matching_rules, - generators, - all_descriptors, - )?; - } else { - // Non-embedded message field (singular value) - debug!(?field_name, "Field is not an embedded message"); - let field_type = if is_repeated_field(&field) { - MessageFieldValueType::Repeated - } else { - MessageFieldValueType::Normal - }; - build_field_value( - path, - message_builder, - field_type, - &field, - field_name, - value, - matching_rules, - generators, - all_descriptors, - )?; - } - } - None => { - return Err(anyhow!( - "Message {} field '{}' is of an unknown type", - message_builder.message_name, - field_name - )) - } - } + if !field_name.starts_with("pact:") { + if let Some(field) = message_builder.field_by_name(field_name) { + trace!(?field_name, descriptor = ?field, "Found a descriptor for field"); + match field.r#type { + Some(r#type) => if r#type == Type::Message as i32 { + // Embedded message + trace!(?field_name, "Field is for an embedded message"); + build_embedded_message_field_value(message_builder, path, &field, field_name, + value, matching_rules, generators, all_descriptors)?; } else { - error!( - "Field '{}' was not found in message '{}'", - field_name, message_builder.message_name - ); - let fields: HashSet = message_builder - .descriptor - .field - .iter() - .map(|field| field.name.clone().unwrap_or_default()) - .collect(); - return Err(anyhow!( - "Message {} has no field '{}'. Fields are {:?}", - message_builder.message_name, - field_name, - fields - )); + // Non-embedded message field (singular value) + trace!(?field_name, "Field is not an embedded message"); + let field_type = if is_repeated_field(&field) { + MessageFieldValueType::Repeated + } else { + MessageFieldValueType::Normal + }; + build_field_value(path, message_builder, field_type, &field, field_name, value, + matching_rules, generators, all_descriptors)?; } + None => { + return Err(anyhow!("Message {} field '{}' is of an unknown type", message_builder.message_name, field_name)) + } + } + } else { + error!("Field '{}' was not found in message '{}'", field_name, message_builder.message_name); + let fields: HashSet = message_builder.descriptor.field.iter() + .map(|field| field.name.clone().unwrap_or_default()) + .collect(); + return Err(anyhow!("Message {} has no field '{}'. Fields are {:?}", message_builder.message_name, field_name, fields)) } - Ok(()) + } + Ok(()) } /// Constructs the field value for a field in a message. @@ -671,93 +471,63 @@ fn construct_message_field( fields(%path, ?field, %value) )] fn build_embedded_message_field_value( - message_builder: &mut MessageBuilder, - path: &DocPath, - field_descriptor: &FieldDescriptorProto, - field: &str, - value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, - all_descriptors: &HashMap, + message_builder: &mut MessageBuilder, + path: &DocPath, + field_descriptor: &FieldDescriptorProto, + field: &str, + value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + all_descriptors: &HashMap ) -> anyhow::Result<()> { - debug!( - ">> build_embedded_message_field_value({}, {:?}, {:?})", - field, value, path - ); - if is_repeated_field(field_descriptor) - && !is_map_field(&message_builder.descriptor, field_descriptor) - { - debug!("{} is a repeated field", field); - - match value { - Value::Array(list) => { - // We have been provided an array of values, so we use the first one to build the type - // information, and then just process the remaining values as additional array items - if let Some((first, rest)) = list.split_first() { - let index_path = path.join("0"); - build_single_embedded_field_value( - &index_path, - message_builder, - MessageFieldValueType::Repeated, - field_descriptor, - field, - first, - matching_rules, - generators, - all_descriptors, - )?; - let mut builder = message_builder.clone(); - for (index, item) in rest.iter().enumerate() { - let index_path = path.join((index + 1).to_string()); - let constructed = build_single_embedded_field_value( - &index_path, - &mut builder, - MessageFieldValueType::Repeated, - field_descriptor, - field, - item, - matching_rules, - generators, - all_descriptors, - )?; - if let Some(constructed) = constructed { - message_builder.add_repeated_field_value( - field_descriptor, - field, - constructed, - ); - } - } - } - Ok(()) + if is_repeated_field(field_descriptor) && !is_map_field(&message_builder.descriptor, field_descriptor) { + debug!("{} is a repeated field", field); + + match value { + Value::Array(list) => { + // We have been provided an array of values, so we use the first one to build the type + // information, and then just process the remaining values as additional array items + if let Some((first, rest)) = list.split_first() { + let index_path = path.join("0"); + build_single_embedded_field_value( + &index_path, message_builder, MessageFieldValueType::Repeated, field_descriptor, + field, first, matching_rules, generators, all_descriptors)?; + let mut builder = message_builder.clone(); + for (index, item) in rest.iter().enumerate() { + let index_path = path.join((index + 1).to_string()); + let constructed = build_single_embedded_field_value( + &index_path, &mut builder, MessageFieldValueType::Repeated, + field_descriptor, field, item, matching_rules, generators, all_descriptors + )?; + if let Some(constructed) = constructed { + message_builder.add_repeated_field_value(field_descriptor, field, constructed); + } + } + } + Ok(()) + } + Value::Object(map) => { + if let Some(definition) = map.get("pact:match") { + // We have received a map to configure the repeated field with a match value, so we + // process the rest of the map as a single example value applied against the pact:match + // expression. Normally it should be a matchValues or matchKeys (or both) + let definition = json_to_string(definition); + debug!("Configuring repeated field from a matcher definition expression '{}'", definition); + let mrd = parse_matcher_def( definition.as_str())?; + + let each_value = mrd.rules.iter() + .filter_map(|rule| rule.clone().left()) + .find_map(|rule| match rule { + matchingrules::MatchingRule::EachValue(def) => Some(def), + _ => None + }); + if let Some(each_value_def) = &each_value { + debug!("Found each value matcher"); + if mrd.rules.len() > 1 { + warn!("{}: each value matcher can not be combined with other matchers, ignoring the other matching rules", path); } - Value::Object(map) => { - if let Some(definition) = map.get("pact:match") { - // We have received a map to configure the repeated field with a match value, so we - // process the rest of the map as a single example value applied against the pact:match - // expression. Normally it should be a matchValues or matchKeys (or both) - let definition = json_to_string(definition); - debug!( - "Configuring repeated field from a matcher definition expression '{}'", - definition - ); - let mrd = parse_matcher_def(definition.as_str())?; - - let each_value = mrd - .rules - .iter() - .filter_map(|rule| rule.clone().left()) - .find_map(|rule| match rule { - matchingrules::MatchingRule::EachValue(def) => Some(def), - _ => None, - }); - if let Some(each_value_def) = &each_value { - debug!("Found each value matcher"); - if mrd.rules.len() > 1 { - warn!("{}: each value matcher can not be combined with other matchers, ignoring the other matching rules", path); - } - - match each_value_def.rules.first() { + + match each_value_def.rules.first() { Some(either) => match either { Either::Left(_) => { matching_rules.add_rule(path.clone(), matchingrules::MatchingRule::EachValue(each_value_def.clone()), RuleLogic::And); @@ -782,84 +552,48 @@ fn build_embedded_message_field_value( } None => Err(anyhow!("Got an EachValue matcher with no associated matching rules to apply")) } - } else { - if !mrd.rules.is_empty() { - for rule in &mrd.rules { - match rule { + } else { + if !mrd.rules.is_empty() { + for rule in &mrd.rules { + match rule { Either::Left(rule) => matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And), Either::Right(mr) => return Err(anyhow!("References can only be used with an EachValue matcher - {:?}", mr)) } - } - } - if let Some(generator) = mrd.generator { - generators.insert(path.to_string(), generator); - } - - let constructed = value_for_type( - field, - mrd.value.as_str(), - field_descriptor, - &message_builder.descriptor, - all_descriptors, - )?; - message_builder.add_repeated_field_value( - field_descriptor, - field, - constructed, - ); - - Ok(()) - } - } else { - // No matching definition, so we have to assume the map contains the attributes of a - // single example. - trace!("No matching definition, assuming config contains the attributes of a single example"); - build_single_embedded_field_value( - &path.join("*"), - message_builder, - MessageFieldValueType::Repeated, - field_descriptor, - field, - value, - matching_rules, - generators, - all_descriptors, - ) - .map(|_| ()) - } + } } - _ => { - // Not a map or list structure, so could be a primitive repeated field - trace!("Not a map or list structure, assuming a single field"); - build_single_embedded_field_value( - path, - message_builder, - MessageFieldValueType::Repeated, - field_descriptor, - field, - value, - matching_rules, - generators, - all_descriptors, - ) - .map(|_| ()) + if let Some(generator) = mrd.generator { + generators.insert(path.to_string(), generator); } + + let constructed = value_for_type(field, mrd.value.as_str(), + field_descriptor, &message_builder.descriptor, all_descriptors)?; + message_builder.add_repeated_field_value(field_descriptor, field, constructed); + + Ok(()) + } + } else { + // No matching definition, so we have to assume the map contains the attributes of a + // single example. + trace!("No matching definition, assuming config contains the attributes of a single example"); + build_single_embedded_field_value(&path.join("*"), message_builder, MessageFieldValueType::Repeated, field_descriptor, field, value, + matching_rules, generators, all_descriptors) + .map(|_| ()) } - } else { - trace!("processing a standard field"); - build_single_embedded_field_value( - path, - message_builder, - MessageFieldValueType::Normal, - field_descriptor, - field, - value, - matching_rules, - generators, - all_descriptors, - ) - .map(|_| ()) + } + _ => { + // Not a map or list structure, so could be a primitive repeated field + trace!("Not a map or list structure, assuming a single field"); + build_single_embedded_field_value(path, message_builder, MessageFieldValueType::Repeated, field_descriptor, field, value, + matching_rules, generators, all_descriptors) + .map(|_| ()) + } } + } else { + trace!("processing a standard field"); + build_single_embedded_field_value(path, message_builder, MessageFieldValueType::Normal, field_descriptor, field, value, + matching_rules, generators, all_descriptors) + .map(|_| ()) + } } /// Construct a non-repeated embedded message field @@ -868,518 +602,309 @@ fn build_embedded_message_field_value( fields(%path, ?field_type, %field, %value) )] fn build_single_embedded_field_value( - path: &DocPath, - message_builder: &mut MessageBuilder, - field_type: MessageFieldValueType, - field_descriptor: &FieldDescriptorProto, - field: &str, - value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, - all_descriptors: &HashMap, + path: &DocPath, + message_builder: &mut MessageBuilder, + field_type: MessageFieldValueType, + field_descriptor: &FieldDescriptorProto, + field: &str, + value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + all_descriptors: &HashMap ) -> anyhow::Result> { - debug!( - "Configuring message field '{}' (type {:?})", - field, field_descriptor.type_name - ); - let type_name = field_descriptor.type_name.clone().unwrap_or_default(); - match type_name.as_str() { - ".google.protobuf.BytesValue" => { - debug!("Field is a Protobuf BytesValue"); - if let Value::String(_) = value { - build_field_value( - path, - message_builder, - field_type, - field_descriptor, - field, - value, - matching_rules, - generators, - all_descriptors, - ) - } else { - Err(anyhow!("Fields of type google.protobuf.BytesValue must be configured with a single string value")) - } - } - ".google.protobuf.Struct" => { - debug!("Field is a Protobuf Struct"); - build_struct_field( - path, - message_builder, - field_type, - field_descriptor, - field, - value, - matching_rules, - generators, - ) - } - _ => { - if is_map_field(&message_builder.descriptor, field_descriptor) { - debug!("Message field '{}' is a Map field", field); - build_map_field( - path, - message_builder, - field_descriptor, - field, - value, - matching_rules, - generators, - all_descriptors, - )?; - Ok(None) - } else if let Value::Object(config) = value { - debug!("Configuring the message from config {:?}", config); - let (message_name, package_name) = split_name(type_name.as_str()); - let embedded_type = find_nested_type(&message_builder.descriptor, field_descriptor) + debug!("Configuring message field '{}' (type {:?})", field, field_descriptor.type_name); + let type_name = field_descriptor.type_name.clone().unwrap_or_default(); + match type_name.as_str() { + ".google.protobuf.BytesValue" => { + debug!("Field is a Protobuf BytesValue"); + if let Value::String(_) = value { + build_field_value(path, message_builder, field_type, field_descriptor, field, + value, matching_rules, generators, all_descriptors) + } else { + Err(anyhow!("Fields of type google.protobuf.BytesValue must be configured with a single string value")) + } + } + ".google.protobuf.Struct" => { + debug!("Field is a Protobuf Struct"); + build_struct_field(path, message_builder, field_type, field_descriptor, field, value, matching_rules, generators) + } + _ => if is_map_field(&message_builder.descriptor, field_descriptor) { + debug!("Message field '{}' is a Map field", field); + build_map_field(path, message_builder, field_descriptor, field, value, matching_rules, generators, all_descriptors)?; + Ok(None) + } else if let Value::Object(config) = value { + debug!("Configuring the message from config {:?}", config); + let (message_name, package_name) = split_name(type_name.as_str()); + let embedded_type = find_nested_type(&message_builder.descriptor, field_descriptor) .or_else(|| find_message_descriptor(message_name, package_name, &message_builder.file_descriptor, all_descriptors).ok()) .ok_or_else(|| anyhow!("Did not find message '{}' in the current message or in the file descriptors", type_name))?; - let mut embedded_builder = MessageBuilder::new( - &embedded_type, - message_name, - &message_builder.file_descriptor, - ); - - let field_value = if let Some(definition) = config.get("pact:match") { - let mut field_value = None; - let mrd = parse_matcher_def(json_to_string(definition).as_str())?; - for rule in &mrd.rules { - match rule { - Either::Left(rule) => { - matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And); - } - Either::Right(reference) => { - if let Some(field_def) = config.get(reference.name.as_str()) { - matching_rules.add_rule( - path.clone(), - matchingrules::MatchingRule::Values, - RuleLogic::And, - ); - let array_path = path.join("*"); - matching_rules.add_rule( - array_path.clone(), - matchingrules::MatchingRule::Type, - RuleLogic::And, - ); - field_value = build_single_embedded_field_value( - &array_path, - message_builder, - MessageFieldValueType::Normal, - field_descriptor, - field, - field_def, - matching_rules, - generators, - all_descriptors, - )?; - } else { - return Err(anyhow!( - "Expression '{}' refers to non-existent item '{}'", - definition, - reference.name - )); - } - } - } - } - if let Some(field_value) = field_value { - field_value - } else { - for (key, value) in config { - if !key.starts_with("pact:") { - let field_path = path.join(key); - construct_message_field( - &mut embedded_builder, - matching_rules, - generators, - key, - value, - &field_path, - all_descriptors, - )?; - } - } - MessageFieldValue { - name: field.to_string(), - raw_value: None, - rtype: RType::Message(Box::new(embedded_builder)), - } - } - } else { - for (key, value) in config { - let field_path = path.join(key); - construct_message_field( - &mut embedded_builder, - matching_rules, - generators, - key, - value, - &field_path, - all_descriptors, - )?; - } - MessageFieldValue { - name: field.to_string(), - raw_value: None, - rtype: RType::Message(Box::new(embedded_builder)), - } - }; - - message_builder.set_field_value(field_descriptor, field, field_value.clone()); - Ok(Some(field_value)) + let mut embedded_builder = MessageBuilder::new(&embedded_type, message_name, &message_builder.file_descriptor); + + let field_value = if let Some(definition) = config.get("pact:match") { + let mut field_value = None; + let mrd = parse_matcher_def(json_to_string(definition).as_str())?; + for rule in &mrd.rules { + match rule { + Either::Left(rule) => { + matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And); + }, + Either::Right(reference) => if let Some(field_def) = config.get(reference.name.as_str()) { + matching_rules.add_rule(path.clone(), matchingrules::MatchingRule::Values, RuleLogic::And); + let array_path = path.join("*"); + matching_rules.add_rule(array_path.clone(), matchingrules::MatchingRule::Type, RuleLogic::And); + field_value = build_single_embedded_field_value(&array_path, message_builder, MessageFieldValueType::Normal, + field_descriptor, field, field_def, matching_rules, generators, all_descriptors)?; } else { - Err(anyhow!( - "For message fields, you need to define a Map of expected fields, got {:?}", - value - )) + return Err(anyhow!("Expression '{}' refers to non-existent item '{}'", definition, reference.name)); + } + } + } + if let Some(field_value) = field_value { + field_value + } else { + for (key, value) in config { + if !key.starts_with("pact:") { + let field_path = path.join(key); + construct_message_field(&mut embedded_builder, matching_rules, generators, + key, value, &field_path, all_descriptors)?; } + } + MessageFieldValue { + name: field.to_string(), + raw_value: None, + rtype: RType::Message(Box::new(embedded_builder)) + } + } + } else { + for (key, value) in config { + let field_path = path.join(key); + construct_message_field(&mut embedded_builder, matching_rules, generators, key, value, &field_path, all_descriptors)?; + } + MessageFieldValue { + name: field.to_string(), + raw_value: None, + rtype: RType::Message(Box::new(embedded_builder)) } + }; + + message_builder.set_field_value(field_descriptor, field, field_value.clone()); + Ok(Some(field_value)) + } else { + Err(anyhow!("For message fields, you need to define a Map of expected fields, got {:?}", value)) } + } } /// Create a field value of type google.protobuf.Struct fn build_struct_field( - path: &DocPath, - message_builder: &mut MessageBuilder, - field_type: MessageFieldValueType, - field_descriptor: &FieldDescriptorProto, - field_name: &str, - field_value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, + path: &DocPath, + message_builder: &mut MessageBuilder, + field_type: MessageFieldValueType, + field_descriptor: &FieldDescriptorProto, + field_name: &str, + field_value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap ) -> anyhow::Result> { - trace!( - ">> build_struct_field('{}', {}, {:?}, {:?}, {:?}, {:?})", - path, - field_name, - field_value, - message_builder, - matching_rules, - generators - ); - - match field_value { - Value::Object(map) => { - if let Some(matching_def) = map.get("pact:match") { - // if (fieldsMap.containsKey("pact:match")) { - // val expression = fieldsMap["pact:match"]!!.stringValue - // when (val ruleDefinition = MatchingRuleDefinition.parseMatchingRuleDefinition(expression)) { - // is Ok -> TODO() - // is Err -> { - // logger.error { "'$expression' is not a valid matching rule definition - ${ruleDefinition.error}" } - // throw RuntimeException("'$expression' is not a valid matching rule definition - ${ruleDefinition.error}") - // } - // } - // } - todo!() - } else { - let mut fields = btreemap! {}; - for (key, value) in map { - let field_path = path.join(key); - let proto_value = - build_proto_value(&field_path, value, matching_rules, generators)?; - fields.insert(key.clone(), proto_value); - } + trace!(">> build_struct_field('{}', {}, {:?}, {:?}, {:?}, {:?})", path, field_name, field_value, + message_builder, matching_rules, generators); + + match field_value { + Value::Object(map) => if let Some(matching_def) = map.get("pact:match") { + // if (fieldsMap.containsKey("pact:match")) { + // val expression = fieldsMap["pact:match"]!!.stringValue + // when (val ruleDefinition = MatchingRuleDefinition.parseMatchingRuleDefinition(expression)) { + // is Ok -> TODO() + // is Err -> { + // logger.error { "'$expression' is not a valid matching rule definition - ${ruleDefinition.error}" } + // throw RuntimeException("'$expression' is not a valid matching rule definition - ${ruleDefinition.error}") + // } + // } + // } + todo!() + } else { + let mut fields = btreemap!{}; + for (key, value) in map { + let field_path = path.join(key); + let proto_value = build_proto_value(&field_path, value, matching_rules, generators)?; + fields.insert(key.clone(), proto_value); + } - let s = Struct { fields }; - let message_field_value = MessageFieldValue { - name: field_name.to_string(), - raw_value: None, - rtype: RType::Struct(s), - }; - match field_type { - MessageFieldValueType::Repeated => message_builder.add_repeated_field_value( - field_descriptor, - field_name, - message_field_value.clone(), - ), - _ => message_builder.set_field_value( - field_descriptor, - field_name, - message_field_value.clone(), - ), - }; - Ok(Some(message_field_value)) - } - } - _ => Err(anyhow!( - "google.protobuf.Struct fields need to be configured with a Map, got {:?}", - field_value - )), + let s = Struct { fields }; + let message_field_value = MessageFieldValue { + name: field_name.to_string(), + raw_value: None, + rtype: RType::Struct(s) + }; + match field_type { + MessageFieldValueType::Repeated => message_builder.add_repeated_field_value(field_descriptor, field_name, message_field_value.clone()), + _ => message_builder.set_field_value(field_descriptor, field_name, message_field_value.clone()) + }; + Ok(Some(message_field_value)) } + _ => Err(anyhow!("google.protobuf.Struct fields need to be configured with a Map, got {:?}", field_value)) + } } fn build_proto_value( - path: &DocPath, - value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, + path: &DocPath, + value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap ) -> anyhow::Result { - trace!( - ">> build_proto_value('{}', {:?}, {:?}, {:?})", - path, - value, - matching_rules, - generators - ); - match value { - Value::Null => Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NullValue(0)), - }), - Value::Bool(b) => Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::BoolValue(*b)), - }), - Value::Number(n) => { - if let Some(f) = n.as_f64() { - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NumberValue(f)), - }) - } else if let Some(f) = n.as_u64() { - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NumberValue(f as f64)), - }) - } else if let Some(f) = n.as_i64() { - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NumberValue(f as f64)), - }) - } else { - Err(anyhow!("Got an invalid number (not f64, i64 or u64)")) - } + trace!(">> build_proto_value('{}', {:?}, {:?}, {:?})", path, value, matching_rules, generators); + match value { + Value::Null => Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NullValue(0)) }), + Value::Bool(b) => Ok(prost_types::Value { kind: Some(prost_types::value::Kind::BoolValue(*b)) }), + Value::Number(n) => if let Some(f) = n.as_f64() { + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(f)) }) + } else if let Some(f) = n.as_u64() { + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(f as f64)) }) + } else if let Some(f) = n.as_i64() { + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(f as f64)) }) + } else { + Err(anyhow!("Got an invalid number (not f64, i64 or u64)")) + }, + Value::String(s) => if is_matcher_def(s.as_str()) { + let mrd = parse_matcher_def(s.as_str())?; + if !mrd.rules.is_empty() { + for rule in &mrd.rules { + match rule { + Either::Left(rule) => matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And), + Either::Right(mr) => return Err(anyhow!("Was expecting a value, but got a matching reference {:?}", mr)) + } } - Value::String(s) => { - if is_matcher_def(s.as_str()) { - let mrd = parse_matcher_def(s.as_str())?; - if !mrd.rules.is_empty() { - for rule in &mrd.rules { - match rule { - Either::Left(rule) => { - matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And) - } - Either::Right(mr) => { - return Err(anyhow!( - "Was expecting a value, but got a matching reference {:?}", - mr - )) - } - } - } - } - if let Some(generator) = mrd.generator { - generators.insert(path.to_string(), generator); - } + } + if let Some(generator) = mrd.generator { + generators.insert(path.to_string(), generator); + } - match mrd.value_type { - ValueType::Unknown | ValueType::String => Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::StringValue(mrd.value.clone())), - }), - ValueType::Number | ValueType::Decimal => { - let num: f64 = mrd.value.parse()?; - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NumberValue(num)), - }) - } - ValueType::Integer => { - let num: i64 = mrd.value.parse()?; - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::NumberValue(num as f64)), - }) - } - ValueType::Boolean => { - let b: bool = mrd.value.parse()?; - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::BoolValue(b)), - }) - } - } - } else { - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::StringValue(s.clone())), - }) - } + match mrd.value_type { + ValueType::Unknown | ValueType::String => Ok(prost_types::Value { kind: Some(prost_types::value::Kind::StringValue(mrd.value.clone())) }), + ValueType::Number | ValueType::Decimal => { + let num: f64 = mrd.value.parse()?; + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(num)) }) } - Value::Array(a) => { - let mut values = a.iter().enumerate().map(|(index, v)| { - let index_path = path.join(index.to_string()); - build_proto_value(&index_path, v, matching_rules, generators) - }); - if let Some(err) = values.find_map(|v| v.err()) { - return Err(anyhow!( - "Could not construct a Protobuf list value - {}", - err - )); - } - // Unwrap here is safe as the previous statement would catch an error - let list = prost_types::ListValue { - values: values.map(|v| v.unwrap()).collect(), - }; - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::ListValue(list)), - }) + ValueType::Integer => { + let num: i64 = mrd.value.parse()?; + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(num as f64)) }) } - Value::Object(map) => { - let mut fields = btreemap! {}; - for (key, value) in map { - let field_path = path.join(key); - let proto_value = - build_proto_value(&field_path, value, matching_rules, generators)?; - fields.insert(key.clone(), proto_value); - } - - let s = prost_types::Struct { fields }; - Ok(prost_types::Value { - kind: Some(prost_types::value::Kind::StructValue(s)), - }) + ValueType::Boolean => { + let b: bool = mrd.value.parse()?; + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::BoolValue(b)) }) } + } + } else { + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::StringValue(s.clone())) }) + } + Value::Array(a) => { + let mut values = a.iter().enumerate().map(|(index, v)| { + let index_path = path.join(index.to_string()); + build_proto_value(&index_path, v, matching_rules, generators) + }); + if let Some(err) = values.find_map(|v| v.err()) { + return Err(anyhow!("Could not construct a Protobuf list value - {}", err)) + } + // Unwrap here is safe as the previous statement would catch an error + let list = prost_types::ListValue { values: values.map(|v| v.unwrap()).collect() }; + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::ListValue(list)) }) + } + Value::Object(map) => { + let mut fields = btreemap!{}; + for (key, value) in map { + let field_path = path.join(key); + let proto_value = build_proto_value(&field_path, value, matching_rules, generators)?; + fields.insert(key.clone(), proto_value); + } + + let s = prost_types::Struct { fields }; + Ok(prost_types::Value { kind: Some(prost_types::value::Kind::StructValue(s)) }) } + } } /// Constructs a message map field. Map fields are repeated fields with an embedded entry message /// type which has a key and value field. fn build_map_field( - path: &DocPath, - message_builder: &mut MessageBuilder, - field_descriptor: &FieldDescriptorProto, - field: &str, - value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, - all_descriptors: &HashMap, + path: &DocPath, + message_builder: &mut MessageBuilder, + field_descriptor: &FieldDescriptorProto, + field: &str, + value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + all_descriptors: &HashMap ) -> anyhow::Result<()> { - trace!( - ">> build_map_field('{}', {}, {:?}, {:?})", - path, - field, - value, - message_builder - ); - let field_type = field_descriptor.type_name.clone().unwrap_or_default(); - trace!("build_map_field: field_type = {}", field_type); - - if let Value::Object(config) = value { - if let Some(definition) = config.get("pact:match") { - debug!("Parsing matching rule definition {:?}", definition); - let definition = json_to_string(definition); - let mrd = parse_matcher_def(definition.as_str())?; - if !mrd.rules.is_empty() { - trace!("Found matching rules: {:?}", mrd.rules); - for rule in &mrd.rules { - match rule { - Either::Left(rule) => { - matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And) - } - Either::Right(mr) => todo!(), - } - } - } - if let Some(generator) = mrd.generator { - generators.insert(path.to_string(), generator); - } + trace!(">> build_map_field('{}', {}, {:?}, {:?})", path, field, value, message_builder); + let field_type = field_descriptor.type_name.clone().unwrap_or_default(); + trace!("build_map_field: field_type = {}", field_type); + + if let Value::Object(config) = value { + if let Some(definition) = config.get("pact:match") { + debug!("Parsing matching rule definition {:?}", definition); + let definition = json_to_string(definition); + let mrd = parse_matcher_def(definition.as_str())?; + if !mrd.rules.is_empty() { + trace!("Found matching rules: {:?}", mrd.rules); + for rule in &mrd.rules { + match rule { + Either::Left(rule) => { + matching_rules.add_rule(path.clone(), rule.clone(), RuleLogic::And) + }, + Either::Right(mr) => todo!() + } } + } + if let Some(generator) = mrd.generator { + generators.insert(path.to_string(), generator); + } + } - if let Some(map_type) = find_nested_type(&message_builder.descriptor, field_descriptor) { - let message_name = map_type.name.clone().unwrap_or_default(); - let key_descriptor = map_type - .field - .iter() - .find(|f| f.name.clone().unwrap_or_default() == "key") - .ok_or_else(|| { - anyhow!("Did not find the key field in the descriptor for the map field") - })?; - let value_descriptor = map_type - .field - .iter() - .find(|f| f.name.clone().unwrap_or_default() == "value") - .ok_or_else(|| { - anyhow!("Did not find the value field in the descriptor for the map field") - })?; - trace!("Map field key descriptor = {:?}", key_descriptor); - trace!("Map field value descriptor = {:?}", value_descriptor); - - let mut embedded_builder = MessageBuilder::new( - &map_type, - message_name.as_str(), - &message_builder.file_descriptor, - ); - for (inner_field, value) in config { - if inner_field != "pact:match" { - let entry_path = path.join(inner_field); - - let key_value = build_field_value( - &entry_path, - &mut embedded_builder, - MessageFieldValueType::Normal, - key_descriptor, - "key", - &Value::String(inner_field.clone()), - matching_rules, - generators, - all_descriptors, - )? - .ok_or_else(|| { - anyhow!( - "Was not able to construct map key value {:?}", - key_descriptor.type_name - ) - })?; - - let value_value = if value_descriptor.r#type() == Type::Message { - // Embedded message - trace!("Value is an embedded message type"); - build_single_embedded_field_value( - &entry_path, - &mut embedded_builder, - MessageFieldValueType::Normal, - value_descriptor, - "value", - value, - matching_rules, - generators, - all_descriptors, - )? - } else { - // Non-embedded message field (singular value) - trace!("Value is not an embedded message"); - build_field_value( - &entry_path, - &mut embedded_builder, - MessageFieldValueType::Normal, - value_descriptor, - "value", - value, - matching_rules, - generators, - all_descriptors, - )? - } - .ok_or_else(|| { - anyhow!( - "Was not able to construct map value value {:?}", - value_descriptor.type_name - ) - })?; - - message_builder.add_map_field_value( - field_descriptor, - field, - key_value, - value_value, - ); - } - } - Ok(()) - } else { - Err(anyhow!( - "Did not find the nested map type {:?} in the message descriptor nested types", - field_descriptor.type_name - )) + if let Some(map_type) = find_nested_type(&message_builder.descriptor, field_descriptor) { + let message_name = map_type.name.clone().unwrap_or_default(); + let key_descriptor = map_type.field.iter() + .find(|f| f.name.clone().unwrap_or_default() == "key") + .ok_or_else(|| anyhow!("Did not find the key field in the descriptor for the map field"))?; + let value_descriptor = map_type.field.iter() + .find(|f| f.name.clone().unwrap_or_default() == "value") + .ok_or_else(|| anyhow!("Did not find the value field in the descriptor for the map field"))?; + trace!("Map field key descriptor = {:?}", key_descriptor); + trace!("Map field value descriptor = {:?}", value_descriptor); + + let mut embedded_builder = MessageBuilder::new(&map_type, message_name.as_str(), &message_builder.file_descriptor); + for (inner_field, value) in config { + if inner_field != "pact:match" { + let entry_path = path.join(inner_field); + + let key_value = build_field_value(&entry_path, &mut embedded_builder, MessageFieldValueType::Normal, + key_descriptor, "key", &Value::String(inner_field.clone()), + matching_rules, generators, all_descriptors + )? + .ok_or_else(|| anyhow!("Was not able to construct map key value {:?}", key_descriptor.type_name))?; + + let value_value = if value_descriptor.r#type() == Type::Message { + // Embedded message + trace!("Value is an embedded message type"); + build_single_embedded_field_value(&entry_path, &mut embedded_builder, MessageFieldValueType::Normal, + value_descriptor, "value", value, matching_rules, generators, all_descriptors)? + } else { + // Non-embedded message field (singular value) + trace!("Value is not an embedded message"); + build_field_value(&entry_path, &mut embedded_builder, MessageFieldValueType::Normal, + value_descriptor, "value", value, matching_rules, generators, all_descriptors)? + } + .ok_or_else(|| anyhow!("Was not able to construct map value value {:?}", value_descriptor.type_name))?; + + message_builder.add_map_field_value(field_descriptor, field, key_value, value_value); } + } + Ok(()) } else { - Err(anyhow!( - "Map fields need to be configured with a Map, got {:?}", - value - )) + Err(anyhow!("Did not find the nested map type {:?} in the message descriptor nested types", field_descriptor.type_name)) } + } else { + Err(anyhow!("Map fields need to be configured with a Map, got {:?}", value)) + } } /// Constructs a simple message field (non-repeated or map) from the configuration value and @@ -1389,177 +914,95 @@ fn build_map_field( fields(%path, ?field_type, %field_name, %value) )] fn build_field_value( - path: &DocPath, - message_builder: &mut MessageBuilder, - field_type: MessageFieldValueType, - descriptor: &FieldDescriptorProto, - field_name: &str, - value: &Value, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, - all_descriptors: &HashMap, + path: &DocPath, + message_builder: &mut MessageBuilder, + field_type: MessageFieldValueType, + descriptor: &FieldDescriptorProto, + field_name: &str, + value: &Value, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + all_descriptors: &HashMap ) -> anyhow::Result> { - debug!( - ">> build_field_value({}, {}, {:?})", - path, field_name, value - ); + trace!(">> build_field_value({}, {}, {:?})", path, field_name, value); - match value { - Value::Null => Ok(None), - Value::String(s) => { - let constructed_value = match field_type { - MessageFieldValueType::Repeated => { - let path = path.join("*"); - let constructed_value = construct_value_from_string( - &path, - message_builder, - descriptor, - field_name, - matching_rules, - generators, - s, - all_descriptors, - )?; - debug!( - "Setting field {:?}:repeated to value {:?}", - field_name, constructed_value - ); - message_builder.add_repeated_field_value( - descriptor, - field_name, - constructed_value.clone(), - ); - constructed_value - } - _ => { - let constructed_value = construct_value_from_string( - path, - message_builder, - descriptor, - field_name, - matching_rules, - generators, - s, - all_descriptors, - )?; - debug!( - "Setting field {:?}:{:?} to value {:?}", - field_name, field_type, constructed_value - ); - message_builder.set_field_value( - descriptor, - field_name, - constructed_value.clone(), - ); - constructed_value - } - }; - Ok(Some(constructed_value)) - } - Value::Array(list) => { - if let Some((first, rest)) = list.split_first() { - let index_path = path.join("0"); - let constructed_value = build_field_value( - &index_path, - message_builder, - MessageFieldValueType::Repeated, - descriptor, - field_name, - first, - matching_rules, - generators, - all_descriptors, - )?; - for (index, value) in rest.iter().enumerate() { - let index_path = path.join((index + 1).to_string()); - build_field_value( - &index_path, - message_builder, - MessageFieldValueType::Repeated, - descriptor, - field_name, - value, - matching_rules, - generators, - all_descriptors, - )?; - } - trace!(?message_builder, "Constructed repeated field from array"); - Ok(constructed_value) - } else { - Ok(None) - } + match value { + Value::Null => Ok(None), + Value::String(s) => { + let constructed_value = match field_type { + MessageFieldValueType::Repeated => { + let path = path.join("*"); + let constructed_value = construct_value_from_string(&path, message_builder, + descriptor, field_name, matching_rules, generators, s, all_descriptors)?; + debug!("Setting field {:?}:repeated to value {:?}", field_name, constructed_value); + message_builder.add_repeated_field_value(descriptor, field_name, constructed_value.clone()); + constructed_value + }, + _ => { + let constructed_value = construct_value_from_string(path, message_builder, + descriptor, field_name, matching_rules, generators, s, all_descriptors)?; + debug!("Setting field {:?}:{:?} to value {:?}", field_name, field_type, constructed_value); + message_builder.set_field_value(descriptor, field_name, constructed_value.clone()); + constructed_value + }, + }; + Ok(Some(constructed_value)) + } + Value::Array(list) => { + if let Some((first, rest)) = list.split_first() { + let index_path = path.join("0"); + let constructed_value = build_field_value(&index_path, message_builder, + MessageFieldValueType::Repeated, descriptor, field_name, first, + matching_rules, generators, all_descriptors + )?; + for (index, value) in rest.iter().enumerate() { + let index_path = path.join((index + 1).to_string()); + build_field_value(&index_path, message_builder, MessageFieldValueType::Repeated, + descriptor, field_name, value, matching_rules, generators, all_descriptors + )?; } - Value::Bool(b) => { - if descriptor.r#type() == Type::Bool { - let constructed_value = MessageFieldValue { - name: field_name.to_string(), - raw_value: Some(b.to_string()), - rtype: RType::Boolean(*b), - }; - update_message_builder( - message_builder, - field_type, - descriptor, - field_name, - &constructed_value, - ); - Ok(Some(constructed_value)) - } else { - Err(anyhow!("Only boolean field values can be configured with a boolean value, field {} type is {:?}", + trace!(?message_builder, "Constructed repeated field from array"); + Ok(constructed_value) + } else { + Ok(None) + } + } + Value::Bool(b) => if descriptor.r#type() == Type::Bool { + let constructed_value = MessageFieldValue { + name: field_name.to_string(), + raw_value: Some(b.to_string()), + rtype: RType::Boolean(*b) + }; + update_message_builder(message_builder, field_type, descriptor, field_name, &constructed_value); + Ok(Some(constructed_value)) + } else { + Err(anyhow!("Only boolean field values can be configured with a boolean value, field {} type is {:?}", field_name, descriptor.r#type())) - } - } - Value::Number(n) => { - if n.is_u64() { - let f = n.as_u64().unwrap_or_default(); - construct_numeric_value( - message_builder, - field_type, - descriptor, - field_name, - value, - f, - ) - } else if n.is_i64() { - let f = n.as_i64().unwrap_or_default(); - construct_numeric_value( - message_builder, - field_type, - descriptor, - field_name, - value, - f, - ) - } else { - let f = n.as_f64().unwrap_or_default(); - construct_numeric_value( - message_builder, - field_type, - descriptor, - field_name, - value, - f, - ) - } - } - _ => Err(anyhow!( - "Field values must be configured with a string value, got {:?}", - value - )), } + Value::Number(n) => if n.is_u64() { + let f = n.as_u64().unwrap_or_default(); + construct_numeric_value(message_builder, field_type, descriptor, field_name, value, f) + } else if n.is_i64() { + let f = n.as_i64().unwrap_or_default(); + construct_numeric_value(message_builder, field_type, descriptor, field_name, value, f) + } else { + let f = n.as_f64().unwrap_or_default(); + construct_numeric_value(message_builder, field_type, descriptor, field_name, value, f) + } + _ => Err(anyhow!("Field values must be configured with a string value, got {:?}", value)) + } } fn construct_numeric_value( - message_builder: &mut MessageBuilder, - field_type: MessageFieldValueType, - descriptor: &FieldDescriptorProto, - field_name: &str, - value: &Value, - f: N, + message_builder: &mut MessageBuilder, + field_type: MessageFieldValueType, + descriptor: &FieldDescriptorProto, + field_name: &str, + value: &Value, + f: N ) -> anyhow::Result> { - match descriptor.r#type() { + match descriptor.r#type() { Type::Double => if let Some(f) = f.to_f64() { let constructed_value = MessageFieldValue { name: field_name.to_string(), @@ -1640,1064 +1083,985 @@ fn construct_numeric_value( } fn update_message_builder( - message_builder: &mut MessageBuilder, - field_type: MessageFieldValueType, - descriptor: &FieldDescriptorProto, - field_name: &str, - constructed_value: &MessageFieldValue, + message_builder: &mut MessageBuilder, + field_type: MessageFieldValueType, + descriptor: &FieldDescriptorProto, + field_name: &str, + constructed_value: &MessageFieldValue ) { - match field_type { - MessageFieldValueType::Repeated => { - debug!( - "Setting field {:?}:repeated to value {:?}", - field_name, constructed_value - ); - message_builder.add_repeated_field_value( - descriptor, - field_name, - constructed_value.clone(), - ); - } - _ => { - debug!( - "Setting field {:?}:{:?} to value {:?}", - field_name, field_type, constructed_value - ); - message_builder.set_field_value(descriptor, field_name, constructed_value.clone()); - } + match field_type { + MessageFieldValueType::Repeated => { + debug!("Setting field {:?}:repeated to value {:?}", field_name, constructed_value); + message_builder.add_repeated_field_value(descriptor, field_name, constructed_value.clone()); + }, + _ => { + debug!("Setting field {:?}:{:?} to value {:?}", field_name, field_type, constructed_value); + message_builder.set_field_value(descriptor, field_name, constructed_value.clone()); } + } } fn construct_value_from_string( - path: &DocPath, - message_builder: &mut MessageBuilder, - descriptor: &FieldDescriptorProto, - field_name: &str, - matching_rules: &mut MatchingRuleCategory, - generators: &mut HashMap, - s: &str, - all_descriptors: &HashMap, + path: &DocPath, + message_builder: &mut MessageBuilder, + descriptor: &FieldDescriptorProto, + field_name: &str, + matching_rules: &mut MatchingRuleCategory, + generators: &mut HashMap, + s: &str, + all_descriptors: &HashMap ) -> anyhow::Result { - trace!(?field_name, string = ?s, "Building value from string"); - if is_matcher_def(s) { - trace!("String value is a matcher definition"); - let mrd = parse_matcher_def(s)?; - trace!("matcher definition = {:?}", mrd); - if !mrd.rules.is_empty() { - for rule in &mrd.rules { - match rule { - Either::Left(rule) => { - let path = if rule.is_values_matcher() && path.is_wildcard() { - // TODO: replace this with "path.parent().unwrap_or(DocPath::root())" when pact_models - // 1.1.6 is released - parent(path).unwrap_or(DocPath::root()) - } else { - path.clone() - }; - trace!(?path, ?rule, "adding matching rule to path"); - matching_rules.add_rule(path, rule.clone(), RuleLogic::And) - } - Either::Right(mr) => { - return Err(anyhow!( - "Was expecting a value for '{}', but got a matching reference {:?}", - path, - mr - )) - } - } - } - } - if let Some(generator) = &mrd.generator { - generators.insert(path.to_string(), generator.clone()); + trace!(?field_name, string = ?s, "Building value from string"); + if is_matcher_def(s) { + trace!("String value is a matcher definition"); + let mrd = parse_matcher_def(s)?; + trace!("matcher definition = {:?}", mrd); + if !mrd.rules.is_empty() { + for rule in &mrd.rules { + match rule { + Either::Left(rule) => { + let path = if rule.is_values_matcher() && path.is_wildcard() { + // TODO: replace this with "path.parent().unwrap_or(DocPath::root())" when pact_models + // 1.1.6 is released + parent(path).unwrap_or(DocPath::root()) + } else { + path.clone() + }; + trace!(?path, ?rule, "adding matching rule to path"); + matching_rules.add_rule(path, rule.clone(), RuleLogic::And) + }, + Either::Right(mr) => return Err(anyhow!("Was expecting a value for '{}', but got a matching reference {:?}", path, mr)) } - value_for_type( - field_name, - &value_for_field(&mrd), - descriptor, - &message_builder.descriptor, - all_descriptors, - ) - } else { - value_for_type( - field_name, - s, - descriptor, - &message_builder.descriptor, - all_descriptors, - ) + } } + if let Some(generator) = &mrd.generator { + generators.insert(path.to_string(), generator.clone()); + } + value_for_type(field_name, &value_for_field(&mrd), descriptor, &message_builder.descriptor, + all_descriptors) + } else { + value_for_type(field_name, s, descriptor, &message_builder.descriptor, + all_descriptors) + } } fn parent(path: &DocPath) -> Option { - let tokens = path.tokens().clone(); - if path.is_root() || tokens.len() <= 1 { - None - } else { - let mut path = DocPath::root(); - let tokens = tokens.split_last().unwrap().1; - for part in tokens.iter().skip(1) { - path = path.join(part.to_string()); - } - Some(path) + let tokens = path.tokens().clone(); + if path.is_root() || tokens.len() <= 1 { + None + } else { + let mut path = DocPath::root(); + let tokens = tokens.split_last().unwrap().1; + for part in tokens.iter().skip(1) { + path = path.join(part.to_string()); } + Some(path) + } } fn value_for_field(mrd: &MatchingRuleDefinition) -> String { - if mrd.value.is_empty() { - if let Some(value_matcher) = mrd.rules.iter().find_map(|m| match m { - Either::Left(mr) => match mr { - matchingrules::MatchingRule::EachValue(def) => Some(def.value.clone()), - _ => None, - }, - Either::Right(_) => None, - }) { - value_matcher - } else { - String::default() + if mrd.value.is_empty() { + if let Some(value_matcher) = mrd.rules.iter().find_map(|m| { + match m { + Either::Left(mr) => match mr { + matchingrules::MatchingRule::EachValue(def) => Some(def.value.clone()), + _ => None } + Either::Right(_) => None + } + }) { + value_matcher } else { - mrd.value.clone() + String::default() } + } else { + mrd.value.clone() + } } fn value_for_type( - field_name: &str, - field_value: &str, - descriptor: &FieldDescriptorProto, - message_descriptor: &DescriptorProto, - all_descriptors: &HashMap, + field_name: &str, + field_value: &str, + descriptor: &FieldDescriptorProto, + message_descriptor: &DescriptorProto, + all_descriptors: &HashMap ) -> anyhow::Result { - trace!("value_for_type({}, {}, _)", field_name, field_value); - - let type_name = descriptor.type_name.clone().unwrap_or_default(); - debug!( - "Creating value for type {:?} from '{}'", - type_name, field_value - ); - - let t = descriptor.r#type(); - match t { - Type::Double => MessageFieldValue::double(field_name, field_value), - Type::Float => MessageFieldValue::float(field_name, field_value), - Type::Int64 | Type::Sfixed64 | Type::Sint64 => { - MessageFieldValue::integer_64(field_name, field_value) - } - Type::Uint64 | Type::Fixed64 => MessageFieldValue::uinteger_64(field_name, field_value), - Type::Int32 | Type::Sfixed32 | Type::Sint32 => { - MessageFieldValue::integer_32(field_name, field_value) - } - Type::Uint32 | Type::Fixed32 => MessageFieldValue::uinteger_32(field_name, field_value), - Type::Bool => MessageFieldValue::boolean(field_name, field_value), - Type::String => Ok(MessageFieldValue::string(field_name, field_value)), - Type::Message => { - if type_name == ".google.protobuf.BytesValue" { - Ok(MessageFieldValue::bytes(field_name, field_value)) - } else { - Err(anyhow!( - "value_for_type: Protobuf field {} has an unsupported type {:?} {}", - field_name, - t, - type_name - )) - } - } - Type::Bytes => Ok(MessageFieldValue::bytes(field_name, field_value)), - Type::Enum => { - let result = find_enum_value_by_name_in_message( - &message_descriptor.enum_type, - type_name.as_str(), - field_value, - ) - .or_else(|| find_enum_value_by_name(all_descriptors, type_name.as_str(), field_value)); - if let Some((n, desc)) = result { - Ok(MessageFieldValue { - name: field_name.to_string(), - raw_value: Some(field_value.to_string()), - rtype: RType::Enum(n, desc), - }) - } else { - Err(anyhow!( - "Protobuf enum value {} has no value {}", - type_name, - field_value - )) - } - } - _ => Err(anyhow!( - "Protobuf field {} has an unsupported type {:?}", - field_name, - t - )), + trace!("value_for_type({}, {}, _)", field_name, field_value); + + let type_name = descriptor.type_name.clone().unwrap_or_default(); + debug!("Creating value for type {:?} from '{}'", type_name, field_value); + + let t = descriptor.r#type(); + match t { + Type::Double => MessageFieldValue::double(field_name, field_value), + Type::Float => MessageFieldValue::float(field_name, field_value), + Type::Int64 | Type::Sfixed64 | Type::Sint64 => MessageFieldValue::integer_64(field_name, field_value), + Type::Uint64 | Type::Fixed64 => MessageFieldValue::uinteger_64(field_name, field_value), + Type::Int32 | Type::Sfixed32 | Type::Sint32 => MessageFieldValue::integer_32(field_name, field_value), + Type::Uint32 | Type::Fixed32 => MessageFieldValue::uinteger_32(field_name, field_value), + Type::Bool => MessageFieldValue::boolean(field_name, field_value), + Type::String => Ok(MessageFieldValue::string(field_name, field_value)), + Type::Message => { + if type_name == ".google.protobuf.BytesValue" { + Ok(MessageFieldValue::bytes(field_name, field_value)) + } else { + Err(anyhow!("value_for_type: Protobuf field {} has an unsupported type {:?} {}", field_name, t, type_name)) + } + } + Type::Bytes => Ok(MessageFieldValue::bytes(field_name, field_value)), + Type::Enum => { + let result = find_enum_value_by_name_in_message(&message_descriptor.enum_type, type_name.as_str(), field_value) + .or_else(|| find_enum_value_by_name(all_descriptors, type_name.as_str(), field_value)); + if let Some((n, desc)) = result { + Ok(MessageFieldValue { + name: field_name.to_string(), + raw_value: Some(field_value.to_string()), + rtype: RType::Enum(n, desc) + }) + } else { + Err(anyhow!("Protobuf enum value {} has no value {}", type_name, field_value)) + } } + _ => Err(anyhow!("Protobuf field {} has an unsupported type {:?}", field_name, t)) + } } #[cfg(test)] pub(crate) mod tests { - use std::collections::HashMap; - - use base64::engine::general_purpose::STANDARD as BASE64; - use base64::Engine; - use bytes::Bytes; - use expectest::prelude::*; - use lazy_static::lazy_static; - use maplit::{btreemap, hashmap}; - use pact_models::matchingrules::expressions::{MatchingRuleDefinition, ValueType}; - use pact_models::path_exp::DocPath; - use pact_models::prelude::MatchingRuleCategory; - use pact_models::{matchingrules, matchingrules_list}; - use pact_plugin_driver::proto::interaction_response::MarkupType; - use pact_plugin_driver::proto::{MatchingRule, MatchingRules}; - use prost::Message; - use prost_types::field_descriptor_proto::{Label, Type}; - use prost_types::value::Kind::{ListValue, NullValue, NumberValue, StringValue, StructValue}; - use prost_types::{ - field_descriptor_proto, DescriptorProto, FieldDescriptorProto, FileDescriptorProto, - FileDescriptorSet, MethodDescriptorProto, MethodOptions, OneofDescriptorProto, - ServiceDescriptorProto, Struct, + use std::collections::HashMap; + + use base64::Engine; + use base64::engine::general_purpose::STANDARD as BASE64; + use bytes::Bytes; + use expectest::prelude::*; + use lazy_static::lazy_static; + use maplit::{btreemap, hashmap}; + use pact_models::{matchingrules, matchingrules_list}; + use pact_models::matchingrules::expressions::{MatchingRuleDefinition, ValueType}; + use pact_models::path_exp::DocPath; + use pact_models::prelude::MatchingRuleCategory; + use pact_plugin_driver::proto::{MatchingRule, MatchingRules}; + use pact_plugin_driver::proto::interaction_response::MarkupType; + use prost::Message; + use prost_types::{ + DescriptorProto, + field_descriptor_proto, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, + MethodDescriptorProto, + MethodOptions, + OneofDescriptorProto, + ServiceDescriptorProto, + Struct + }; + use prost_types::field_descriptor_proto::{Label, Type}; + use prost_types::value::Kind::{ListValue, NullValue, NumberValue, StringValue, StructValue}; + use serde_json::{json, Value}; + use trim_margin::MarginTrimmable; + + use crate::message_builder::{MessageBuilder, MessageFieldValue, MessageFieldValueType, RType}; + use crate::protobuf::{ + build_embedded_message_field_value, + build_field_value, + build_single_embedded_field_value, + construct_message_field, + construct_protobuf_interaction_for_message, + construct_protobuf_interaction_for_service, + request_part, + response_part, + value_for_type + }; + use crate::utils::find_message_type_by_name; + + #[test] + fn value_for_type_test() { + let message_descriptor = DescriptorProto { + name: None, + field: vec![], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] }; - use serde_json::{json, Value}; - use trim_margin::MarginTrimmable; - - use crate::message_builder::{MessageBuilder, MessageFieldValue, MessageFieldValueType, RType}; - use crate::protobuf::{ - build_embedded_message_field_value, build_field_value, build_single_embedded_field_value, - construct_message_field, construct_protobuf_interaction_for_message, - construct_protobuf_interaction_for_service, request_part, response_part, value_for_type, + let descriptor = FieldDescriptorProto { + name: None, + number: None, + label: None, + r#type: Some(Type::String as i32), + type_name: Some("test".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None }; - use crate::utils::find_message_type_by_name; - - #[test] - fn value_for_type_test() { - let message_descriptor = DescriptorProto { - name: None, - field: vec![], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let descriptor = FieldDescriptorProto { - name: None, - number: None, - label: None, - r#type: Some(Type::String as i32), - type_name: Some("test".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }; - let result = value_for_type( - "test", - "test", - &descriptor, - &message_descriptor, - &hashmap! {}, - ) - .unwrap(); - expect!(result.name).to(be_equal_to("test")); - expect!(result.raw_value).to(be_some().value("test".to_string())); - expect!(result.rtype).to(be_equal_to(RType::String("test".to_string()))); - - let descriptor = FieldDescriptorProto { - name: None, - number: None, - label: None, - r#type: Some(Type::Uint64 as i32), - type_name: Some("uint64".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }; - let result = value_for_type( - "test", - "100", - &descriptor, - &message_descriptor, - &hashmap! {}, - ) - .unwrap(); - expect!(result.name).to(be_equal_to("test")); - expect!(result.raw_value).to(be_some().value("100".to_string())); - expect!(result.rtype).to(be_equal_to(RType::UInteger64(100))); - } + let result = value_for_type("test", "test", &descriptor, &message_descriptor, &hashmap!{}).unwrap(); + expect!(result.name).to(be_equal_to("test")); + expect!(result.raw_value).to(be_some().value("test".to_string())); + expect!(result.rtype).to(be_equal_to(RType::String("test".to_string()))); + + let descriptor = FieldDescriptorProto { + name: None, + number: None, + label: None, + r#type: Some(Type::Uint64 as i32), + type_name: Some("uint64".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + }; + let result = value_for_type("test", "100", &descriptor, &message_descriptor, &hashmap!{}).unwrap(); + expect!(result.name).to(be_equal_to("test")); + expect!(result.raw_value).to(be_some().value("100".to_string())); + expect!(result.rtype).to(be_equal_to(RType::UInteger64(100))); + } - #[test] - fn construct_protobuf_interaction_for_message_test() { - let file_descriptor = FileDescriptorProto { - name: None, - package: None, - dependency: vec![], - public_dependency: vec![], - weak_dependency: vec![], - message_type: vec![], - enum_type: vec![], - service: vec![], - extension: vec![], - options: None, - source_code_info: None, - syntax: None, - }; - let message_descriptor = DescriptorProto { - name: Some("test_message".to_string()), - field: vec![ - FieldDescriptorProto { - name: Some("implementation".to_string()), - number: Some(1), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }, - FieldDescriptorProto { - name: Some("version".to_string()), - number: Some(2), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }, - FieldDescriptorProto { - name: Some("length".to_string()), - number: Some(3), - label: None, - r#type: Some(field_descriptor_proto::Type::Int64 as i32), - type_name: Some("int64".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }, - FieldDescriptorProto { - name: Some("hash".to_string()), - number: Some(4), - label: None, - r#type: Some(field_descriptor_proto::Type::Uint64 as i32), - type_name: Some("uint64".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }, - ], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let config = btreemap! { - "implementation".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("notEmpty('plugin-driver-rust')".to_string())) }, - "version".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("matching(semver, '0.0.0')".to_string())) }, - "hash".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("matching(integer, 1234)".to_string())) } - }; + #[test] + fn construct_protobuf_interaction_for_message_test() { + let file_descriptor = FileDescriptorProto { + name: None, + package: None, + dependency: vec![], + public_dependency: vec![], + weak_dependency: vec![], + message_type: vec![], + enum_type: vec![], + service: vec![], + extension: vec![], + options: None, + source_code_info: None, + syntax: None + }; + let message_descriptor = DescriptorProto { + name: Some("test_message".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("implementation".to_string()), + number: Some(1), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("version".to_string()), + number: Some(2), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("length".to_string()), + number: Some(3), + label: None, + r#type: Some(field_descriptor_proto::Type::Int64 as i32), + type_name: Some("int64".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("hash".to_string()), + number: Some(4), + label: None, + r#type: Some(field_descriptor_proto::Type::Uint64 as i32), + type_name: Some("uint64".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; + let config = btreemap! { + "implementation".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("notEmpty('plugin-driver-rust')".to_string())) }, + "version".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("matching(semver, '0.0.0')".to_string())) }, + "hash".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("matching(integer, 1234)".to_string())) } + }; - let result = construct_protobuf_interaction_for_message( - &message_descriptor, - &config, - "test_message", - "", - &file_descriptor, - &hashmap! {}, - None, - ) - .unwrap(); - - let body = result.contents.as_ref().unwrap(); - expect!(body.content_type.as_str()) - .to(be_equal_to("application/protobuf;message=test_message")); - expect!(body.content_type_hint).to(be_equal_to(2)); - expect!(body.content.as_ref()).to(be_some().value(&vec![ - 10, // field 1 length encoded (1 << 3 + 2 == 10) - 18, // 18 bytes - 112, 108, 117, 103, 105, 110, 45, 100, 114, 105, 118, 101, 114, 45, 114, 117, 115, 116, - 18, // field 2 length encoded (2 << 3 + 2 == 18) - 5, // 5 bytes - 48, 46, 48, 46, 48, 32, // field 4 varint encoded (4 << 3 + 0 == 32) - 210, 9, // 9 << 7 + 210 == 1234 - ])); - - expect!(result.rules).to(be_equal_to(hashmap! { + let result = construct_protobuf_interaction_for_message(&message_descriptor, &config, + "test_message", "", &file_descriptor, &hashmap!{}, None).unwrap(); + + let body = result.contents.as_ref().unwrap(); + expect!(body.content_type.as_str()).to(be_equal_to("application/protobuf;message=test_message")); + expect!(body.content_type_hint).to(be_equal_to(2)); + expect!(body.content.as_ref()).to(be_some().value(&vec![ + 10, // field 1 length encoded (1 << 3 + 2 == 10) + 18, // 18 bytes + 112, 108, 117, 103, 105, 110, 45, 100, 114, 105, 118, 101, 114, 45, 114, 117, 115, 116, + 18, // field 2 length encoded (2 << 3 + 2 == 18) + 5, // 5 bytes + 48, 46, 48, 46, 48, + 32, // field 4 varint encoded (4 << 3 + 0 == 32) + 210, 9 // 9 << 7 + 210 == 1234 + ])); + + expect!(result.rules).to(be_equal_to(hashmap! { "$.implementation".to_string() => MatchingRules { rule: vec![ MatchingRule { r#type: "not-empty".to_string(), .. MatchingRule::default() } ] }, "$.version".to_string() => MatchingRules { rule: vec![ MatchingRule { r#type: "semver".to_string(), .. MatchingRule::default() } ] }, "$.hash".to_string() => MatchingRules { rule: vec![ MatchingRule { r#type: "integer".to_string(), .. MatchingRule::default() } ] } })); - expect!(result.generators).to(be_equal_to(hashmap! {})); + expect!(result.generators).to(be_equal_to(hashmap! {})); - expect!(result.interaction_markup_type).to(be_equal_to(MarkupType::CommonMark as i32)); - expect!(result.interaction_markup).to(be_equal_to( - "|```protobuf + expect!(result.interaction_markup_type).to(be_equal_to(MarkupType::CommonMark as i32)); + expect!(result.interaction_markup).to(be_equal_to( + "|```protobuf |message test_message { | string implementation = 1; | string version = 2; | uint64 hash = 4; |} |``` - |" - .trim_margin() - .unwrap(), - )); - } - - const DESCRIPTORS_FOR_EACH_VALUE_TEST: [u8; 267] = [ - 10, 136, 2, 10, 12, 115, 105, 109, 112, 108, 101, 46, 112, 114, 111, 116, 111, 34, 27, 10, - 9, 77, 101, 115, 115, 97, 103, 101, 73, 110, 18, 14, 10, 2, 105, 110, 24, 1, 32, 1, 40, 8, - 82, 2, 105, 110, 34, 30, 10, 10, 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 18, 16, 10, - 3, 111, 117, 116, 24, 1, 32, 1, 40, 8, 82, 3, 111, 117, 116, 34, 39, 10, 15, 86, 97, 108, - 117, 101, 115, 77, 101, 115, 115, 97, 103, 101, 73, 110, 18, 20, 10, 5, 118, 97, 108, 117, - 101, 24, 1, 32, 3, 40, 9, 82, 5, 118, 97, 108, 117, 101, 34, 40, 10, 16, 86, 97, 108, 117, - 101, 115, 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 18, 20, 10, 5, 118, 97, 108, 117, - 101, 24, 1, 32, 3, 40, 9, 82, 5, 118, 97, 108, 117, 101, 50, 96, 10, 4, 84, 101, 115, 116, - 18, 36, 10, 7, 71, 101, 116, 84, 101, 115, 116, 18, 10, 46, 77, 101, 115, 115, 97, 103, - 101, 73, 110, 26, 11, 46, 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 34, 0, 18, 50, 10, - 9, 71, 101, 116, 86, 97, 108, 117, 101, 115, 18, 16, 46, 86, 97, 108, 117, 101, 115, 77, - 101, 115, 115, 97, 103, 101, 73, 110, 26, 17, 46, 86, 97, 108, 117, 101, 115, 77, 101, 115, - 115, 97, 103, 101, 79, 117, 116, 34, 0, 98, 6, 112, 114, 111, 116, 111, 51, - ]; - - #[test_log::test] - fn construct_protobuf_interaction_for_message_with_each_value_matcher() { - let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); - let fs = fds.file.first().unwrap(); - let all_descriptors = hashmap! { "simple.proto".to_string() => fs }; - let config = btreemap! { - "value".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("eachValue(matching(type, '00000000000000000000000000000000'))".to_string())) } - }; - let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); - - let result = construct_protobuf_interaction_for_message( - &message_descriptor, - &config, - "ValuesMessageIn", - "", - fs, - &all_descriptors, - None, - ) - .unwrap(); - - let body = result.contents.as_ref().unwrap(); - expect!(body.content_type.as_str()) - .to(be_equal_to("application/protobuf;message=ValuesMessageIn")); - expect!(body.content.as_ref()).to(be_some().value(&vec![ - 10, // field 1 length encoded (1 << 3 + 2 == 10) - 32, // 32 bytes - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, // Lots of zeros - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - ])); - - let value_matcher = result.rules.get("$.value").unwrap().rule.first().unwrap(); - expect!(&value_matcher.r#type).to(be_equal_to("each-value")); - let values = value_matcher.values.clone().unwrap(); - expect!(values.fields.get("value").unwrap().kind.clone().unwrap()).to(be_equal_to( - StringValue("00000000000000000000000000000000".to_string()), - )); - expect!(result.generators).to(be_equal_to(hashmap! {})); - } + |".trim_margin().unwrap())); + } - #[test_log::test] - fn construct_message_field_with_message_with_each_value_matcher() { - let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); - let fs = fds.file.first().unwrap(); - let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); - let mut message_builder = MessageBuilder::new(&message_descriptor, "ValuesMessageIn", fs); - let path = DocPath::new("$.value").unwrap(); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = construct_message_field( - &mut message_builder, - &mut matching_rules, - &mut generators, - "value", - &Value::String( - "eachValue(matching(type, '00000000000000000000000000000000'))".to_string(), - ), - &path, - &file_descriptors, - ); - expect!(result).to(be_ok()); - - let field = message_builder.fields.get("value"); - expect!(field).to(be_some()); - let inner = field.unwrap(); - expect!(inner.values.clone()).to(be_equal_to(vec![MessageFieldValue { - name: "value".to_string(), - raw_value: Some("00000000000000000000000000000000".to_string()), - rtype: RType::String("00000000000000000000000000000000".to_string()), - }])); - - expect!(matching_rules).to(be_equal_to(matchingrules_list! { - "body"; - "$.value" => [ - pact_models::matchingrules::MatchingRule::EachValue( - MatchingRuleDefinition::new("00000000000000000000000000000000".to_string(), - ValueType::Unknown, pact_models::matchingrules::MatchingRule::Type, None) - ) - ] - })); - } + const DESCRIPTORS_FOR_EACH_VALUE_TEST: [u8; 267] = [ + 10, 136, 2, 10, 12, 115, 105, 109, 112, 108, 101, 46, 112, 114, 111, + 116, 111, 34, 27, 10, 9, 77, 101, 115, 115, 97, 103, 101, 73, 110, 18, 14, 10, 2, 105, 110, + 24, 1, 32, 1, 40, 8, 82, 2, 105, 110, 34, 30, 10, 10, 77, 101, 115, 115, 97, 103, 101, 79, + 117, 116, 18, 16, 10, 3, 111, 117, 116, 24, 1, 32, 1, 40, 8, 82, 3, 111, 117, 116, 34, 39, + 10, 15, 86, 97, 108, 117, 101, 115, 77, 101, 115, 115, 97, 103, 101, 73, 110, 18, 20, 10, 5, + 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 9, 82, 5, 118, 97, 108, 117, 101, 34, 40, 10, 16, + 86, 97, 108, 117, 101, 115, 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 18, 20, 10, 5, + 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 9, 82, 5, 118, 97, 108, 117, 101, 50, 96, 10, 4, + 84, 101, 115, 116, 18, 36, 10, 7, 71, 101, 116, 84, 101, 115, 116, 18, 10, 46, 77, 101, 115, + 115, 97, 103, 101, 73, 110, 26, 11, 46, 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 34, + 0, 18, 50, 10, 9, 71, 101, 116, 86, 97, 108, 117, 101, 115, 18, 16, 46, 86, 97, 108, 117, + 101, 115, 77, 101, 115, 115, 97, 103, 101, 73, 110, 26, 17, 46, 86, 97, 108, 117, 101, 115, + 77, 101, 115, 115, 97, 103, 101, 79, 117, 116, 34, 0, 98, 6, 112, 114, 111, 116, 111, 51]; + + #[test_log::test] + fn construct_protobuf_interaction_for_message_with_each_value_matcher() { + let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); + let fs = fds.file.first().unwrap(); + let all_descriptors = hashmap!{ "simple.proto".to_string() => fs }; + let config = btreemap! { + "value".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("eachValue(matching(type, '00000000000000000000000000000000'))".to_string())) } + }; + let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); + + let result = construct_protobuf_interaction_for_message( + &message_descriptor, + &config, + "ValuesMessageIn", + "", + fs, + &all_descriptors, + None + ).unwrap(); + + let body = result.contents.as_ref().unwrap(); + expect!(body.content_type.as_str()).to(be_equal_to("application/protobuf;message=ValuesMessageIn")); + expect!(body.content.as_ref()).to(be_some().value(&vec![ + 10, // field 1 length encoded (1 << 3 + 2 == 10) + 32, // 32 bytes + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, // Lots of zeros + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48 + ])); + + let value_matcher = result.rules.get("$.value").unwrap().rule.first().unwrap(); + expect!(&value_matcher.r#type).to(be_equal_to("each-value")); + let values = value_matcher.values.clone().unwrap(); + expect!(values.fields.get("value").unwrap().kind.clone().unwrap()).to(be_equal_to( + StringValue("00000000000000000000000000000000".to_string()) + )); + expect!(result.generators).to(be_equal_to(hashmap! {})); + } - #[test_log::test] - fn build_field_value_with_message_with_each_value_matcher() { - let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); - let fs = fds.file.first().unwrap(); - let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); - let field_descriptor = message_descriptor.field.first().unwrap(); - let mut message_builder = MessageBuilder::new(&message_descriptor, "ValuesMessageIn", fs); - let path = DocPath::new("$.value").unwrap(); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = build_field_value( - &path, - &mut message_builder, - MessageFieldValueType::Repeated, - field_descriptor, - "value", - &Value::String( - "eachValue(matching(type, '00000000000000000000000000000000'))".to_string(), - ), - &mut matching_rules, - &mut generators, - &file_descriptors, + #[test_log::test] + fn construct_message_field_with_message_with_each_value_matcher() { + let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); + let fs = fds.file.first().unwrap(); + let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "ValuesMessageIn", fs); + let path = DocPath::new("$.value").unwrap(); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = construct_message_field(&mut message_builder, &mut matching_rules, + &mut generators, "value", &Value::String("eachValue(matching(type, '00000000000000000000000000000000'))".to_string()), + &path, &file_descriptors); + expect!(result).to(be_ok()); + + let field = message_builder.fields.get("value"); + expect!(field).to(be_some()); + let inner = field.unwrap(); + expect!(inner.values.clone()).to(be_equal_to(vec![ + MessageFieldValue { + name: "value".to_string(), + raw_value: Some("00000000000000000000000000000000".to_string()), + rtype: RType::String("00000000000000000000000000000000".to_string()) + } + ])); + + expect!(matching_rules).to(be_equal_to(matchingrules_list! { + "body"; + "$.value" => [ + pact_models::matchingrules::MatchingRule::EachValue( + MatchingRuleDefinition::new("00000000000000000000000000000000".to_string(), + ValueType::Unknown, pact_models::matchingrules::MatchingRule::Type, None) ) - .unwrap(); - - expect!(result.as_ref()).to(be_some()); - let message_field_value = result.unwrap(); - expect!(message_field_value).to(be_equal_to(MessageFieldValue { - name: "value".to_string(), - raw_value: Some("00000000000000000000000000000000".to_string()), - rtype: RType::String("00000000000000000000000000000000".to_string()), - })); - let field_value = message_builder.fields.get("value").unwrap(); - expect!(field_value.values.as_ref()).to(be_equal_to(vec![MessageFieldValue { - name: "value".to_string(), - raw_value: Some("00000000000000000000000000000000".to_string()), - rtype: RType::String("00000000000000000000000000000000".to_string()), - }])); - } + ] + })); + } - #[test] - fn construct_protobuf_interaction_for_service_returns_error_on_invalid_request_type() { - let string_descriptor = DescriptorProto { - name: Some("StringValue".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let message_descriptor = DescriptorProto { - name: Some("test_message".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let file_descriptor = FileDescriptorProto { - name: None, - package: None, - dependency: vec![], - public_dependency: vec![], - weak_dependency: vec![], - message_type: vec![string_descriptor, message_descriptor], - enum_type: vec![], - service: vec![], - extension: vec![], - options: None, - source_code_info: None, - syntax: None, - }; - let service_descriptor = ServiceDescriptorProto { - name: Some("test_service".to_string()), - method: vec![MethodDescriptorProto { - name: Some("call".to_string()), - input_type: Some(".google.protobuf.StringValue".to_string()), - output_type: Some("test_message".to_string()), - options: None, - client_streaming: None, - server_streaming: None, - }], - options: None, - }; + #[test_log::test] + fn build_field_value_with_message_with_each_value_matcher() { + let fds = FileDescriptorSet::decode(DESCRIPTORS_FOR_EACH_VALUE_TEST.as_slice()).unwrap(); + let fs = fds.file.first().unwrap(); + let (message_descriptor, _) = find_message_type_by_name("ValuesMessageIn", &fds).unwrap(); + let field_descriptor = message_descriptor.field.first().unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "ValuesMessageIn", fs); + let path = DocPath::new("$.value").unwrap(); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = build_field_value( + &path, &mut message_builder, MessageFieldValueType::Repeated, field_descriptor, + "value", &Value::String("eachValue(matching(type, '00000000000000000000000000000000'))".to_string()), + &mut matching_rules, &mut generators, &file_descriptors + ).unwrap(); + + expect!(result.as_ref()).to(be_some()); + let message_field_value = result.unwrap(); + expect!(message_field_value).to(be_equal_to(MessageFieldValue { + name: "value".to_string(), + raw_value: Some("00000000000000000000000000000000".to_string()), + rtype: RType::String("00000000000000000000000000000000".to_string()) + })); + let field_value = message_builder.fields.get("value").unwrap(); + expect!(field_value.values.as_ref()).to(be_equal_to(vec![ + MessageFieldValue { + name: "value".to_string(), + raw_value: Some("00000000000000000000000000000000".to_string()), + rtype: RType::String("00000000000000000000000000000000".to_string()) + } + ])); + } - let config = btreemap! { - "request".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::BoolValue(true)) } - }; + #[test] + fn construct_protobuf_interaction_for_service_returns_error_on_invalid_request_type() { + let string_descriptor = DescriptorProto { + name: Some("StringValue".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; + let message_descriptor = DescriptorProto { + name: Some("test_message".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; + let file_descriptor = FileDescriptorProto { + name: None, + package: None, + dependency: vec![], + public_dependency: vec![], + weak_dependency: vec![], + message_type: vec![ string_descriptor, message_descriptor ], + enum_type: vec![], + service: vec![], + extension: vec![], + options: None, + source_code_info: None, + syntax: None + }; + let service_descriptor = ServiceDescriptorProto { + name: Some("test_service".to_string()), + method: vec![ + MethodDescriptorProto { + name: Some("call".to_string()), + input_type: Some(".google.protobuf.StringValue".to_string()), + output_type: Some("test_message".to_string()), + options: None, + client_streaming: None, + server_streaming: None + } + ], + options: None + }; + + let config = btreemap! { + "request".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::BoolValue(true)) } + }; - let result = construct_protobuf_interaction_for_service( - &service_descriptor, - &config, - "test_service", - "call", - &hashmap! { "file".to_string() => &file_descriptor }, - &file_descriptor, - ); - expect!(result.as_ref()).to(be_err()); - expect!(result.unwrap_err().to_string()).to( + let result = construct_protobuf_interaction_for_service(&service_descriptor, &config, + "test_service", "call", &hashmap!{ "file".to_string() => &file_descriptor }, &file_descriptor); + expect!(result.as_ref()).to(be_err()); + expect!(result.unwrap_err().to_string()).to( be_equal_to("Request contents is of an un-processable type: BoolValue(true), it should be either a Struct or a StringValue") ); - } + } - #[test_log::test] - fn construct_protobuf_interaction_for_service_supports_string_value_type() { - let string_descriptor = DescriptorProto { - name: Some("StringValue".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let message_descriptor = DescriptorProto { - name: Some("test_message".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: None, - r#type: Some(field_descriptor_proto::Type::String as i32), - type_name: Some("string".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; - let file_descriptor = FileDescriptorProto { - name: None, - package: None, - dependency: vec![], - public_dependency: vec![], - weak_dependency: vec![], - message_type: vec![string_descriptor, message_descriptor], - enum_type: vec![], - service: vec![], - extension: vec![], - options: None, - source_code_info: None, - syntax: None, - }; - let service_descriptor = ServiceDescriptorProto { - name: Some("test_service".to_string()), - method: vec![MethodDescriptorProto { - name: Some("call".to_string()), - input_type: Some(".google.protobuf.StringValue".to_string()), - output_type: Some("test_message".to_string()), - options: None, - client_streaming: None, - server_streaming: None, - }], - options: None, - }; + #[test_log::test] + fn construct_protobuf_interaction_for_service_supports_string_value_type() { + let string_descriptor = DescriptorProto { + name: Some("StringValue".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; + let message_descriptor = DescriptorProto { + name: Some("test_message".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: None, + r#type: Some(field_descriptor_proto::Type::String as i32), + type_name: Some("string".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; + let file_descriptor = FileDescriptorProto { + name: None, + package: None, + dependency: vec![], + public_dependency: vec![], + weak_dependency: vec![], + message_type: vec![ string_descriptor, message_descriptor ], + enum_type: vec![], + service: vec![], + extension: vec![], + options: None, + source_code_info: None, + syntax: None + }; + let service_descriptor = ServiceDescriptorProto { + name: Some("test_service".to_string()), + method: vec![ + MethodDescriptorProto { + name: Some("call".to_string()), + input_type: Some(".google.protobuf.StringValue".to_string()), + output_type: Some("test_message".to_string()), + options: None, + client_streaming: None, + server_streaming: None + } + ], + options: None + }; - let config = btreemap! { - "request".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("true".to_string())) } - }; + let config = btreemap! { + "request".to_string() => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue("true".to_string())) } + }; - let result = construct_protobuf_interaction_for_service( - &service_descriptor, - &config, - "test_service", - "call", - &hashmap! { "file".to_string() => &file_descriptor }, - &file_descriptor, - ); - expect!(result).to(be_ok()); - } + let result = construct_protobuf_interaction_for_service(&service_descriptor, &config, + "test_service", "call", &hashmap!{ "file".to_string() => &file_descriptor }, &file_descriptor); + expect!(result).to(be_ok()); + } - lazy_static! { - static ref FILE_DESCRIPTOR: FileDescriptorProto = FileDescriptorProto { - name: Some("area_calculator.proto".to_string()), - package: Some("area_calculator".to_string()), - dependency: vec![], - public_dependency: vec![], - weak_dependency: vec![], - message_type: vec![ - DescriptorProto { - name: Some("ShapeMessage".to_string()), - field: vec![ - FieldDescriptorProto { - name: Some("square".to_string()), - number: Some(1), - label: Some(Label::Optional as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Square".to_string()), - extendee: None, - default_value: None, - oneof_index: Some(0), - json_name: Some("square".to_string()), - options: None, - proto3_optional: None - }, - FieldDescriptorProto { - name: Some("rectangle".to_string()), - number: Some(2), - label: Some(Label::Optional as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Rectangle".to_string()), - extendee: None, - default_value: None, - oneof_index: Some(0), - json_name: Some("rectangle".to_string()), - options: None, - proto3_optional: None - } - ], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![OneofDescriptorProto { - name: Some("shape".to_string()), - options: None - }], - options: None, - reserved_range: vec![], - reserved_name: vec![] - }, - DescriptorProto { - name: Some("Square".to_string()), - field: vec![FieldDescriptorProto { - name: Some("edge_length".to_string()), - number: Some(1), - label: Some(Label::Optional as i32), - r#type: Some(Type::Float as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("edgeLength".to_string()), - options: None, - proto3_optional: None - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![] - }, - DescriptorProto { - name: Some("Rectangle".to_string()), - field: vec![ - FieldDescriptorProto { - name: Some("length".to_string()), - number: Some(1), - label: Some(Label::Optional as i32), - r#type: Some(Type::Float as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("length".to_string()), - options: None, - proto3_optional: None - }, - FieldDescriptorProto { - name: Some("width".to_string()), - number: Some(2), - label: Some(Label::Optional as i32), - r#type: Some(Type::Float as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("width".to_string()), - options: None, - proto3_optional: None - } - ], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![] - }, - DescriptorProto { - name: Some("Area".to_string()), - field: vec![ - FieldDescriptorProto { - name: Some("id".to_string()), - number: Some(1), - label: Some(Label::Optional as i32), - r#type: Some(Type::String as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("id".to_string()), - options: None, - proto3_optional: None - }, - FieldDescriptorProto { - name: Some("shape".to_string()), - number: Some(2), - label: Some(Label::Optional as i32), - r#type: Some(Type::String as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("shape".to_string()), - options: None, - proto3_optional: None - }, - FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(3), - label: Some(Label::Optional as i32), - r#type: Some(Type::Float as i32), - type_name: None, - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("value".to_string()), - options: None, - proto3_optional: None - } - ], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![] - }, - DescriptorProto { - name: Some("AreaResponse".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: Some(Label::Optional as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Area".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("value".to_string()), - options: None, - proto3_optional: None - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![] - } - ], - enum_type: vec![], - service: vec![ServiceDescriptorProto { - name: Some("Calculator".to_string()), - method: vec![ - MethodDescriptorProto { - name: Some("calculateOne".to_string()), - input_type: Some(".area_calculator.ShapeMessage".to_string()), - output_type: Some(".area_calculator.AreaResponse".to_string()), - options: Some(MethodOptions { - deprecated: None, - idempotency_level: None, - uninterpreted_option: vec![] - }), - client_streaming: None, - server_streaming: None - }, - MethodDescriptorProto { - name: Some("calculateMulti".to_string()), - input_type: Some(".area_calculator.AreaRequest".to_string()), - output_type: Some(".area_calculator.AreaResponse".to_string()), - options: Some(MethodOptions { - deprecated: None, - idempotency_level: None, - uninterpreted_option: vec![] - }), - client_streaming: None, - server_streaming: None - } - ], - options: None - }], - extension: vec![], - options: None, - source_code_info: None, - syntax: Some("proto3".to_string()) - }; - } + lazy_static! { + static ref FILE_DESCRIPTOR: FileDescriptorProto = FileDescriptorProto { + name: Some("area_calculator.proto".to_string()), + package: Some("area_calculator".to_string()), + dependency: vec![], + public_dependency: vec![], + weak_dependency: vec![], + message_type: vec![ + DescriptorProto { + name: Some("ShapeMessage".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("square".to_string()), + number: Some(1), + label: Some(Label::Optional as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Square".to_string()), + extendee: None, + default_value: None, + oneof_index: Some(0), + json_name: Some("square".to_string()), + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("rectangle".to_string()), + number: Some(2), + label: Some(Label::Optional as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Rectangle".to_string()), + extendee: None, + default_value: None, + oneof_index: Some(0), + json_name: Some("rectangle".to_string()), + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![ + OneofDescriptorProto { + name: Some("shape".to_string()), + options: None + } + ], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }, + DescriptorProto { + name: Some("Square".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("edge_length".to_string()), + number: Some(1), + label: Some(Label::Optional as i32), + r#type: Some(Type::Float as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("edgeLength".to_string()), + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }, + DescriptorProto { + name: Some("Rectangle".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("length".to_string()), + number: Some(1), + label: Some(Label::Optional as i32), + r#type: Some(Type::Float as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("length".to_string()), + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("width".to_string()), + number: Some(2), + label: Some(Label::Optional as i32), + r#type: Some(Type::Float as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("width".to_string()), + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }, + DescriptorProto { + name: Some("Area".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("id".to_string()), + number: Some(1), + label: Some(Label::Optional as i32), + r#type: Some(Type::String as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("id".to_string()), + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("shape".to_string()), + number: Some(2), + label: Some(Label::Optional as i32), + r#type: Some(Type::String as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("shape".to_string()), + options: None, + proto3_optional: None + }, + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(3), + label: Some(Label::Optional as i32), + r#type: Some(Type::Float as i32), + type_name: None, + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("value".to_string()), + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }, + DescriptorProto { + name: Some("AreaResponse".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: Some(Label::Optional as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Area".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("value".to_string()), + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + } + ], + enum_type: vec![], + service: vec![ + ServiceDescriptorProto { + name: Some("Calculator".to_string()), + method: vec![ + MethodDescriptorProto { + name: Some("calculateOne".to_string()), + input_type: Some(".area_calculator.ShapeMessage".to_string()), + output_type: Some(".area_calculator.AreaResponse".to_string()), + options: Some(MethodOptions { + deprecated: None, + idempotency_level: None, + uninterpreted_option: vec![] + }), + client_streaming: None, + server_streaming: None + }, + MethodDescriptorProto { + name: Some("calculateMulti".to_string()), + input_type: Some(".area_calculator.AreaRequest".to_string()), + output_type: Some(".area_calculator.AreaResponse".to_string()), + options: Some(MethodOptions { + deprecated: None, + idempotency_level: None, + uninterpreted_option: vec![] + }), + client_streaming: None, + server_streaming: None + } + ], + options: None + } + ], + extension: vec![], + options: None, + source_code_info: None, + syntax: Some("proto3".to_string()) + }; + } - #[test_log::test] - fn build_embedded_message_field_value_with_repeated_field_configured_from_map_with_eachvalue_test( - ) { - let message_descriptor = DescriptorProto { - name: Some("AreaResponse".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: Some(Label::Repeated as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Area".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; + #[test_log::test] + fn build_embedded_message_field_value_with_repeated_field_configured_from_map_with_eachvalue_test() { + let message_descriptor = DescriptorProto { + name: Some("AreaResponse".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: Some(Label::Repeated as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Area".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; - let mut message_builder = - MessageBuilder::new(&message_descriptor, "AreaResponse", &FILE_DESCRIPTOR); - let path = DocPath::new("$.value").unwrap(); - let field_descriptor = FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: Some(Label::Repeated as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Area".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("value".to_string()), - options: None, - proto3_optional: None, - }; - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let config = json!({ - "area": { - "id": "matching(regex, '\\d+', '1234')", - "shape": "matching(type, 'rectangle')", - "value": "matching(number, 12)" - }, - "pact:match": "eachValue(matching($'area'))" - }); - - let result = build_embedded_message_field_value( - &mut message_builder, - &path, - &field_descriptor, - "value", - &config, - &mut matching_rules, - &mut generators, - &hashmap! {}, - ); - - let expected_rules = matchingrules! { + let mut message_builder = MessageBuilder::new(&message_descriptor, "AreaResponse", &FILE_DESCRIPTOR); + let path = DocPath::new("$.value").unwrap(); + let field_descriptor = FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: Some(Label::Repeated as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Area".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("value".to_string()), + options: None, + proto3_optional: None + }; + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let config = json!({ + "area": { + "id": "matching(regex, '\\d+', '1234')", + "shape": "matching(type, 'rectangle')", + "value": "matching(number, 12)" + }, + "pact:match": "eachValue(matching($'area'))" + }); + + let result = build_embedded_message_field_value(&mut message_builder, &path, &field_descriptor, + "value", &config, &mut matching_rules, &mut generators, &hashmap!{} + ); + + let expected_rules = matchingrules! { "body" => { "$.value" => [ pact_models::matchingrules::MatchingRule::Values ], "$.value.*" => [ pact_models::matchingrules::MatchingRule::Type ], @@ -2706,85 +2070,78 @@ pub(crate) mod tests { "$.value.*.value" => [ pact_models::matchingrules::MatchingRule::Number ] } }.rules_for_category("body").unwrap(); - expect!(result).to(be_ok()); - expect!(matching_rules).to(be_equal_to(expected_rules)); - } + expect!(result).to(be_ok()); + expect!(matching_rules).to(be_equal_to(expected_rules)); + } - #[test_log::test] - fn build_embedded_message_field_value_with_repeated_field_configured_from_map_test() { - let message_descriptor = DescriptorProto { - name: Some("AreaResponse".to_string()), - field: vec![FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: Some(Label::Repeated as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Area".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: None, - options: None, - proto3_optional: None, - }], - extension: vec![], - nested_type: vec![], - enum_type: vec![], - extension_range: vec![], - oneof_decl: vec![], - options: None, - reserved_range: vec![], - reserved_name: vec![], - }; + #[test_log::test] + fn build_embedded_message_field_value_with_repeated_field_configured_from_map_test() { + let message_descriptor = DescriptorProto { + name: Some("AreaResponse".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: Some(Label::Repeated as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Area".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: None, + options: None, + proto3_optional: None + } + ], + extension: vec![], + nested_type: vec![], + enum_type: vec![], + extension_range: vec![], + oneof_decl: vec![], + options: None, + reserved_range: vec![], + reserved_name: vec![] + }; - let mut message_builder = - MessageBuilder::new(&message_descriptor, "AreaResponse", &FILE_DESCRIPTOR); - let path = DocPath::new("$.value").unwrap(); - let field_descriptor = FieldDescriptorProto { - name: Some("value".to_string()), - number: Some(1), - label: Some(Label::Repeated as i32), - r#type: Some(Type::Message as i32), - type_name: Some(".area_calculator.Area".to_string()), - extendee: None, - default_value: None, - oneof_index: None, - json_name: Some("value".to_string()), - options: None, - proto3_optional: None, - }; - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let config = json!({ - "id": "matching(regex, '\\d+', '1234')", - "shape": "matching(type, 'rectangle')", - "value": "matching(number, 12)" - }); - - let result = build_embedded_message_field_value( - &mut message_builder, - &path, - &field_descriptor, - "value", - &config, - &mut matching_rules, - &mut generators, - &hashmap! {}, - ); - - let expected_rules = matchingrules! { + let mut message_builder = MessageBuilder::new(&message_descriptor, "AreaResponse", &FILE_DESCRIPTOR); + let path = DocPath::new("$.value").unwrap(); + let field_descriptor = FieldDescriptorProto { + name: Some("value".to_string()), + number: Some(1), + label: Some(Label::Repeated as i32), + r#type: Some(Type::Message as i32), + type_name: Some(".area_calculator.Area".to_string()), + extendee: None, + default_value: None, + oneof_index: None, + json_name: Some("value".to_string()), + options: None, + proto3_optional: None + }; + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let config = json!({ + "id": "matching(regex, '\\d+', '1234')", + "shape": "matching(type, 'rectangle')", + "value": "matching(number, 12)" + }); + + let result = build_embedded_message_field_value(&mut message_builder, &path, &field_descriptor, + "value", &config, &mut matching_rules, &mut generators, &hashmap!{} + ); + + let expected_rules = matchingrules! { "body" => { "$.value.*.id" => [ pact_models::matchingrules::MatchingRule::Regex("\\d+".to_string()) ], "$.value.*.shape" => [ pact_models::matchingrules::MatchingRule::Type ], "$.value.*.value" => [ pact_models::matchingrules::MatchingRule::Number ] } }.rules_for_category("body").unwrap(); - expect!(result).to(be_ok()); - expect!(matching_rules).to(be_equal_to(expected_rules)); - } + expect!(result).to(be_ok()); + expect!(matching_rules).to(be_equal_to(expected_rules)); + } - pub const DESCRIPTOR_BYTES: &str = - "CrYHCgxjb21tb24ucHJvdG8SD2FyZWFfY2FsY3VsYXRvciL9AwoPTGlzdGVuZXJDb\ + pub const DESCRIPTOR_BYTES: &str = "CrYHCgxjb21tb24ucHJvdG8SD2FyZWFfY2FsY3VsYXRvciL9AwoPTGlzdGVuZXJDb\ 250ZXh0EiMKC2xpc3RlbmVyX2lkGAEgASgDQgIwAVIKbGlzdGVuZXJJZBIaCgh1c2VybmFtZRgCIAEoCVIIdXNlcm5hbW\ USMgoVbGlzdGVuZXJfZGF0ZV9jcmVhdGVkGAMgASgDUhNsaXN0ZW5lckRhdGVDcmVhdGVkEjYKF2ZpbHRlcl9leHBsaWN\ pdF9jb250ZW50GAQgASgIUhVmaWx0ZXJFeHBsaWNpdENvbnRlbnQSGQoIemlwX2NvZGUYBSABKAlSB3ppcENvZGUSIQoMY\ @@ -2823,642 +2180,533 @@ pub(crate) mod tests { V9jYWxjdWxhdG9yLkFyZWFSZXF1ZXN0Gh0uYXJlYV9jYWxjdWxhdG9yLkFyZWFSZXNwb25zZSIAQhxaF2lvLnBhY3QvYXJ\ lYV9jYWxjdWxhdG9y0AIBYgZwcm90bzM="; - #[test_log::test] - fn build_embedded_message_field_value_with_field_from_different_proto_file() { - let bytes = BASE64.decode(DESCRIPTOR_BYTES).unwrap(); - let bytes1 = Bytes::copy_from_slice(bytes.as_slice()); - let fds: FileDescriptorSet = FileDescriptorSet::decode(bytes1).unwrap(); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "ShapeMessage") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "ShapeMessage", main_descriptor); - let path = DocPath::new("$.listener_context").unwrap(); - let field_descriptor = message_descriptor - .field - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "listener_context") - .unwrap(); - let field_config = json!({ - "listener_id": "matching(number, 4)" - }); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = build_embedded_message_field_value( - &mut message_builder, - &path, - field_descriptor, - "listener_context", - &field_config, - &mut matching_rules, - &mut generators, - &file_descriptors, - ); - expect!(result).to(be_ok()); - } - - #[test_log::test] - fn build_single_embedded_field_value_with_field_from_different_proto_file() { - let bytes = BASE64.decode(DESCRIPTOR_BYTES).unwrap(); - let bytes1 = Bytes::copy_from_slice(bytes.as_slice()); - let fds: FileDescriptorSet = FileDescriptorSet::decode(bytes1).unwrap(); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "ShapeMessage") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "ShapeMessage", main_descriptor); - let path = DocPath::new("$.listener_context").unwrap(); - let field_descriptor = message_descriptor - .field - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "listener_context") - .unwrap(); - let field_config = json!({ - "listener_id": "matching(number, 4)" - }); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = build_single_embedded_field_value( - &path, - &mut message_builder, - MessageFieldValueType::Normal, - field_descriptor, - "listener_context", - &field_config, - &mut matching_rules, - &mut generators, - &file_descriptors, - ); - expect!(result).to(be_ok()); - } + #[test_log::test] + fn build_embedded_message_field_value_with_field_from_different_proto_file() { + let bytes = BASE64.decode(DESCRIPTOR_BYTES).unwrap(); + let bytes1 = Bytes::copy_from_slice(bytes.as_slice()); + let fds: FileDescriptorSet = FileDescriptorSet::decode(bytes1).unwrap(); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "ShapeMessage").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "ShapeMessage", main_descriptor); + let path = DocPath::new("$.listener_context").unwrap(); + let field_descriptor = message_descriptor.field.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "listener_context") + .unwrap(); + let field_config = json!({ + "listener_id": "matching(number, 4)" + }); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); - pub(crate) const DESCRIPTOR_WITH_ENUM_BYTES: [u8; 1128] = [ - 10, 229, 8, 10, 21, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, - 112, 114, 111, 116, 111, 18, 15, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, - 111, 114, 34, 186, 2, 10, 12, 83, 104, 97, 112, 101, 77, 101, 115, 115, 97, 103, 101, 18, - 49, 10, 6, 115, 113, 117, 97, 114, 101, 24, 1, 32, 1, 40, 11, 50, 23, 46, 97, 114, 101, 97, - 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 83, 113, 117, 97, 114, 101, 72, 0, - 82, 6, 115, 113, 117, 97, 114, 101, 18, 58, 10, 9, 114, 101, 99, 116, 97, 110, 103, 108, - 101, 24, 2, 32, 1, 40, 11, 50, 26, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, - 116, 111, 114, 46, 82, 101, 99, 116, 97, 110, 103, 108, 101, 72, 0, 82, 9, 114, 101, 99, - 116, 97, 110, 103, 108, 101, 18, 49, 10, 6, 99, 105, 114, 99, 108, 101, 24, 3, 32, 1, 40, - 11, 50, 23, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 67, - 105, 114, 99, 108, 101, 72, 0, 82, 6, 99, 105, 114, 99, 108, 101, 18, 55, 10, 8, 116, 114, - 105, 97, 110, 103, 108, 101, 24, 4, 32, 1, 40, 11, 50, 25, 46, 97, 114, 101, 97, 95, 99, - 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 84, 114, 105, 97, 110, 103, 108, 101, 72, 0, - 82, 8, 116, 114, 105, 97, 110, 103, 108, 101, 18, 70, 10, 13, 112, 97, 114, 97, 108, 108, - 101, 108, 111, 103, 114, 97, 109, 24, 5, 32, 1, 40, 11, 50, 30, 46, 97, 114, 101, 97, 95, - 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 80, 97, 114, 97, 108, 108, 101, 108, 111, - 103, 114, 97, 109, 72, 0, 82, 13, 112, 97, 114, 97, 108, 108, 101, 108, 111, 103, 114, 97, - 109, 66, 7, 10, 5, 115, 104, 97, 112, 101, 34, 41, 10, 6, 83, 113, 117, 97, 114, 101, 18, - 31, 10, 11, 101, 100, 103, 101, 95, 108, 101, 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, - 10, 101, 100, 103, 101, 76, 101, 110, 103, 116, 104, 34, 125, 10, 9, 82, 101, 99, 116, 97, - 110, 103, 108, 101, 18, 22, 10, 6, 108, 101, 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, - 6, 108, 101, 110, 103, 116, 104, 18, 20, 10, 5, 119, 105, 100, 116, 104, 24, 2, 32, 1, 40, - 2, 82, 5, 119, 105, 100, 116, 104, 18, 66, 10, 13, 97, 100, 95, 98, 114, 101, 97, 107, 95, - 116, 121, 112, 101, 24, 5, 32, 1, 40, 14, 50, 30, 46, 97, 114, 101, 97, 95, 99, 97, 108, - 99, 117, 108, 97, 116, 111, 114, 46, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, - 101, 82, 11, 97, 100, 66, 114, 101, 97, 107, 84, 121, 112, 101, 34, 32, 10, 6, 67, 105, - 114, 99, 108, 101, 18, 22, 10, 6, 114, 97, 100, 105, 117, 115, 24, 1, 32, 1, 40, 2, 82, 6, - 114, 97, 100, 105, 117, 115, 34, 79, 10, 8, 84, 114, 105, 97, 110, 103, 108, 101, 18, 21, - 10, 6, 101, 100, 103, 101, 95, 97, 24, 1, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, 65, 18, - 21, 10, 6, 101, 100, 103, 101, 95, 98, 24, 2, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, 66, - 18, 21, 10, 6, 101, 100, 103, 101, 95, 99, 24, 3, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, - 67, 34, 72, 10, 13, 80, 97, 114, 97, 108, 108, 101, 108, 111, 103, 114, 97, 109, 18, 31, - 10, 11, 98, 97, 115, 101, 95, 108, 101, 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, 10, - 98, 97, 115, 101, 76, 101, 110, 103, 116, 104, 18, 22, 10, 6, 104, 101, 105, 103, 104, 116, - 24, 2, 32, 1, 40, 2, 82, 6, 104, 101, 105, 103, 104, 116, 34, 68, 10, 11, 65, 114, 101, 97, - 82, 101, 113, 117, 101, 115, 116, 18, 53, 10, 6, 115, 104, 97, 112, 101, 115, 24, 1, 32, 3, - 40, 11, 50, 29, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, - 83, 104, 97, 112, 101, 77, 101, 115, 115, 97, 103, 101, 82, 6, 115, 104, 97, 112, 101, 115, - 34, 36, 10, 12, 65, 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 18, 20, 10, 5, - 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 2, 82, 5, 118, 97, 108, 117, 101, 42, 85, 10, 13, - 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, 101, 18, 28, 10, 24, 77, 73, 83, 83, - 73, 78, 71, 95, 65, 68, 95, 66, 82, 69, 65, 75, 95, 65, 68, 95, 84, 89, 80, 69, 16, 0, 18, - 18, 10, 14, 65, 85, 68, 73, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 1, 18, 18, 10, 14, - 86, 73, 68, 69, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 2, 50, 173, 1, 10, 10, 67, 97, - 108, 99, 117, 108, 97, 116, 111, 114, 18, 78, 10, 12, 99, 97, 108, 99, 117, 108, 97, 116, - 101, 79, 110, 101, 18, 29, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, - 111, 114, 46, 83, 104, 97, 112, 101, 77, 101, 115, 115, 97, 103, 101, 26, 29, 46, 97, 114, - 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, - 115, 112, 111, 110, 115, 101, 34, 0, 18, 79, 10, 14, 99, 97, 108, 99, 117, 108, 97, 116, - 101, 77, 117, 108, 116, 105, 18, 28, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, - 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, 113, 117, 101, 115, 116, 26, 29, 46, 97, - 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, - 101, 115, 112, 111, 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, 112, 97, 99, 116, - 47, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 208, 2, 1, 98, 6, - 112, 114, 111, 116, 111, 51, - ]; - - #[test_log::test] - fn construct_message_field_with_global_enum_test() { - let bytes: &[u8] = &DESCRIPTOR_WITH_ENUM_BYTES; - let buffer = Bytes::from(bytes); - let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "Rectangle") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "Rectangle", main_descriptor); - let path = DocPath::new("$.rectangle.ad_break_type").unwrap(); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = construct_message_field( - &mut message_builder, - &mut matching_rules, - &mut generators, - "ad_break_type", - &Value::String("AUDIO_AD_BREAK".to_string()), - &path, - &file_descriptors, - ); - expect!(result).to(be_ok()); - - let field = message_builder.fields.get("ad_break_type"); - expect!(field).to(be_some()); - } + let result = build_embedded_message_field_value(&mut message_builder, &path, field_descriptor, + "listener_context", &field_config, &mut matching_rules, &mut generators, &file_descriptors + ); + expect!(result).to(be_ok()); + } - pub(crate) const DESCRIPTOR_WITH_EMBEDDED_MESSAGE: [u8; 644] = [ - 10, 129, 5, 10, 21, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, - 112, 114, 111, 116, 111, 18, 15, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, - 111, 114, 34, 211, 2, 10, 14, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, - 116, 18, 88, 10, 16, 97, 100, 95, 98, 114, 101, 97, 107, 95, 99, 111, 110, 116, 101, 120, - 116, 24, 1, 32, 3, 40, 11, 50, 46, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, - 116, 111, 114, 46, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 46, - 65, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, 82, 14, 97, 100, 66, 114, - 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, 26, 230, 1, 10, 14, 65, 100, 66, 114, 101, - 97, 107, 67, 111, 110, 116, 101, 120, 116, 18, 36, 10, 14, 102, 111, 114, 99, 101, 100, 95, - 108, 105, 110, 101, 95, 105, 100, 24, 1, 32, 1, 40, 9, 82, 12, 102, 111, 114, 99, 101, 100, - 76, 105, 110, 101, 73, 100, 18, 44, 10, 18, 102, 111, 114, 99, 101, 100, 95, 99, 114, 101, - 97, 116, 105, 118, 101, 95, 105, 100, 24, 2, 32, 1, 40, 9, 82, 16, 102, 111, 114, 99, 101, - 100, 67, 114, 101, 97, 116, 105, 118, 101, 73, 100, 18, 30, 10, 11, 97, 100, 95, 98, 114, - 101, 97, 107, 95, 105, 100, 24, 3, 32, 1, 40, 9, 82, 9, 97, 100, 66, 114, 101, 97, 107, 73, - 100, 18, 28, 10, 9, 115, 101, 115, 115, 105, 111, 110, 73, 100, 24, 4, 32, 1, 40, 9, 82, 9, - 115, 101, 115, 115, 105, 111, 110, 73, 100, 18, 66, 10, 13, 97, 100, 95, 98, 114, 101, 97, - 107, 95, 116, 121, 112, 101, 24, 5, 32, 1, 40, 14, 50, 30, 46, 97, 114, 101, 97, 95, 99, - 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, - 121, 112, 101, 82, 11, 97, 100, 66, 114, 101, 97, 107, 84, 121, 112, 101, 34, 36, 10, 12, - 65, 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 18, 20, 10, 5, 118, 97, 108, 117, - 101, 24, 1, 32, 3, 40, 2, 82, 5, 118, 97, 108, 117, 101, 42, 85, 10, 13, 65, 100, 66, 114, - 101, 97, 107, 65, 100, 84, 121, 112, 101, 18, 28, 10, 24, 77, 73, 83, 83, 73, 78, 71, 95, - 65, 68, 95, 66, 82, 69, 65, 75, 95, 65, 68, 95, 84, 89, 80, 69, 16, 0, 18, 18, 10, 14, 65, - 85, 68, 73, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 1, 18, 18, 10, 14, 86, 73, 68, 69, - 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 2, 50, 94, 10, 10, 67, 97, 108, 99, 117, 108, - 97, 116, 111, 114, 18, 80, 10, 12, 99, 97, 108, 99, 117, 108, 97, 116, 101, 79, 110, 101, - 18, 31, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, - 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 26, 29, 46, 97, 114, 101, 97, - 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, 115, 112, - 111, 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, 112, 97, 99, 116, 47, 97, 114, - 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 208, 2, 1, 98, 6, 112, 114, 111, - 116, 111, 51, - ]; - - #[test_log::test] - fn build_single_embedded_field_value_with_embedded_message() { - let bytes: &[u8] = &DESCRIPTOR_WITH_EMBEDDED_MESSAGE; - let buffer = Bytes::from(bytes); - let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "AdBreakRequest") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "AdBreakRequest", main_descriptor); - let path = DocPath::new("$.ad_break_context").unwrap(); - let field_descriptor = message_descriptor - .field - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "ad_break_context") - .unwrap(); - let field_config = json!({ - "ad_break_type": "AUDIO_AD_BREAK" - }); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = build_single_embedded_field_value( - &path, - &mut message_builder, - MessageFieldValueType::Normal, - field_descriptor, - "ad_break_type", - &field_config, - &mut matching_rules, - &mut generators, - &file_descriptors, - ); - expect!(result).to(be_ok()); - } + #[test_log::test] + fn build_single_embedded_field_value_with_field_from_different_proto_file() { + let bytes = BASE64.decode(DESCRIPTOR_BYTES).unwrap(); + let bytes1 = Bytes::copy_from_slice(bytes.as_slice()); + let fds: FileDescriptorSet = FileDescriptorSet::decode(bytes1).unwrap(); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "ShapeMessage").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "ShapeMessage", main_descriptor); + let path = DocPath::new("$.listener_context").unwrap(); + let field_descriptor = message_descriptor.field.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "listener_context") + .unwrap(); + let field_config = json!({ + "listener_id": "matching(number, 4)" + }); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = build_single_embedded_field_value( + &path, &mut message_builder, MessageFieldValueType::Normal, field_descriptor, + "listener_context", &field_config, &mut matching_rules, &mut generators, &file_descriptors); + expect!(result).to(be_ok()); + } - const DESCRIPTORS_ROUTE_GUIDE_WITH_ENUM_BASIC: [u8; 320] = [ - 10, 189, 2, 10, 15, 116, 101, 115, 116, 95, 101, 110, 117, 109, 46, 112, 114, 111, 116, - 111, 18, 13, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, 50, 34, 65, 10, 5, - 80, 111, 105, 110, 116, 18, 26, 10, 8, 108, 97, 116, 105, 116, 117, 100, 101, 24, 1, 32, 1, - 40, 5, 82, 8, 108, 97, 116, 105, 116, 117, 100, 101, 18, 28, 10, 9, 108, 111, 110, 103, - 105, 116, 117, 100, 101, 24, 2, 32, 1, 40, 5, 82, 9, 108, 111, 110, 103, 105, 116, 117, - 100, 101, 34, 58, 10, 7, 70, 101, 97, 116, 117, 114, 101, 18, 47, 10, 6, 114, 101, 115, - 117, 108, 116, 24, 1, 32, 1, 40, 14, 50, 23, 46, 114, 111, 117, 116, 101, 103, 117, 105, - 100, 101, 46, 118, 50, 46, 84, 101, 115, 116, 69, 110, 117, 109, 82, 6, 114, 101, 115, 117, - 108, 116, 42, 56, 10, 8, 84, 101, 115, 116, 69, 110, 117, 109, 18, 14, 10, 10, 86, 65, 76, - 85, 69, 95, 90, 69, 82, 79, 16, 0, 18, 13, 10, 9, 86, 65, 76, 85, 69, 95, 79, 78, 69, 16, - 1, 18, 13, 10, 9, 86, 65, 76, 85, 69, 95, 84, 87, 79, 16, 2, 50, 69, 10, 4, 84, 101, 115, - 116, 18, 61, 10, 11, 71, 101, 116, 70, 101, 97, 116, 117, 114, 101, 50, 18, 20, 46, 114, - 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, 50, 46, 80, 111, 105, 110, 116, 26, - 22, 46, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, 50, 46, 70, 101, 97, - 116, 117, 114, 101, 34, 0, 66, 19, 90, 17, 105, 111, 46, 112, 97, 99, 116, 47, 116, 101, - 115, 116, 95, 101, 110, 117, 109, 98, 6, 112, 114, 111, 116, 111, 51, - ]; - - #[test_log::test] - fn build_field_value_with_global_enum() { - let bytes: &[u8] = &DESCRIPTORS_ROUTE_GUIDE_WITH_ENUM_BASIC; - let buffer = Bytes::from(bytes); - let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "test_enum.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "Feature") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "Feature", main_descriptor); - let path = DocPath::new("$.result").unwrap(); - let field_descriptor = message_descriptor - .field - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "result") - .unwrap(); - let field_config = json!("matching(type, 'VALUE_ONE')"); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = build_field_value( - &path, - &mut message_builder, - MessageFieldValueType::Normal, - field_descriptor, - "result", - &field_config, - &mut matching_rules, - &mut generators, - &file_descriptors, - ); - expect!(result).to(be_ok()); - } + pub(crate) const DESCRIPTOR_WITH_ENUM_BYTES: [u8; 1128] = [ + 10, 229, 8, 10, 21, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, + 112, 114, 111, 116, 111, 18, 15, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, + 111, 114, 34, 186, 2, 10, 12, 83, 104, 97, 112, 101, 77, 101, 115, 115, 97, 103, 101, 18, + 49, 10, 6, 115, 113, 117, 97, 114, 101, 24, 1, 32, 1, 40, 11, 50, 23, 46, 97, 114, 101, 97, + 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 83, 113, 117, 97, 114, 101, 72, 0, 82, + 6, 115, 113, 117, 97, 114, 101, 18, 58, 10, 9, 114, 101, 99, 116, 97, 110, 103, 108, 101, 24, + 2, 32, 1, 40, 11, 50, 26, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, + 114, 46, 82, 101, 99, 116, 97, 110, 103, 108, 101, 72, 0, 82, 9, 114, 101, 99, 116, 97, 110, + 103, 108, 101, 18, 49, 10, 6, 99, 105, 114, 99, 108, 101, 24, 3, 32, 1, 40, 11, 50, 23, 46, + 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 67, 105, 114, 99, + 108, 101, 72, 0, 82, 6, 99, 105, 114, 99, 108, 101, 18, 55, 10, 8, 116, 114, 105, 97, 110, + 103, 108, 101, 24, 4, 32, 1, 40, 11, 50, 25, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, + 108, 97, 116, 111, 114, 46, 84, 114, 105, 97, 110, 103, 108, 101, 72, 0, 82, 8, 116, 114, + 105, 97, 110, 103, 108, 101, 18, 70, 10, 13, 112, 97, 114, 97, 108, 108, 101, 108, 111, 103, + 114, 97, 109, 24, 5, 32, 1, 40, 11, 50, 30, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, + 108, 97, 116, 111, 114, 46, 80, 97, 114, 97, 108, 108, 101, 108, 111, 103, 114, 97, 109, 72, + 0, 82, 13, 112, 97, 114, 97, 108, 108, 101, 108, 111, 103, 114, 97, 109, 66, 7, 10, 5, 115, + 104, 97, 112, 101, 34, 41, 10, 6, 83, 113, 117, 97, 114, 101, 18, 31, 10, 11, 101, 100, 103, + 101, 95, 108, 101, 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, 10, 101, 100, 103, 101, 76, + 101, 110, 103, 116, 104, 34, 125, 10, 9, 82, 101, 99, 116, 97, 110, 103, 108, 101, 18, 22, + 10, 6, 108, 101, 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, 6, 108, 101, 110, 103, 116, + 104, 18, 20, 10, 5, 119, 105, 100, 116, 104, 24, 2, 32, 1, 40, 2, 82, 5, 119, 105, 100, 116, + 104, 18, 66, 10, 13, 97, 100, 95, 98, 114, 101, 97, 107, 95, 116, 121, 112, 101, 24, 5, 32, + 1, 40, 14, 50, 30, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, + 46, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, 101, 82, 11, 97, 100, 66, 114, + 101, 97, 107, 84, 121, 112, 101, 34, 32, 10, 6, 67, 105, 114, 99, 108, 101, 18, 22, 10, 6, + 114, 97, 100, 105, 117, 115, 24, 1, 32, 1, 40, 2, 82, 6, 114, 97, 100, 105, 117, 115, 34, 79, + 10, 8, 84, 114, 105, 97, 110, 103, 108, 101, 18, 21, 10, 6, 101, 100, 103, 101, 95, 97, 24, + 1, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, 65, 18, 21, 10, 6, 101, 100, 103, 101, 95, 98, + 24, 2, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, 66, 18, 21, 10, 6, 101, 100, 103, 101, 95, + 99, 24, 3, 32, 1, 40, 2, 82, 5, 101, 100, 103, 101, 67, 34, 72, 10, 13, 80, 97, 114, 97, + 108, 108, 101, 108, 111, 103, 114, 97, 109, 18, 31, 10, 11, 98, 97, 115, 101, 95, 108, 101, + 110, 103, 116, 104, 24, 1, 32, 1, 40, 2, 82, 10, 98, 97, 115, 101, 76, 101, 110, 103, 116, + 104, 18, 22, 10, 6, 104, 101, 105, 103, 104, 116, 24, 2, 32, 1, 40, 2, 82, 6, 104, 101, 105, + 103, 104, 116, 34, 68, 10, 11, 65, 114, 101, 97, 82, 101, 113, 117, 101, 115, 116, 18, 53, + 10, 6, 115, 104, 97, 112, 101, 115, 24, 1, 32, 3, 40, 11, 50, 29, 46, 97, 114, 101, 97, 95, + 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 83, 104, 97, 112, 101, 77, 101, 115, 115, + 97, 103, 101, 82, 6, 115, 104, 97, 112, 101, 115, 34, 36, 10, 12, 65, 114, 101, 97, 82, 101, + 115, 112, 111, 110, 115, 101, 18, 20, 10, 5, 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 2, 82, + 5, 118, 97, 108, 117, 101, 42, 85, 10, 13, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, + 112, 101, 18, 28, 10, 24, 77, 73, 83, 83, 73, 78, 71, 95, 65, 68, 95, 66, 82, 69, 65, 75, 95, + 65, 68, 95, 84, 89, 80, 69, 16, 0, 18, 18, 10, 14, 65, 85, 68, 73, 79, 95, 65, 68, 95, 66, 82, + 69, 65, 75, 16, 1, 18, 18, 10, 14, 86, 73, 68, 69, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, + 2, 50, 173, 1, 10, 10, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 18, 78, 10, 12, 99, 97, + 108, 99, 117, 108, 97, 116, 101, 79, 110, 101, 18, 29, 46, 97, 114, 101, 97, 95, 99, 97, 108, + 99, 117, 108, 97, 116, 111, 114, 46, 83, 104, 97, 112, 101, 77, 101, 115, 115, 97, 103, 101, + 26, 29, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, + 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 34, 0, 18, 79, 10, 14, 99, 97, 108, 99, 117, + 108, 97, 116, 101, 77, 117, 108, 116, 105, 18, 28, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, + 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, 113, 117, 101, 115, 116, 26, 29, + 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, + 82, 101, 115, 112, 111, 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, 112, 97, 99, 116, + 47, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 208, 2, 1, 98, 6, 112, + 114, 111, 116, 111, 51]; + + #[test_log::test] + fn construct_message_field_with_global_enum_test() { + let bytes: &[u8] = &DESCRIPTOR_WITH_ENUM_BYTES; + let buffer = Bytes::from(bytes); + let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "Rectangle").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "Rectangle", main_descriptor); + let path = DocPath::new("$.rectangle.ad_break_type").unwrap(); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = construct_message_field(&mut message_builder, &mut matching_rules, + &mut generators, "ad_break_type", &Value::String("AUDIO_AD_BREAK".to_string()), + &path, &file_descriptors); + expect!(result).to(be_ok()); + + let field = message_builder.fields.get("ad_break_type"); + expect!(field).to(be_some()); + } - #[test] - fn configuring_request_part_returns_the_config_as_is_if_the_service_part_is_for_the_request() { - let config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(NullValue(0)) } - }; - let result = request_part(&config, "request").unwrap(); - expect!(result).to(be_equal_to(config)); - } + pub(crate) const DESCRIPTOR_WITH_EMBEDDED_MESSAGE: [u8; 644] = [ + 10, 129, 5, 10, 21, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, + 112, 114, 111, 116, 111, 18, 15, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, + 114, 34, 211, 2, 10, 14, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 18, + 88, 10, 16, 97, 100, 95, 98, 114, 101, 97, 107, 95, 99, 111, 110, 116, 101, 120, 116, 24, 1, + 32, 3, 40, 11, 50, 46, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, + 46, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 46, 65, 100, 66, 114, + 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, 82, 14, 97, 100, 66, 114, 101, 97, 107, 67, + 111, 110, 116, 101, 120, 116, 26, 230, 1, 10, 14, 65, 100, 66, 114, 101, 97, 107, 67, 111, + 110, 116, 101, 120, 116, 18, 36, 10, 14, 102, 111, 114, 99, 101, 100, 95, 108, 105, 110, 101, + 95, 105, 100, 24, 1, 32, 1, 40, 9, 82, 12, 102, 111, 114, 99, 101, 100, 76, 105, 110, 101, 73, + 100, 18, 44, 10, 18, 102, 111, 114, 99, 101, 100, 95, 99, 114, 101, 97, 116, 105, 118, 101, 95, + 105, 100, 24, 2, 32, 1, 40, 9, 82, 16, 102, 111, 114, 99, 101, 100, 67, 114, 101, 97, 116, 105, + 118, 101, 73, 100, 18, 30, 10, 11, 97, 100, 95, 98, 114, 101, 97, 107, 95, 105, 100, 24, 3, 32, + 1, 40, 9, 82, 9, 97, 100, 66, 114, 101, 97, 107, 73, 100, 18, 28, 10, 9, 115, 101, 115, 115, + 105, 111, 110, 73, 100, 24, 4, 32, 1, 40, 9, 82, 9, 115, 101, 115, 115, 105, 111, 110, 73, 100, + 18, 66, 10, 13, 97, 100, 95, 98, 114, 101, 97, 107, 95, 116, 121, 112, 101, 24, 5, 32, 1, 40, + 14, 50, 30, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, + 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, 101, 82, 11, 97, 100, 66, 114, 101, 97, 107, + 84, 121, 112, 101, 34, 36, 10, 12, 65, 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 18, + 20, 10, 5, 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 2, 82, 5, 118, 97, 108, 117, 101, 42, 85, + 10, 13, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, 101, 18, 28, 10, 24, 77, 73, 83, + 83, 73, 78, 71, 95, 65, 68, 95, 66, 82, 69, 65, 75, 95, 65, 68, 95, 84, 89, 80, 69, 16, 0, 18, + 18, 10, 14, 65, 85, 68, 73, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 1, 18, 18, 10, 14, 86, + 73, 68, 69, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 2, 50, 94, 10, 10, 67, 97, 108, 99, + 117, 108, 97, 116, 111, 114, 18, 80, 10, 12, 99, 97, 108, 99, 117, 108, 97, 116, 101, 79, 110, + 101, 18, 31, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, + 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 26, 29, 46, 97, 114, 101, 97, 95, + 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, 115, 112, 111, + 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, 112, 97, 99, 116, 47, 97, 114, 101, 97, 95, + 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 208, 2, 1, 98, 6, 112, 114, 111, 116, 111, 51 + ]; + + #[test_log::test] + fn build_single_embedded_field_value_with_embedded_message() { + let bytes: &[u8] = &DESCRIPTOR_WITH_EMBEDDED_MESSAGE; + let buffer = Bytes::from(bytes); + let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "area_calculator.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "AdBreakRequest").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "AdBreakRequest", main_descriptor); + let path = DocPath::new("$.ad_break_context").unwrap(); + let field_descriptor = message_descriptor.field.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "ad_break_context") + .unwrap(); + let field_config = json!({ + "ad_break_type": "AUDIO_AD_BREAK" + }); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = build_single_embedded_field_value( + &path, &mut message_builder, MessageFieldValueType::Normal, field_descriptor, + "ad_break_type", &field_config, &mut matching_rules, &mut generators, &file_descriptors); + expect!(result).to(be_ok()); + } - #[test] - fn configuring_request_part_returns_empty_map_if_there_is_no_request_element() { - let config = btreemap! {}; - let result = request_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(config)); - } + const DESCRIPTORS_ROUTE_GUIDE_WITH_ENUM_BASIC: [u8; 320] = [ + 10, 189, 2, 10, 15, 116, 101, 115, 116, 95, 101, 110, 117, 109, 46, 112, 114, 111, 116, 111, + 18, 13, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, 50, 34, 65, 10, 5, 80, 111, + 105, 110, 116, 18, 26, 10, 8, 108, 97, 116, 105, 116, 117, 100, 101, 24, 1, 32, 1, 40, 5, 82, + 8, 108, 97, 116, 105, 116, 117, 100, 101, 18, 28, 10, 9, 108, 111, 110, 103, 105, 116, 117, + 100, 101, 24, 2, 32, 1, 40, 5, 82, 9, 108, 111, 110, 103, 105, 116, 117, 100, 101, 34, 58, 10, + 7, 70, 101, 97, 116, 117, 114, 101, 18, 47, 10, 6, 114, 101, 115, 117, 108, 116, 24, 1, 32, 1, + 40, 14, 50, 23, 46, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, 50, 46, 84, 101, + 115, 116, 69, 110, 117, 109, 82, 6, 114, 101, 115, 117, 108, 116, 42, 56, 10, 8, 84, 101, 115, + 116, 69, 110, 117, 109, 18, 14, 10, 10, 86, 65, 76, 85, 69, 95, 90, 69, 82, 79, 16, 0, 18, 13, + 10, 9, 86, 65, 76, 85, 69, 95, 79, 78, 69, 16, 1, 18, 13, 10, 9, 86, 65, 76, 85, 69, 95, 84, + 87, 79, 16, 2, 50, 69, 10, 4, 84, 101, 115, 116, 18, 61, 10, 11, 71, 101, 116, 70, 101, 97, + 116, 117, 114, 101, 50, 18, 20, 46, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, 46, 118, + 50, 46, 80, 111, 105, 110, 116, 26, 22, 46, 114, 111, 117, 116, 101, 103, 117, 105, 100, 101, + 46, 118, 50, 46, 70, 101, 97, 116, 117, 114, 101, 34, 0, 66, 19, 90, 17, 105, 111, 46, 112, + 97, 99, 116, 47, 116, 101, 115, 116, 95, 101, 110, 117, 109, 98, 6, 112, 114, 111, 116, 111, + 51 + ]; + + #[test_log::test] + fn build_field_value_with_global_enum() { + let bytes: &[u8] = &DESCRIPTORS_ROUTE_GUIDE_WITH_ENUM_BASIC; + let buffer = Bytes::from(bytes); + let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "test_enum.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "Feature").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "Feature", main_descriptor); + let path = DocPath::new("$.result").unwrap(); + let field_descriptor = message_descriptor.field.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "result") + .unwrap(); + let field_config = json!("matching(type, 'VALUE_ONE')"); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); - #[test] - fn configuring_request_part_returns_an_error_if_request_config_is_not_the_correct_form() { - let config = btreemap! { - "request".to_string() => prost_types::Value { kind: Some(NumberValue(0.0)) } - }; - let result = request_part(&config, ""); - expect!(result).to(be_err()); - } - #[test] - fn configuring_request_part_returns_any_struct_from_the_request_attribute() { - let request_config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let config = btreemap! { - "request".to_string() => prost_types::Value { kind: Some(StructValue(Struct { - fields: request_config.clone() - })) - } - }; - let result = request_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(request_config)); - } + let result = build_field_value(&path, &mut message_builder, + MessageFieldValueType::Normal, field_descriptor, "result", &field_config, + &mut matching_rules, &mut generators, &file_descriptors + ); + expect!(result).to(be_ok()); + } - #[test] - fn configuring_request_part_returns_a_struct_with_a_value_attribute_if_the_request_attribute_is_a_string( - ) { - let request_config = btreemap! { - "value".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let config = btreemap! { - "request".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let result = request_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(request_config)); - } + #[test] + fn configuring_request_part_returns_the_config_as_is_if_the_service_part_is_for_the_request() { + let config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(NullValue(0)) } + }; + let result = request_part(&config, "request").unwrap(); + expect!(result).to(be_equal_to(config)); + } - #[test] - fn configuring_response_part_returns_the_config_as_is_if_the_service_part_is_for_the_response() - { - let config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(NullValue(0)) } - }; - let result = response_part(&config, "response").unwrap(); - expect!(result).to(be_equal_to(vec![(config.clone(), None)])); - } + #[test] + fn configuring_request_part_returns_empty_map_if_there_is_no_request_element() { + let config = btreemap!{}; + let result = request_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(config)); + } - #[test] - fn configuring_response_part_returns_empty_map_if_there_is_no_response_elements() { - let config = btreemap! {}; - let result = response_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(vec![])); - } + #[test] + fn configuring_request_part_returns_an_error_if_request_config_is_not_the_correct_form() { + let config = btreemap!{ + "request".to_string() => prost_types::Value { kind: Some(NumberValue(0.0)) } + }; + let result = request_part(&config, ""); + expect!(result).to(be_err()); + } - #[test] - fn configuring_response_part_ignores_any_config_that_is_not_the_correct_form() { - let config = btreemap! { - "response".to_string() => prost_types::Value { kind: Some(NumberValue(0.0)) } - }; - let result = response_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(vec![])); - } + #[test] + fn configuring_request_part_returns_any_struct_from_the_request_attribute() { + let request_config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let config = btreemap!{ + "request".to_string() => prost_types::Value { kind: Some(StructValue(Struct { + fields: request_config.clone() + })) + } + }; + let result = request_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(request_config)); + } - #[test] - fn configuring_response_part_returns_any_struct_from_the_response_attribute() { - let response_config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let config = btreemap! { - "response".to_string() => prost_types::Value { kind: Some(StructValue(Struct { - fields: response_config.clone() - })) - } - }; - let result = response_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(vec![(response_config, None)])); - } + #[test] + fn configuring_request_part_returns_a_struct_with_a_value_attribute_if_the_request_attribute_is_a_string() { + let request_config = btreemap!{ + "value".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let config = btreemap!{ + "request".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let result = request_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(request_config)); + } - #[test] - fn configuring_response_part_returns_a_struct_with_a_value_attribute_if_the_response_attribute_is_a_string( - ) { - let response_config = btreemap! { - "value".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let config = btreemap! { - "response".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let result = response_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(vec![(response_config, None)])); - } + #[test] + fn configuring_response_part_returns_the_config_as_is_if_the_service_part_is_for_the_response() { + let config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(NullValue(0)) } + }; + let result = response_part(&config, "response").unwrap(); + expect!(result).to(be_equal_to(vec![(config.clone(), None)])); + } - #[test] - fn configuring_response_part_configures_each_item_if_the_response_attribute_is_a_list() { - let response_config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let response_config2 = btreemap! { - "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } - }; - let config = btreemap! { - "response".to_string() => prost_types::Value { - kind: Some(ListValue(prost_types::ListValue { - values: vec![ - prost_types::Value { kind: Some(StructValue(Struct { - fields: response_config.clone() - })) - }, - prost_types::Value { kind: Some(StructValue(Struct { - fields: response_config2.clone() - })) - } - ] - })) - } - }; - let result = response_part(&config, "").unwrap(); - expect!(result).to(be_equal_to(vec![ - (response_config, None), - (response_config2, None), - ])); - } + #[test] + fn configuring_response_part_returns_empty_map_if_there_is_no_response_elements() { + let config = btreemap!{}; + let result = response_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(vec![])); + } - #[test] - fn configuring_response_part_returns_also_returns_metadata_from_the_response_metadata_attribute( - ) { - let response_config = btreemap! { - "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } - }; - let response_metadata_config = btreemap! { - "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } - }; - let config = btreemap! { - "response".to_string() => prost_types::Value { kind: Some(StructValue(Struct { - fields: response_config.clone() - })) - }, - "responseMetadata".to_string() => prost_types::Value { kind: Some(StructValue(Struct { - fields: response_metadata_config.clone() - })) - } - }; - let result = response_part(&config, "").unwrap(); - let expected_metadata = prost_types::Value { - kind: Some(StructValue(Struct { - fields: response_metadata_config.clone(), - })), - }; - expect!(result).to(be_equal_to(vec![( - response_config, - Some(&expected_metadata), - )])); - } + #[test] + fn configuring_response_part_ignores_any_config_that_is_not_the_correct_form() { + let config = btreemap!{ + "response".to_string() => prost_types::Value { kind: Some(NumberValue(0.0)) } + }; + let result = response_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(vec![])); + } - #[test] - fn configuring_response_part_deals_with_the_case_where_there_is_only_metadata() { - let response_metadata_config = btreemap! { - "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } - }; - let config = btreemap! { - "responseMetadata".to_string() => prost_types::Value { kind: Some(StructValue(Struct { - fields: response_metadata_config.clone() - })) - } - }; - let result = response_part(&config, "").unwrap(); - let expected_metadata = prost_types::Value { - kind: Some(StructValue(Struct { - fields: response_metadata_config.clone(), - })), - }; - expect!(result).to(be_equal_to(vec![(btreemap! {}, Some(&expected_metadata))])); - } + #[test] + fn configuring_response_part_returns_any_struct_from_the_response_attribute() { + let response_config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let config = btreemap!{ + "response".to_string() => prost_types::Value { kind: Some(StructValue(Struct { + fields: response_config.clone() + })) + } + }; + let result = response_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(vec![(response_config, None)])); + } - #[test] - fn path_parent() { - let something = DocPath::root().join("something"); - let something_else = something.join("else"); - let something_star = something.join("*"); - let something_escaped = something.join("e l s e"); - let something_escaped2 = something_escaped.join("two"); - let something_star_child = something_star.join("child"); - - expect!(super::parent(&something)).to(be_some().value(DocPath::root())); - expect!(super::parent(&something_else)).to(be_some().value(something.clone())); - expect!(super::parent(&something_star)).to(be_some().value(something.clone())); - expect!(super::parent(&something_escaped)).to(be_some().value(something.clone())); - expect!(super::parent(&something_escaped2)).to(be_some().value(something_escaped.clone())); - expect!(super::parent(&something_star_child)).to(be_some().value(something_star.clone())); - - expect!(super::parent(&DocPath::root())).to(be_none()); - expect!(super::parent(&DocPath::empty())).to(be_none()); - } + #[test] + fn configuring_response_part_returns_a_struct_with_a_value_attribute_if_the_response_attribute_is_a_string() { + let response_config = btreemap!{ + "value".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let config = btreemap!{ + "response".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let result = response_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(vec![(response_config, None)])); + } - const DESCRIPTORS_MAP_WITH_PRIMITIVE_FIELDS: [u8; 334] = [ - 10, 203, 2, 10, 10, 109, 97, 112, 115, 46, 112, 114, 111, 116, 111, 18, 4, 109, 97, 112, - 115, 34, 169, 1, 10, 13, 65, 99, 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 18, - 22, 10, 6, 97, 99, 116, 105, 111, 110, 24, 1, 32, 1, 40, 9, 82, 6, 97, 99, 116, 105, 111, - 110, 18, 52, 10, 5, 112, 97, 114, 97, 109, 24, 2, 32, 3, 40, 11, 50, 30, 46, 109, 97, 112, - 115, 46, 65, 99, 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 46, 80, 97, 114, 97, - 109, 69, 110, 116, 114, 121, 82, 5, 112, 97, 114, 97, 109, 18, 16, 10, 3, 105, 100, 115, - 24, 3, 32, 3, 40, 9, 82, 3, 105, 100, 115, 26, 56, 10, 10, 80, 97, 114, 97, 109, 69, 110, - 116, 114, 121, 18, 16, 10, 3, 107, 101, 121, 24, 1, 32, 1, 40, 9, 82, 3, 107, 101, 121, 18, - 20, 10, 5, 118, 97, 108, 117, 101, 24, 2, 32, 1, 40, 9, 82, 5, 118, 97, 108, 117, 101, 58, - 2, 56, 1, 34, 56, 10, 14, 65, 99, 116, 105, 111, 110, 82, 101, 115, 112, 111, 110, 115, - 101, 18, 38, 10, 14, 114, 101, 115, 112, 111, 110, 115, 101, 83, 116, 97, 116, 117, 115, - 24, 1, 32, 1, 40, 8, 82, 14, 114, 101, 115, 112, 111, 110, 115, 101, 83, 116, 97, 116, 117, - 115, 50, 73, 10, 6, 79, 77, 67, 97, 108, 99, 18, 63, 10, 18, 104, 97, 110, 100, 108, 101, - 66, 97, 116, 99, 104, 82, 101, 113, 117, 101, 115, 116, 18, 19, 46, 109, 97, 112, 115, 46, - 65, 99, 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 26, 20, 46, 109, 97, 112, - 115, 46, 65, 99, 116, 105, 111, 110, 82, 101, 115, 112, 111, 110, 115, 101, 98, 6, 112, - 114, 111, 116, 111, 51, - ]; - - #[test_log::test] - fn configure_message_with_map_with_primitive_fields() { - let bytes: &[u8] = &DESCRIPTORS_MAP_WITH_PRIMITIVE_FIELDS; - let buffer = Bytes::from(bytes); - let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); - dbg!(&fds); - - let main_descriptor = fds - .file - .iter() - .find(|fd| fd.name.clone().unwrap_or_default() == "maps.proto") - .unwrap(); - let message_descriptor = main_descriptor - .message_type - .iter() - .find(|md| md.name.clone().unwrap_or_default() == "ActionRequest") - .unwrap(); - let mut message_builder = - MessageBuilder::new(&message_descriptor, "ActionRequest", main_descriptor); - let path = DocPath::new("$.param").unwrap(); - let mut matching_rules = MatchingRuleCategory::empty("body"); - let mut generators = hashmap! {}; - let file_descriptors: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - - let result = construct_message_field( - &mut message_builder, - &mut matching_rules, - &mut generators, - "param", - &json!({"apply":"Skip_holiday"}), - &path, - &file_descriptors, - ); - - expect!(result).to(be_ok()); - - let constructed = message_builder.fields.get("param").unwrap(); - expect!(constructed.proto_type).to(be_equal_to(Type::Message)); - expect!(constructed.field_type).to(be_equal_to(MessageFieldValueType::Map)); - expect!(&constructed.values).to(be_equal_to(&vec![ - MessageFieldValue { - name: "key".to_string(), - raw_value: Some("apply".to_string()), - rtype: RType::String("apply".to_string()), - }, - MessageFieldValue { - name: "value".to_string(), - raw_value: Some("Skip_holiday".to_string()), - rtype: RType::String("Skip_holiday".to_string()), + #[test] + fn configuring_response_part_configures_each_item_if_the_response_attribute_is_a_list() { + let response_config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let response_config2 = btreemap!{ + "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } + }; + let config = btreemap!{ + "response".to_string() => prost_types::Value { + kind: Some(ListValue(prost_types::ListValue { + values: vec![ + prost_types::Value { kind: Some(StructValue(Struct { + fields: response_config.clone() + })) }, - ])); - } + prost_types::Value { kind: Some(StructValue(Struct { + fields: response_config2.clone() + })) + } + ] + })) + } + }; + let result = response_part(&config, "").unwrap(); + expect!(result).to(be_equal_to(vec![ + (response_config, None), + (response_config2, None) + ])); + } + + #[test] + fn configuring_response_part_returns_also_returns_metadata_from_the_response_metadata_attribute() { + let response_config = btreemap!{ + "A".to_string() => prost_types::Value { kind: Some(StringValue("B".to_string())) } + }; + let response_metadata_config = btreemap!{ + "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } + }; + let config = btreemap!{ + "response".to_string() => prost_types::Value { kind: Some(StructValue(Struct { + fields: response_config.clone() + })) + }, + "responseMetadata".to_string() => prost_types::Value { kind: Some(StructValue(Struct { + fields: response_metadata_config.clone() + })) + } + }; + let result = response_part(&config, "").unwrap(); + let expected_metadata = prost_types::Value { + kind: Some(StructValue(Struct { + fields: response_metadata_config.clone() + })) + }; + expect!(result).to(be_equal_to(vec![(response_config, Some(&expected_metadata))])); + } + + #[test] + fn configuring_response_part_deals_with_the_case_where_there_is_only_metadata() { + let response_metadata_config = btreemap!{ + "C".to_string() => prost_types::Value { kind: Some(StringValue("D".to_string())) } + }; + let config = btreemap!{ + "responseMetadata".to_string() => prost_types::Value { kind: Some(StructValue(Struct { + fields: response_metadata_config.clone() + })) + } + }; + let result = response_part(&config, "").unwrap(); + let expected_metadata = prost_types::Value { + kind: Some(StructValue(Struct { + fields: response_metadata_config.clone() + })) + }; + expect!(result).to(be_equal_to(vec![(btreemap!{}, Some(&expected_metadata))])); + } + + #[test] + fn path_parent() { + let something = DocPath::root().join("something"); + let something_else = something.join("else"); + let something_star = something.join("*"); + let something_escaped = something.join("e l s e"); + let something_escaped2 = something_escaped.join("two"); + let something_star_child = something_star.join("child"); + + expect!(super::parent(&something)).to(be_some().value(DocPath::root())); + expect!(super::parent(&something_else)).to(be_some().value(something.clone())); + expect!(super::parent(&something_star)).to(be_some().value(something.clone())); + expect!(super::parent(&something_escaped)).to(be_some().value(something.clone())); + expect!(super::parent(&something_escaped2)).to(be_some().value(something_escaped.clone())); + expect!(super::parent(&something_star_child)).to(be_some().value(something_star.clone())); + + expect!(super::parent(&DocPath::root())).to(be_none()); + expect!(super::parent(&DocPath::empty())).to(be_none()); + } + + const DESCRIPTORS_MAP_WITH_PRIMITIVE_FIELDS: [u8; 334] = [ + 10, 203, 2, 10, 10, 109, 97, 112, 115, 46, 112, 114, 111, 116, 111, 18, 4, 109, 97, 112, 115, + 34, 169, 1, 10, 13, 65, 99, 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 18, 22, 10, + 6, 97, 99, 116, 105, 111, 110, 24, 1, 32, 1, 40, 9, 82, 6, 97, 99, 116, 105, 111, 110, 18, 52, + 10, 5, 112, 97, 114, 97, 109, 24, 2, 32, 3, 40, 11, 50, 30, 46, 109, 97, 112, 115, 46, 65, 99, + 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 46, 80, 97, 114, 97, 109, 69, 110, 116, + 114, 121, 82, 5, 112, 97, 114, 97, 109, 18, 16, 10, 3, 105, 100, 115, 24, 3, 32, 3, 40, 9, 82, + 3, 105, 100, 115, 26, 56, 10, 10, 80, 97, 114, 97, 109, 69, 110, 116, 114, 121, 18, 16, 10, 3, + 107, 101, 121, 24, 1, 32, 1, 40, 9, 82, 3, 107, 101, 121, 18, 20, 10, 5, 118, 97, 108, 117, 101, + 24, 2, 32, 1, 40, 9, 82, 5, 118, 97, 108, 117, 101, 58, 2, 56, 1, 34, 56, 10, 14, 65, 99, 116, + 105, 111, 110, 82, 101, 115, 112, 111, 110, 115, 101, 18, 38, 10, 14, 114, 101, 115, 112, 111, + 110, 115, 101, 83, 116, 97, 116, 117, 115, 24, 1, 32, 1, 40, 8, 82, 14, 114, 101, 115, 112, 111, + 110, 115, 101, 83, 116, 97, 116, 117, 115, 50, 73, 10, 6, 79, 77, 67, 97, 108, 99, 18, 63, 10, + 18, 104, 97, 110, 100, 108, 101, 66, 97, 116, 99, 104, 82, 101, 113, 117, 101, 115, 116, 18, 19, + 46, 109, 97, 112, 115, 46, 65, 99, 116, 105, 111, 110, 82, 101, 113, 117, 101, 115, 116, 26, 20, + 46, 109, 97, 112, 115, 46, 65, 99, 116, 105, 111, 110, 82, 101, 115, 112, 111, 110, 115, 101, + 98, 6, 112, 114, 111, 116, 111, 51 + ]; + + #[test_log::test] + fn configure_message_with_map_with_primitive_fields() { + let bytes: &[u8] = &DESCRIPTORS_MAP_WITH_PRIMITIVE_FIELDS; + let buffer = Bytes::from(bytes); + let fds: FileDescriptorSet = FileDescriptorSet::decode(buffer).unwrap(); + dbg!(&fds); + + let main_descriptor = fds.file.iter() + .find(|fd| fd.name.clone().unwrap_or_default() == "maps.proto") + .unwrap(); + let message_descriptor = main_descriptor.message_type.iter() + .find(|md| md.name.clone().unwrap_or_default() == "ActionRequest").unwrap(); + let mut message_builder = MessageBuilder::new(&message_descriptor, "ActionRequest", main_descriptor); + let path = DocPath::new("$.param").unwrap(); + let mut matching_rules = MatchingRuleCategory::empty("body"); + let mut generators = hashmap!{}; + let file_descriptors: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let result = construct_message_field( + &mut message_builder, + &mut matching_rules, + &mut generators, + "param", + &json!({"apply":"Skip_holiday"}), + &path, + &file_descriptors + ); + + expect!(result).to(be_ok()); + + let constructed = message_builder.fields.get("param").unwrap(); + expect!(constructed.proto_type).to(be_equal_to(Type::Message)); + expect!(constructed.field_type).to(be_equal_to(MessageFieldValueType::Map)); + expect!(&constructed.values).to(be_equal_to( + &vec![ + MessageFieldValue { name: "key".to_string(), raw_value: Some("apply".to_string()), rtype: RType::String("apply".to_string()) }, + MessageFieldValue { name: "value".to_string(), raw_value: Some("Skip_holiday".to_string()), rtype: RType::String("Skip_holiday".to_string()) } + ] + )); + } - #[test] - fn find_message_descriptor_test() { - let descriptors = "CpAEChdpbXBvcnRlZC9pbXBvcnRlZC5wcm90bxIIaW1wb3J0ZWQiOQoJUmVjdGFuZ2x\ + #[test] + fn find_message_descriptor_test() { + let descriptors = "CpAEChdpbXBvcnRlZC9pbXBvcnRlZC5wcm90bxIIaW1wb3J0ZWQiOQoJUmVjdGFuZ2x\ lEhQKBXdpZHRoGAEgASgFUgV3aWR0aBIWCgZsZW5ndGgYAiABKAVSBmxlbmd0aCJIChhSZWN0YW5nbGVMb2NhdGlvblJ\ lcXVlc3QSFAoFd2lkdGgYASABKAVSBXdpZHRoEhYKBmxlbmd0aBgCIAEoBVIGbGVuZ3RoIkgKGVJlY3RhbmdsZUxvY2F0\ aW9uUmVzcG9uc2USKwoIbG9jYXRpb24YASABKAsyDy5pbXBvcnRlZC5Qb2ludFIIbG9jYXRpb24iQQoFUG9pbnQSGgoIb\ @@ -3474,43 +2722,21 @@ pub(crate) mod tests { GVMb2NhdGlvblJlcXVlc3QaIi5wcmltYXJ5LlJlY3RhbmdsZUxvY2F0aW9uUmVzcG9uc2UiAEJnChhpby5ncnBjLmV4YW1\ wbGVzLnByaW1hcnlCDFByaW1hcnlQcm90b1ABWjtnaXRodWIuY29tL3BhY3QtZm91bmRhdGlvbi9wYWN0LWdvL3YyL2V4Y\ W1wbGVzL2dycGMvcHJpbWFyeWIGcHJvdG8z"; - let decoded = BASE64.decode(descriptors).unwrap(); - let bytes = Bytes::copy_from_slice(decoded.as_slice()); - let fds = FileDescriptorSet::decode(bytes).unwrap(); - let all: HashMap = fds - .file - .iter() - .map(|des| (des.name.clone().unwrap_or_default(), des)) - .collect(); - let file_descriptor = &fds.file[0]; - - let result = - super::find_message_descriptor("RectangleLocationRequest", None, file_descriptor, &all) - .unwrap(); - expect!(result.field.len()).to(be_equal_to(2)); - let result = super::find_message_descriptor( - "RectangleLocationRequest", - Some("primary"), - file_descriptor, - &all, - ) - .unwrap(); - expect!(result.field.len()).to(be_equal_to(4)); - let result = super::find_message_descriptor( - "RectangleLocationRequest", - Some(".primary"), - file_descriptor, - &all, - ) - .unwrap(); - expect!(result.field.len()).to(be_equal_to(4)); - let result = super::find_message_descriptor( - "RectangleLocationRequest", - Some("imported"), - file_descriptor, - &all, - ) - .unwrap(); - expect!(result.field.len()).to(be_equal_to(2)); - } + let decoded = BASE64.decode(descriptors).unwrap(); + let bytes = Bytes::copy_from_slice(decoded.as_slice()); + let fds = FileDescriptorSet::decode(bytes).unwrap(); + let all: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + let file_descriptor = &fds.file[0]; + + let result = super::find_message_descriptor("RectangleLocationRequest", None, file_descriptor, &all).unwrap(); + expect!(result.field.len()).to(be_equal_to(2)); + let result = super::find_message_descriptor("RectangleLocationRequest", Some("primary"), file_descriptor, &all).unwrap(); + expect!(result.field.len()).to(be_equal_to(4)); + let result = super::find_message_descriptor("RectangleLocationRequest", Some(".primary"), file_descriptor, &all).unwrap(); + expect!(result.field.len()).to(be_equal_to(4)); + let result = super::find_message_descriptor("RectangleLocationRequest", Some("imported"), file_descriptor, &all).unwrap(); + expect!(result.field.len()).to(be_equal_to(2)); + } } diff --git a/src/utils.rs b/src/utils.rs index 127f560..dbefc01 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,8 +5,8 @@ use std::fmt::Write; use std::panic::RefUnwindSafe; use anyhow::anyhow; -use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; use bytes::{Bytes, BytesMut}; use field_descriptor_proto::Type; use maplit::hashmap; @@ -15,61 +15,71 @@ use pact_models::pact::load_pact_from_json; use pact_models::prelude::v4::V4Pact; use pact_models::v4::interaction::V4Interaction; use prost::Message; -use prost_types::field_descriptor_proto::Label; -use prost_types::value::Kind; use prost_types::{ - field_descriptor_proto, DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, - FileDescriptorProto, FileDescriptorSet, MethodDescriptorProto, ServiceDescriptorProto, Value, + DescriptorProto, + EnumDescriptorProto, + field_descriptor_proto, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, + MethodDescriptorProto, + ServiceDescriptorProto, + Value }; +use prost_types::field_descriptor_proto::Label; +use prost_types::value::Kind; use serde_json::json; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, error, trace, warn}; use crate::message_decoder::{decode_message, ProtobufField, ProtobufFieldData}; /// Return the last name in a dot separated string pub fn last_name(entry_type_name: &str) -> &str { - entry_type_name.split('.').last().unwrap_or(entry_type_name) + entry_type_name.split('.').last().unwrap_or(entry_type_name) } /// Split a dot-seperated string into the package and name part pub fn split_name(name: &str) -> (&str, Option<&str>) { - name.rsplit_once('.') - .map(|(package, name)| { - if package.is_empty() { - (name, None) - } else { - if let Some(trimmed) = package.strip_prefix(".") { - (name, Some(trimmed)) - } else { - (name, Some(package)) - } - } - }) - .unwrap_or_else(|| (name, None)) + name.rsplit_once('.') + .map(|(package, name)| { + if package.is_empty() { + (name, None) + } else { + if let Some(trimmed) = package.strip_prefix(".") { + (name, Some(trimmed)) + } else { + (name, Some(package)) + } + } + }) + .unwrap_or_else(|| (name, None)) } /// Search for a message by type name in all the file descriptors pub fn find_message_type_by_name( - message_name: &str, - descriptors: &FileDescriptorSet, + message_name: &str, + descriptors: &FileDescriptorSet ) -> anyhow::Result<(DescriptorProto, FileDescriptorProto)> { - descriptors - .file - .iter() - .map(|descriptor| { - find_message_type_in_file_descriptor(message_name, descriptor) - .map(|ds| (ds, descriptor)) - .ok() - }) - .find(|result| result.is_some()) - .flatten() - .map(|(m, f)| (m, f.clone())) - .ok_or_else(|| { - anyhow!( - "Did not find a message type '{}' in the descriptors", - message_name - ) - }) + descriptors.file.iter() + .map(|descriptor| { + find_message_type_in_file_descriptor(message_name, descriptor).map(|ds| (ds, descriptor)).ok() + }) + .find(|result| result.is_some()) + .flatten() + .map(|(m, f)| (m, f.clone())) + .ok_or_else(|| anyhow!("Did not find a message type '{}' in the descriptors", message_name)) +} + +/// Search for a message by type name in the file descriptor +pub fn find_message_type_in_file_descriptor( + message_name: &str, + descriptor: &FileDescriptorProto +) -> anyhow::Result { + descriptor.message_type.iter() + .find(|message| message.name.clone().unwrap_or_default() == message_name) + .cloned() + .ok_or_else(|| anyhow!("Did not find a message type '{}' in the file descriptor '{}'", + message_name, descriptor.name.as_deref().unwrap_or("unknown"))) } /// Very similar to find_message_type_by_name, but it narrows down a list of file descriptors to the ones where package @@ -83,7 +93,7 @@ pub fn find_message_descriptor( let package = package .filter(|p| !p.is_empty()) .unwrap_or(file_descriptor.package.as_deref().unwrap_or_default()); - info!( + debug!( "Looking for message '{}' in package '{}'", message_name, package ); @@ -100,25 +110,6 @@ pub fn find_message_descriptor( }) } -/// Search for a message by type name in the file descriptor -pub fn find_message_type_in_file_descriptor( - message_name: &str, - descriptor: &FileDescriptorProto, -) -> anyhow::Result { - descriptor - .message_type - .iter() - .find(|message| message.name.clone().unwrap_or_default() == message_name) - .cloned() - .ok_or_else(|| { - anyhow!( - "Did not find a message type '{}' in the file descriptor '{}'", - message_name, - descriptor.name.as_deref().unwrap_or("unknown") - ) - }) -} - fn find_all_file_descriptors_for_package<'a>( package: &str, all_descriptors: &'a HashMap, @@ -128,11 +119,11 @@ fn find_all_file_descriptors_for_package<'a>( } else { package }; - info!("Looking for file descriptors for package '{}'", package); + debug!("Looking for file descriptors for package '{}'", package); let found = all_descriptors .values() .filter(|descriptor| { - info!( + debug!( "Checking file descriptor '{}' with package '{}'", descriptor.name.as_deref().unwrap_or("unknown"), descriptor.package.as_deref().unwrap_or("unknown") @@ -151,7 +142,7 @@ fn find_all_file_descriptors_for_package<'a>( package )) } else { - info!( + debug!( "Found {} file descriptors for package '{}'", found.len(), package @@ -163,893 +154,798 @@ fn find_all_file_descriptors_for_package<'a>( /// If the field is a map field. A field will be a map field if it is a repeated field, the field /// type is a message and the nested type has the map flag set on the message options. pub fn is_map_field(message_descriptor: &DescriptorProto, field: &FieldDescriptorProto) -> bool { - if field.label() == Label::Repeated && field.r#type() == Type::Message { - match find_nested_type(message_descriptor, field) { - Some(nested) => match nested.options { - None => false, - Some(options) => options.map_entry.unwrap_or(false), - }, - None => false, - } - } else { - false + if field.label() == Label::Repeated && field.r#type() == Type::Message { + match find_nested_type(message_descriptor, field) { + Some(nested) => match nested.options { + None => false, + Some(options) => options.map_entry.unwrap_or(false) + }, + None => false } + } else { + false + } } /// Returns the nested descriptor for this field. -pub fn find_nested_type( - message_descriptor: &DescriptorProto, - field: &FieldDescriptorProto, -) -> Option { - trace!( - ">> find_nested_type({:?}, {:?}, {:?}, {:?})", - message_descriptor.name, - field.name, - field.r#type(), - field.type_name - ); - if field.r#type() == Type::Message { - let type_name = field.type_name.clone().unwrap_or_default(); - let message_type = last_name(type_name.as_str()); - trace!( - "find_nested_type: Looking for nested type '{}'", - message_type - ); - message_descriptor - .nested_type - .iter() - .find(|nested| { - trace!("find_nested_type: type = '{:?}'", nested.name); - nested.name.clone().unwrap_or_default() == message_type - }) - .cloned() - } else { - None - } +pub fn find_nested_type(message_descriptor: &DescriptorProto, field: &FieldDescriptorProto) -> Option { + trace!(">> find_nested_type({:?}, {:?}, {:?}, {:?})", message_descriptor.name, field.name, field.r#type(), field.type_name); + if field.r#type() == Type::Message { + let type_name = field.type_name.clone().unwrap_or_default(); + let message_type = last_name(type_name.as_str()); + trace!("find_nested_type: Looking for nested type '{}'", message_type); + message_descriptor.nested_type.iter().find(|nested| { + trace!("find_nested_type: type = '{:?}'", nested.name); + nested.name.clone().unwrap_or_default() == message_type + }).cloned() + } else { + None + } } /// Return the hexadecimal representation for the bytes pub(crate) fn as_hex(data: &[u8]) -> String { - let mut buffer = String::with_capacity(data.len() * 2); + let mut buffer = String::with_capacity(data.len() * 2); - for b in data { - let _ = write!(&mut buffer, "{:02x}", b); - } + for b in data { + let _ = write!(&mut buffer, "{:02x}", b); + } - buffer + buffer } /// Create a string from the byte array for rendering/displaying pub(crate) fn display_bytes(data: &[u8]) -> String { - if data.len() <= 16 { - as_hex(data) - } else { - format!("{}... ({} bytes)", as_hex(&data[0..16]), data.len()) - } + if data.len() <= 16 { + as_hex(data) + } else { + format!("{}... ({} bytes)", as_hex(&data[0..16]), data.len()) + } } /// Look for the message field data with the given name -pub fn find_message_field_by_name( - descriptor: &DescriptorProto, - field_data: Vec, - field_name: &str, -) -> Option { - let field_num = match descriptor - .field - .iter() - .find(|f| f.name.clone().unwrap_or_default() == field_name) - .map(|f| f.number.unwrap_or(-1)) - { - Some(n) => n, - None => return None, - }; - - field_data - .iter() - .find(|d| d.field_num == field_num as u32) - .cloned() +pub fn find_message_field_by_name(descriptor: &DescriptorProto, field_data: Vec, field_name: &str) -> Option { + let field_num = match descriptor.field.iter() + .find(|f| f.name.clone().unwrap_or_default() == field_name) + .map(|f| f.number.unwrap_or(-1)) { + Some(n) => n, + None => return None + }; + + field_data.iter().find(|d| d.field_num == field_num as u32).cloned() } /// If the field is a repeated field pub fn is_repeated_field(descriptor: &FieldDescriptorProto) -> bool { - descriptor.label() == Label::Repeated + descriptor.label() == Label::Repeated } /// Get the name of the enum value pub fn enum_name(enum_value: i32, descriptor: &EnumDescriptorProto) -> String { - descriptor - .value - .iter() - .find(|v| v.number.unwrap_or(-1) == enum_value) - .map(|v| { - v.name - .clone() - .unwrap_or_else(|| format!("enum {}", enum_value)) - }) - .unwrap_or_else(|| format!("Unknown enum {}", enum_value)) + descriptor.value.iter().find(|v| v.number.unwrap_or(-1) == enum_value) + .map(|v| v.name.clone().unwrap_or_else(|| format!("enum {}", enum_value))) + .unwrap_or_else(|| format!("Unknown enum {}", enum_value)) } /// Find the integer value of the given enum type and name in the message descriptor. #[tracing::instrument(ret, skip_all, fields(%enum_name, %enum_value))] pub fn find_enum_value_by_name_in_message( - enum_types: &[EnumDescriptorProto], - enum_name: &str, - enum_value: &str, + enum_types: &[EnumDescriptorProto], + enum_name: &str, + enum_value: &str ) -> Option<(i32, EnumDescriptorProto)> { - trace!( - ">> find_enum_value_by_name_in_message({}, {})", - enum_name, - enum_value - ); - enum_types.iter().find_map(|enum_descriptor| { - trace!( - "find_enum_value_by_name_in_message: enum type = {:?}", - enum_descriptor.name - ); - if let Some(name) = &enum_descriptor.name { - if name == last_name(enum_name) { - enum_descriptor.value.iter().find_map(|val| { - if let Some(name) = &val.name { - if name == enum_value { - val.number.map(|n| (n, enum_descriptor.clone())) - } else { - None - } - } else { - None - } - }) - } else { + trace!(">> find_enum_value_by_name_in_message({}, {})",enum_name, enum_value); + enum_types.iter() + .find_map(|enum_descriptor| { + trace!("find_enum_value_by_name_in_message: enum type = {:?}", enum_descriptor.name); + if let Some(name) = &enum_descriptor.name { + if name == last_name(enum_name) { + enum_descriptor.value.iter().find_map(|val| { + if let Some(name) = &val.name { + if name == enum_value { + val.number.map(|n| (n, enum_descriptor.clone())) + } else { None + } + } else { + None } + }) } else { - None + None } + } else { + None + } }) } /// Find the enum type by name in the message descriptor. #[tracing::instrument(ret, skip_all, fields(%enum_name))] pub fn find_enum_by_name_in_message( - enum_types: &[EnumDescriptorProto], - enum_name: &str, + enum_types: &[EnumDescriptorProto], + enum_name: &str ) -> Option { - trace!(">> find_enum_by_name_in_message({})", enum_name); - enum_types.iter().find_map(|enum_descriptor| { - trace!( - "find_enum_by_name_in_message: enum type = {:?}", - enum_descriptor.name - ); - if let Some(name) = &enum_descriptor.name { - if name == last_name(enum_name) { - Some(enum_descriptor.clone()) - } else { - None - } + trace!(">> find_enum_by_name_in_message({})",enum_name); + enum_types.iter() + .find_map(|enum_descriptor| { + trace!("find_enum_by_name_in_message: enum type = {:?}", enum_descriptor.name); + if let Some(name) = &enum_descriptor.name { + if name == last_name(enum_name) { + Some(enum_descriptor.clone()) } else { - None + None } + } else { + None + } }) } /// Find the integer value of the given enum type and name in all the descriptors. #[tracing::instrument(ret, skip_all, fields(%enum_name, %enum_value))] pub fn find_enum_value_by_name( - descriptors: &HashMap, - enum_name: &str, - enum_value: &str, + descriptors: &HashMap, + enum_name: &str, + enum_value: &str ) -> Option<(i32, EnumDescriptorProto)> { - trace!(">> find_enum_value_by_name({}, {})", enum_name, enum_value); - let enum_name_full = enum_name - .split('.') - .filter(|v| !v.is_empty()) - .collect::>() - .join("."); - let result = descriptors.values().find_map(|fd| { - let package = fd.package.clone().unwrap_or_default(); - if enum_name_full.starts_with(&package) { + trace!(">> find_enum_value_by_name({}, {})", enum_name, enum_value); + let enum_name_full = enum_name.split('.').filter(|v| !v.is_empty()).collect::>().join("."); + let result = descriptors.values() + .find_map(|fd| { + let package = fd.package.clone().unwrap_or_default(); + if enum_name_full.starts_with(&package) { let enum_name_short = enum_name_full.replace(&package, ""); - let enum_name_parts = enum_name_short - .split('.') - .filter(|v| !v.is_empty()) - .collect::>(); + let enum_name_parts = enum_name_short.split('.').filter(|v| !v.is_empty()).collect::>(); if let Some((_name, message_name)) = enum_name_parts.split_last() { - if message_name.is_empty() { - find_enum_value_by_name_in_message(&fd.enum_type, enum_name, enum_value) + if message_name.is_empty() { + find_enum_value_by_name_in_message(&fd.enum_type, enum_name, enum_value) + } else { + let message_name = message_name.join("."); + if let Ok(message_descriptor) = find_message_type_in_file_descriptor(&message_name, fd) { + find_enum_value_by_name_in_message(&message_descriptor.enum_type, enum_name, enum_value) } else { - let message_name = message_name.join("."); - if let Ok(message_descriptor) = - find_message_type_in_file_descriptor(&message_name, fd) - { - find_enum_value_by_name_in_message( - &message_descriptor.enum_type, - enum_name, - enum_value, - ) - } else { - None - } + None } + } } else { - None + None } - } else { + } else { None - } - }); - if result.is_some() { - result - } else { - None - } + } + }); + if result.is_some() { + result + } else { + None + } } /// Find the given enum type by name in all the descriptors. #[tracing::instrument(ret, skip_all, fields(%enum_name))] pub fn find_enum_by_name( - descriptors: &FileDescriptorSet, - enum_name: &str, + descriptors: &FileDescriptorSet, + enum_name: &str ) -> Option { - trace!(">> find_enum_by_name({})", enum_name); - let enum_name_full = enum_name - .split('.') - .filter(|v| !v.is_empty()) - .collect::>() - .join("."); - let result = descriptors.file.iter().find_map(|fd| { - let package = fd.package.clone().unwrap_or_default(); - if enum_name_full.starts_with(&package) { + trace!(">> find_enum_by_name({})", enum_name); + let enum_name_full = enum_name.split('.').filter(|v| !v.is_empty()).collect::>().join("."); + let result = descriptors.file.iter() + .find_map(|fd| { + let package = fd.package.clone().unwrap_or_default(); + if enum_name_full.starts_with(&package) { let enum_name_short = enum_name_full.replace(&package, ""); - let enum_name_parts = enum_name_short - .split('.') - .filter(|v| !v.is_empty()) - .collect::>(); + let enum_name_parts = enum_name_short.split('.').filter(|v| !v.is_empty()).collect::>(); if let Some((_name, message_name)) = enum_name_parts.split_last() { - if message_name.is_empty() { - find_enum_by_name_in_message(&fd.enum_type, enum_name) + if message_name.is_empty() { + find_enum_by_name_in_message(&fd.enum_type, enum_name) + } else { + let message_name = message_name.join("."); + if let Ok(message_descriptor) = find_message_type_in_file_descriptor(&message_name, fd) { + find_enum_by_name_in_message(&message_descriptor.enum_type, enum_name) } else { - let message_name = message_name.join("."); - if let Ok(message_descriptor) = - find_message_type_in_file_descriptor(&message_name, fd) - { - find_enum_by_name_in_message(&message_descriptor.enum_type, enum_name) - } else { - None - } + None } + } } else { - None + None } - } else { + } else { None - } - }); - if result.is_some() { - result - } else { - None - } + } + }); + if result.is_some() { + result + } else { + None + } } /// Convert the message field data into a JSON value pub fn field_data_to_json( - field_data: Vec, - descriptor: &DescriptorProto, - descriptors: &FileDescriptorSet, + field_data: Vec, + descriptor: &DescriptorProto, + descriptors: &FileDescriptorSet ) -> anyhow::Result { - let mut object = hashmap! {}; - - for field in field_data { - if let Some(value) = descriptor - .field - .iter() - .find(|f| f.number.unwrap_or(-1) as u32 == field.field_num) - { - match &value.name { - Some(name) => { - object.insert( - name.clone(), - match &field.data { - ProtobufFieldData::String(s) => serde_json::Value::String(s.clone()), - ProtobufFieldData::Boolean(b) => serde_json::Value::Bool(*b), - ProtobufFieldData::UInteger32(n) => json!(n), - ProtobufFieldData::Integer32(n) => json!(n), - ProtobufFieldData::UInteger64(n) => json!(n), - ProtobufFieldData::Integer64(n) => json!(n), - ProtobufFieldData::Float(n) => json!(n), - ProtobufFieldData::Double(n) => json!(n), - ProtobufFieldData::Bytes(b) => { - serde_json::Value::Array(b.iter().map(|v| json!(v)).collect()) - } - ProtobufFieldData::Enum(n, descriptor) => { - serde_json::Value::String(enum_name(*n, descriptor)) - } - ProtobufFieldData::Message(b, descriptor) => { - let mut bytes = BytesMut::from(b.as_slice()); - let message_data = - decode_message(&mut bytes, descriptor, descriptors)?; - field_data_to_json(message_data, descriptor, descriptors)? - } - ProtobufFieldData::Unknown(b) => { - serde_json::Value::Array(b.iter().map(|v| json!(v)).collect()) - } - }, - ); - } - None => warn!( - "Did not get the field name for field number {}", - field.field_num - ), + let mut object = hashmap!{}; + + for field in field_data { + if let Some(value) = descriptor.field.iter().find(|f| f.number.unwrap_or(-1) as u32 == field.field_num) { + match &value.name { + Some(name) => { + object.insert(name.clone(), match &field.data { + ProtobufFieldData::String(s) => serde_json::Value::String(s.clone()), + ProtobufFieldData::Boolean(b) => serde_json::Value::Bool(*b), + ProtobufFieldData::UInteger32(n) => json!(n), + ProtobufFieldData::Integer32(n) => json!(n), + ProtobufFieldData::UInteger64(n) => json!(n), + ProtobufFieldData::Integer64(n) => json!(n), + ProtobufFieldData::Float(n) => json!(n), + ProtobufFieldData::Double(n) => json!(n), + ProtobufFieldData::Bytes(b) => serde_json::Value::Array(b.iter().map(|v| json!(v)).collect()), + ProtobufFieldData::Enum(n, descriptor) => serde_json::Value::String(enum_name(*n, descriptor)), + ProtobufFieldData::Message(b, descriptor) => { + let mut bytes = BytesMut::from(b.as_slice()); + let message_data = decode_message(&mut bytes, descriptor, descriptors)?; + field_data_to_json(message_data, descriptor, descriptors)? } - } else { - warn!( - "Did not find the descriptor for field number {}", - field.field_num - ); + ProtobufFieldData::Unknown(b) => serde_json::Value::Array(b.iter().map(|v| json!(v)).collect()) + }); } + None => warn!("Did not get the field name for field number {}", field.field_num) + } + } else { + warn!("Did not find the descriptor for field number {}", field.field_num); } + } - Ok(serde_json::Value::Object( - object.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), - )) + Ok(serde_json::Value::Object(object.iter().map(|(k, v)| (k.clone(), v.clone())).collect())) } /// Parse the JSON string into a V4 Pact model -pub(crate) fn parse_pact_from_request_json( - pact_json: &str, - source: &str, -) -> anyhow::Result { - // Parse the Pact JSON string into a JSON struct - let json: serde_json::Value = match serde_json::from_str(pact_json) { - Ok(json) => json, - Err(err) => { - error!("Failed to parse Pact JSON: {}", err); - return Err(anyhow!("Failed to parse Pact JSON: {}", err)); - } - }; - - // Load the Pact model from the JSON - match load_pact_from_json(source, &json) { - Ok(pact) => match pact.as_v4_pact() { - Ok(pact) => Ok(pact), - Err(err) => { - error!("Failed to parse Pact JSON, not a V4 Pact: {}", err); - Err(anyhow!("Failed to parse Pact JSON, not a V4 Pact: {}", err)) - } - }, - Err(err) => { - error!("Failed to parse Pact JSON to a V4 Pact: {}", err); - Err(anyhow!("Failed to parse Pact JSON: {}", err)) - } +pub(crate) fn parse_pact_from_request_json(pact_json: &str, source: &str) -> anyhow::Result { + // Parse the Pact JSON string into a JSON struct + let json: serde_json::Value = match serde_json::from_str(pact_json) { + Ok(json) => json, + Err(err) => { + error!("Failed to parse Pact JSON: {}", err); + return Err(anyhow!("Failed to parse Pact JSON: {}", err)); } + }; + + // Load the Pact model from the JSON + match load_pact_from_json(source, &json) { + Ok(pact) => match pact.as_v4_pact() { + Ok(pact) => Ok(pact), + Err(err) => { + error!("Failed to parse Pact JSON, not a V4 Pact: {}", err); + Err(anyhow!("Failed to parse Pact JSON, not a V4 Pact: {}", err)) + } + }, + Err(err) => { + error!("Failed to parse Pact JSON to a V4 Pact: {}", err); + Err(anyhow!("Failed to parse Pact JSON: {}", err)) + } + } } /// Lookup up the interaction in the Pact file, given the ID pub fn lookup_interaction_by_id<'a>( - interaction_key: &str, - pact: &'a V4Pact, + interaction_key: &str, + pact: &'a V4Pact ) -> Option<&'a (dyn V4Interaction + Send + Sync + RefUnwindSafe)> { - pact.interactions - .iter() - .find(|i| { - trace!( - interaction_key, - unique_key = i.unique_key(), - "Checking interaction for key" - ); - i.unique_key() == interaction_key - }) - .map(|i| i.as_ref()) + pact.interactions.iter() + .find(|i| { + trace!(interaction_key, unique_key=i.unique_key(), "Checking interaction for key"); + i.unique_key() == interaction_key + }) + .map(|i| i.as_ref()) } -pub fn lookup_interaction_config( - interaction: &dyn V4Interaction, -) -> Option> { - interaction.plugin_config().iter().find_map(|(key, value)| { - if key.as_str() == "protobuf" { - Some(value.clone()) - } else { - None - } +pub fn lookup_interaction_config(interaction: &dyn V4Interaction) -> Option> { + interaction.plugin_config().iter() + .find_map(|(key, value)| { + if key.as_str() == "protobuf" { + Some(value.clone()) + } else { + None + } }) } /// Returns the service descriptors for the given interaction pub(crate) fn lookup_service_descriptors_for_interaction( - interaction: &dyn V4Interaction, - pact: &V4Pact, -) -> anyhow::Result<( - FileDescriptorSet, - ServiceDescriptorProto, - MethodDescriptorProto, - String, -)> { - let interaction_config = lookup_interaction_config(interaction) - .ok_or_else(|| anyhow!("Interaction does not have any Protobuf configuration"))?; - let descriptor_key = interaction_config - .get("descriptorKey") - .map(json_to_string) - .ok_or_else(|| anyhow!("Interaction descriptorKey was missing in Pact file"))?; - let service = interaction_config - .get("service") - .map(json_to_string) - .ok_or_else(|| anyhow!("Interaction gRPC service was missing in Pact file"))?; - let (service_name, method_name) = service.split_once('/').ok_or_else(|| { - anyhow!( - "Service name '{}' is not valid, it should be of the form /", - service - ) - })?; - - let plugin_config = pact - .plugin_data - .iter() - .find(|data| data.name == "protobuf") - .map(|data| &data.configuration) - .ok_or_else(|| anyhow!("Did not find any Protobuf configuration in the Pact file"))? - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - let descriptors = get_descriptors_for_interaction(descriptor_key.as_str(), &plugin_config)?; - let (file_descriptor, service_descriptor) = - find_service_descriptor(&descriptors, service_name)?; - let method_descriptor = service_descriptor - .method - .iter() - .find(|method_desc| method_desc.name.clone().unwrap_or_default() == method_name) - .ok_or_else(|| { - anyhow!( - "Did not find the method {} in the Protobuf file descriptor for service '{}'", - method_name, - service - ) - })?; - - let package = file_descriptor.package.clone(); - Ok(( - descriptors.clone(), - service_descriptor.clone(), - method_descriptor.clone(), - package.unwrap_or_default(), - )) + interaction: &dyn V4Interaction, + pact: &V4Pact +) -> anyhow::Result<(FileDescriptorSet, ServiceDescriptorProto, MethodDescriptorProto, String)> { + let interaction_config = lookup_interaction_config(interaction) + .ok_or_else(|| anyhow!("Interaction does not have any Protobuf configuration"))?; + let descriptor_key = interaction_config.get("descriptorKey") + .map(json_to_string) + .ok_or_else(|| anyhow!("Interaction descriptorKey was missing in Pact file"))?; + let service = interaction_config.get("service") + .map(json_to_string) + .ok_or_else(|| anyhow!("Interaction gRPC service was missing in Pact file"))?; + let (service_name, method_name) = service.split_once('/') + .ok_or_else(|| anyhow!("Service name '{}' is not valid, it should be of the form /", service))?; + + let plugin_config = pact.plugin_data.iter() + .find(|data| data.name == "protobuf") + .map(|data| &data.configuration) + .ok_or_else(|| anyhow!("Did not find any Protobuf configuration in the Pact file"))? + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + let descriptors = get_descriptors_for_interaction(descriptor_key.as_str(), + &plugin_config)?; + let (file_descriptor, service_descriptor) = find_service_descriptor(&descriptors, service_name)?; + let method_descriptor = service_descriptor.method.iter().find(|method_desc| { + method_desc.name.clone().unwrap_or_default() == method_name + }).ok_or_else(|| anyhow!("Did not find the method {} in the Protobuf file descriptor for service '{}'", method_name, service))?; + + let package = file_descriptor.package.clone(); + Ok((descriptors.clone(), service_descriptor.clone(), method_descriptor.clone(), package.unwrap_or_default())) } /// Get the encoded Protobuf descriptors from the Pact level configuration for the message key pub fn get_descriptors_for_interaction( - message_key: &str, - plugin_config: &BTreeMap, + message_key: &str, + plugin_config: &BTreeMap ) -> anyhow::Result { - let descriptor_config = plugin_config - .get(message_key) - .ok_or_else(|| { - anyhow!( - "Plugin configuration item with key '{}' is required. Received config {:?}", - message_key, - plugin_config.keys() - ) - })? - .as_object() - .ok_or_else(|| { - anyhow!( - "Plugin configuration item with key '{}' has an invalid format", - message_key - ) - })?; - let descriptor_bytes_encoded = descriptor_config - .get("protoDescriptors") - .map(json_to_string) - .unwrap_or_default(); - if descriptor_bytes_encoded.is_empty() { - return Err(anyhow!("Plugin configuration item with key '{}' is required, but the descriptors were empty. Received config {:?}", message_key, plugin_config.keys())); - } - - // The descriptor bytes will be base 64 encoded. - let descriptor_bytes = match BASE64.decode(descriptor_bytes_encoded) { - Ok(bytes) => Bytes::from(bytes), - Err(err) => { - return Err(anyhow!( - "Failed to decode the Protobuf descriptor - {}", - err - )); - } - }; - debug!( - "Protobuf file descriptor set is {} bytes", - descriptor_bytes.len() - ); - - // Get an MD5 hash of the bytes to check that it matches the descriptor key - let digest = md5::compute(&descriptor_bytes); - let descriptor_hash = format!("{:x}", digest); - if descriptor_hash != message_key { - return Err(anyhow!( - "Protobuf descriptors checksum failed. Expected {} but got {}", - message_key, - descriptor_hash - )); + let descriptor_config = plugin_config.get(message_key) + .ok_or_else(|| anyhow!("Plugin configuration item with key '{}' is required. Received config {:?}", message_key, plugin_config.keys()))? + .as_object() + .ok_or_else(|| anyhow!("Plugin configuration item with key '{}' has an invalid format", message_key))?; + let descriptor_bytes_encoded = descriptor_config.get("protoDescriptors") + .map(json_to_string) + .unwrap_or_default(); + if descriptor_bytes_encoded.is_empty() { + return Err(anyhow!("Plugin configuration item with key '{}' is required, but the descriptors were empty. Received config {:?}", message_key, plugin_config.keys())); + } + + // The descriptor bytes will be base 64 encoded. + let descriptor_bytes = match BASE64.decode(descriptor_bytes_encoded) { + Ok(bytes) => Bytes::from(bytes), + Err(err) => { + return Err(anyhow!("Failed to decode the Protobuf descriptor - {}", err)); } - - // Decode the Protobuf descriptors - FileDescriptorSet::decode(descriptor_bytes).map_err(|err| anyhow!(err)) + }; + debug!("Protobuf file descriptor set is {} bytes", descriptor_bytes.len()); + + // Get an MD5 hash of the bytes to check that it matches the descriptor key + let digest = md5::compute(&descriptor_bytes); + let descriptor_hash = format!("{:x}", digest); + if descriptor_hash != message_key { + return Err(anyhow!("Protobuf descriptors checksum failed. Expected {} but got {}", message_key, descriptor_hash)); + } + + // Decode the Protobuf descriptors + FileDescriptorSet::decode(descriptor_bytes) + .map_err(|err| anyhow!(err)) } pub(crate) fn find_service_descriptor<'a>( - descriptors: &'a FileDescriptorSet, - service_name: &str, + descriptors: &'a FileDescriptorSet, + service_name: &str ) -> anyhow::Result<(&'a FileDescriptorProto, &'a ServiceDescriptorProto)> { - descriptors - .file - .iter() - .filter_map(|descriptor| { - descriptor - .service - .iter() - .find(|p| p.name.clone().unwrap_or_default() == service_name) - .map(|p| (descriptor, p)) - }) - .next() - .ok_or_else(|| anyhow!("Did not find a descriptor for service '{}'", service_name)) + descriptors.file.iter().filter_map(|descriptor| { + descriptor.service.iter() + .find(|p| p.name.clone().unwrap_or_default() == service_name) + .map(|p| (descriptor, p)) + }) + .next() + .ok_or_else(|| anyhow!("Did not find a descriptor for service '{}'", service_name)) } /// If a field type should be packed. These are repeated fields of primitive numeric types /// (types which use the varint, 32-bit, or 64-bit wire types) pub fn should_be_packed_type(field_type: Type) -> bool { - matches!( - field_type, - Type::Double - | Type::Float - | Type::Int64 - | Type::Uint64 - | Type::Int32 - | Type::Fixed64 - | Type::Fixed32 - | Type::Uint32 - | Type::Sfixed32 - | Type::Sfixed64 - | Type::Sint32 - | Type::Sint64 - | Type::Enum - ) + matches!(field_type, Type::Double | Type::Float | Type::Int64 | Type::Uint64 | Type::Int32 | Type::Fixed64 | + Type::Fixed32 | Type::Uint32 | Type::Sfixed32 | Type::Sfixed64 | Type::Sint32 | + Type::Sint64 | Type::Enum) } /// Tries to convert a Protobuf Value to a Map. Returns an error if the incoming value is not a /// value Protobuf type (Struct or NullValue) pub fn proto_value_to_map(val: &Value) -> anyhow::Result> { - match &val.kind { - Some(kind) => match kind { - Kind::NullValue(_) => Ok(BTreeMap::default()), - Kind::StructValue(s) => Ok(s.fields.clone()), - _ => Err(anyhow!( - "Must be a Protobuf Struct or NullValue, got {}", - type_of(kind) - )), - }, - None => Ok(BTreeMap::default()), + match &val.kind { + Some(kind) => match kind { + Kind::NullValue(_) => Ok(BTreeMap::default()), + Kind::StructValue(s) => Ok(s.fields.clone()), + _ => Err(anyhow!("Must be a Protobuf Struct or NullValue, got {}", type_of(kind))) } + None => Ok(BTreeMap::default()) + } } fn type_of(kind: &Kind) -> String { - match kind { - Kind::NullValue(_) => "Null", - Kind::NumberValue(_) => "Number", - Kind::StringValue(_) => "String", - Kind::BoolValue(_) => "Bool", - Kind::StructValue(_) => "Struct", - Kind::ListValue(_) => "List", - } - .to_string() + match kind { + Kind::NullValue(_) => "Null", + Kind::NumberValue(_) => "Number", + Kind::StringValue(_) => "String", + Kind::BoolValue(_) => "Bool", + Kind::StructValue(_) => "Struct", + Kind::ListValue(_) => "List" + }.to_string() } pub(crate) fn prost_string>(s: S) -> Value { - Value { - kind: Some(Kind::StringValue(s.into())), - } + Value { + kind: Some(Kind::StringValue(s.into())) + } } #[cfg(test)] pub(crate) mod tests { - use base64::engine::general_purpose::STANDARD as BASE64; - use base64::Engine; - use bytes::Bytes; - use expectest::prelude::*; - use maplit::hashmap; - use prost::Message; - use prost_types::field_descriptor_proto::{Label, Type}; - use prost_types::{ - DescriptorProto, EnumDescriptorProto, EnumValueDescriptorProto, FieldDescriptorProto, - FileDescriptorProto, FileDescriptorSet, MessageOptions, + use std::collections::HashMap; + use base64::Engine; + use base64::engine::general_purpose::STANDARD as BASE64; + use bytes::Bytes; + use expectest::prelude::*; + use maplit::hashmap; + use prost::Message; + use prost_types::{ + DescriptorProto, + EnumDescriptorProto, + EnumValueDescriptorProto, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, + MessageOptions + }; + use prost_types::field_descriptor_proto::{Label, Type}; + + use crate::utils::{ + as_hex, + find_enum_value_by_name, + find_file_descriptor_for_package, + find_message_type_by_name, + find_nested_type, + is_map_field, + last_name, + split_name + }; + + #[test] + fn last_name_test() { + expect!(last_name("")).to(be_equal_to("")); + expect!(last_name("test")).to(be_equal_to("test")); + expect!(last_name(".")).to(be_equal_to("")); + expect!(last_name("test.")).to(be_equal_to("")); + expect!(last_name(".test")).to(be_equal_to("test")); + expect!(last_name("1.2")).to(be_equal_to("2")); + expect!(last_name("1.2.3.4")).to(be_equal_to("4")); + } + + #[test] + fn split_name_test() { + expect!(split_name("")).to(be_equal_to(("", None))); + expect!(split_name("test")).to(be_equal_to(("test", None))); + expect!(split_name(".")).to(be_equal_to(("", None))); + expect!(split_name("test.")).to(be_equal_to(("", Some("test")))); + expect!(split_name(".test")).to(be_equal_to(("test", None))); + expect!(split_name("1.2")).to(be_equal_to(("2", Some("1")))); + expect!(split_name("1.2.3.4")).to(be_equal_to(("4", Some("1.2.3")))); + } + + pub(crate) const DESCRIPTOR_WITH_EXT_MESSAGE: [u8; 626] = [ + 10, 168, 2, 10, 11, 86, 97, 108, 117, 101, 46, 112, 114, 111, 116, 111, 18, 21, 97, 114, 101, + 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 86, 97, 108, 117, 101, 34, 162, 1, + 10, 14, 65, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, 18, 36, 10, 14, 102, + 111, 114, 99, 101, 100, 95, 108, 105, 110, 101, 95, 105, 100, 24, 1, 32, 1, 40, 9, 82, 12, 102, + 111, 114, 99, 101, 100, 76, 105, 110, 101, 73, 100, 18, 44, 10, 18, 102, 111, 114, 99, 101, + 100, 95, 99, 114, 101, 97, 116, 105, 118, 101, 95, 105, 100, 24, 2, 32, 1, 40, 9, 82, 16, 102, + 111, 114, 99, 101, 100, 67, 114, 101, 97, 116, 105, 118, 101, 73, 100, 18, 30, 10, 11, 97, 100, + 95, 98, 114, 101, 97, 107, 95, 105, 100, 24, 3, 32, 1, 40, 9, 82, 9, 97, 100, 66, 114, 101, 97, + 107, 73, 100, 18, 28, 10, 9, 115, 101, 115, 115, 105, 111, 110, 73, 100, 24, 4, 32, 1, 40, 9, + 82, 9, 115, 101, 115, 115, 105, 111, 110, 73, 100, 42, 85, 10, 13, 65, 100, 66, 114, 101, 97, + 107, 65, 100, 84, 121, 112, 101, 18, 28, 10, 24, 77, 73, 83, 83, 73, 78, 71, 95, 65, 68, 95, + 66, 82, 69, 65, 75, 95, 65, 68, 95, 84, 89, 80, 69, 16, 0, 18, 18, 10, 14, 65, 85, 68, 73, 79, + 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 1, 18, 18, 10, 14, 86, 73, 68, 69, 79, 95, 65, 68, 95, + 66, 82, 69, 65, 75, 16, 2, 98, 6, 112, 114, 111, 116, 111, 51, 10, 196, 2, 10, 21, 97, 114, + 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 112, 114, 111, 116, 111, 18, 15, + 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 26, 11, 86, 97, 108, 117, + 101, 46, 112, 114, 111, 116, 111, 34, 97, 10, 14, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, + 117, 101, 115, 116, 18, 79, 10, 16, 97, 100, 95, 98, 114, 101, 97, 107, 95, 99, 111, 110, 116, + 101, 120, 116, 24, 1, 32, 3, 40, 11, 50, 37, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, + 108, 97, 116, 111, 114, 46, 86, 97, 108, 117, 101, 46, 65, 100, 66, 114, 101, 97, 107, 67, 111, + 110, 116, 101, 120, 116, 82, 14, 97, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, 101, 120, + 116, 34, 36, 10, 12, 65, 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 18, 20, 10, 5, + 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 2, 82, 5, 118, 97, 108, 117, 101, 50, 94, 10, 10, + 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 18, 80, 10, 12, 99, 97, 108, 99, 117, 108, 97, + 116, 101, 79, 110, 101, 18, 31, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, + 111, 114, 46, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, 26, 29, 46, 97, + 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, 114, 101, 97, 82, 101, + 115, 112, 111, 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, 112, 97, 99, 116, 47, 97, + 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 208, 2, 1, 98, 6, 112, 114, + 111, 116, 111, 51 + ]; + + #[test] + fn find_message_type_by_name_test() { + let bytes: &[u8] = &DESCRIPTOR_WITH_EXT_MESSAGE; + let buffer = Bytes::from(bytes); + let fds = FileDescriptorSet::decode(buffer).unwrap(); + + expect!(find_message_type_by_name("", &fds)).to(be_err()); + expect!(find_message_type_by_name("Does not exist", &fds)).to(be_err()); + + let (result, _) = find_message_type_by_name("AdBreakRequest", &fds).unwrap(); + expect!(result.name).to(be_some().value("AdBreakRequest")); + + let (result, _) = find_message_type_by_name("AdBreakContext", &fds).unwrap(); + expect!(result.name).to(be_some().value("AdBreakContext")); + } + + #[test] + fn find_nested_type_test() { + let non_message_field = FieldDescriptorProto { + r#type: Some(Type::Bytes as i32), + .. FieldDescriptorProto::default() }; - use std::collections::HashMap; - - use crate::utils::{ - as_hex, find_enum_value_by_name, find_message_type_by_name, find_nested_type, is_map_field, - last_name, split_name, + let field_with_no_type_name = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + .. FieldDescriptorProto::default() + }; + let field_with_incorrect_type_name = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + type_name: Some("field_with_incorrect_type_name".to_string()), + .. FieldDescriptorProto::default() + }; + let field_with_matching_type_name = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + type_name: Some("CorrectType".to_string()), + .. FieldDescriptorProto::default() + }; + let nested = DescriptorProto { + name: Some("CorrectType".to_string()), + .. DescriptorProto::default() + }; + let message = DescriptorProto { + field: vec![ + non_message_field.clone(), + field_with_no_type_name.clone(), + field_with_incorrect_type_name.clone() + ], + nested_type: vec![ + nested.clone() + ], + .. DescriptorProto::default() + }; + expect!(find_nested_type(&message, &non_message_field)).to(be_none()); + expect!(find_nested_type(&message, &field_with_no_type_name)).to(be_none()); + expect!(find_nested_type(&message, &field_with_incorrect_type_name)).to(be_none()); + expect!(find_nested_type(&message, &field_with_matching_type_name)).to(be_some().value(nested)); + } + + #[test] + fn is_map_field_test() { + let non_message_field = FieldDescriptorProto { + r#type: Some(Type::Bytes as i32), + .. FieldDescriptorProto::default() + }; + let non_repeated_field = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + .. FieldDescriptorProto::default() + }; + let repeated_field_with_no_nested_type = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + label: Some(Label::Repeated as i32), + type_name: Some("field_with_incorrect_type_name".to_string()), + .. FieldDescriptorProto::default() + }; + let field_with_non_map_nested_type = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + label: Some(Label::Repeated as i32), + type_name: Some("NonMapType".to_string()), + .. FieldDescriptorProto::default() + }; + let field_with_map_nested_type = FieldDescriptorProto { + r#type: Some(Type::Message as i32), + label: Some(Label::Repeated as i32), + type_name: Some("MapType".to_string()), + .. FieldDescriptorProto::default() + }; + let non_map_nested = DescriptorProto { + name: Some("NonMapType".to_string()), + .. DescriptorProto::default() + }; + let map_nested = DescriptorProto { + name: Some("MapType".to_string()), + options: Some(MessageOptions { + message_set_wire_format: None, + no_standard_descriptor_accessor: None, + deprecated: None, + map_entry: Some(true), + uninterpreted_option: vec![] + }), + .. DescriptorProto::default() + }; + let message = DescriptorProto { + field: vec![ + non_message_field.clone(), + non_repeated_field.clone(), + repeated_field_with_no_nested_type.clone(), + field_with_non_map_nested_type.clone(), + field_with_map_nested_type.clone() + ], + nested_type: vec![ + non_map_nested, + map_nested + ], + .. DescriptorProto::default() + }; + expect!(is_map_field(&message, &non_message_field)).to(be_false()); + expect!(is_map_field(&message, &non_repeated_field)).to(be_false()); + expect!(is_map_field(&message, &repeated_field_with_no_nested_type)).to(be_false()); + expect!(is_map_field(&message, &field_with_non_map_nested_type)).to(be_false()); + expect!(is_map_field(&message, &field_with_map_nested_type)).to(be_true()); + } + + #[test] + fn as_hex_test() { + expect!(as_hex(&[])).to(be_equal_to("")); + expect!(as_hex(&[1, 2, 3, 255])).to(be_equal_to("010203ff")); + } + + #[test] + fn find_enum_value_by_name_test() { + let enum1 = EnumDescriptorProto { + name: Some("TestEnum".to_string()), + value: vec![ + EnumValueDescriptorProto { + name: Some("VALUE_ZERO".to_string()), + number: Some(0), + options: None, + }, + EnumValueDescriptorProto { + name: Some("VALUE_ONE".to_string()), + number: Some(1), + options: None, + }, + EnumValueDescriptorProto { + name: Some("VALUE_TWO".to_string()), + number: Some(2), + options: None, + }, + ], + .. EnumDescriptorProto::default() + }; + let fds = FileDescriptorProto { + name: Some("test_enum.proto".to_string()), + package: Some("routeguide.v2".to_string()), + message_type: vec![ + DescriptorProto { + name: Some("Feature".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("result".to_string()), + number: Some(1), + label: Some(1), + r#type: Some(14), + type_name: Some(".routeguide.v2.TestEnum".to_string()), + .. FieldDescriptorProto::default() + }, + ], + .. DescriptorProto::default() + } + ], + enum_type: vec![ + enum1.clone() + ], + .. FileDescriptorProto::default() + }; + let fds2 = FileDescriptorProto { + name: Some("test_enum2.proto".to_string()), + package: Some("routeguide".to_string()), + message_type: vec![ + DescriptorProto { + name: Some("Feature".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("result".to_string()), + number: Some(1), + label: Some(1), + r#type: Some(14), + type_name: Some(".routeguide.TestEnum".to_string()), + .. FieldDescriptorProto::default() + }, + ], + .. DescriptorProto::default() + } + ], + enum_type: vec![ + enum1.clone() + ], + .. FileDescriptorProto::default() + }; + let fds3 = FileDescriptorProto { + name: Some("test_enum3.proto".to_string()), + package: Some("".to_string()), + message_type: vec![ + DescriptorProto { + name: Some("Feature".to_string()), + field: vec![ + FieldDescriptorProto { + name: Some("result".to_string()), + number: Some(1), + label: Some(1), + r#type: Some(14), + type_name: Some(".TestEnum".to_string()), + .. FieldDescriptorProto::default() + }, + ], + .. DescriptorProto::default() + } + ], + enum_type: vec![ + enum1.clone() + ], + .. FileDescriptorProto::default() + }; + let fds4 = FileDescriptorProto { + name: Some("test_enum4.proto".to_string()), + package: Some("routeguide.v3".to_string()), + message_type: vec![ + DescriptorProto { + name: Some("Feature".to_string()), + enum_type: vec![ + enum1.clone() + ], + .. DescriptorProto::default() + } + ], + .. FileDescriptorProto::default() + }; + let descriptors = hashmap!{ + "test_enum.proto".to_string() => &fds, + "test_enum2.proto".to_string() => &fds2, + "test_enum3.proto".to_string() => &fds3, + "test_enum4.proto".to_string() => &fds4 }; - #[test] - fn last_name_test() { - expect!(last_name("")).to(be_equal_to("")); - expect!(last_name("test")).to(be_equal_to("test")); - expect!(last_name(".")).to(be_equal_to("")); - expect!(last_name("test.")).to(be_equal_to("")); - expect!(last_name(".test")).to(be_equal_to("test")); - expect!(last_name("1.2")).to(be_equal_to("2")); - expect!(last_name("1.2.3.4")).to(be_equal_to("4")); - } - - #[test] - fn split_name_test() { - expect!(split_name("")).to(be_equal_to(("", None))); - expect!(split_name("test")).to(be_equal_to(("test", None))); - expect!(split_name(".")).to(be_equal_to(("", None))); - expect!(split_name("test.")).to(be_equal_to(("", Some("test")))); - expect!(split_name(".test")).to(be_equal_to(("test", None))); - expect!(split_name("1.2")).to(be_equal_to(("2", Some("1")))); - expect!(split_name("1.2.3.4")).to(be_equal_to(("4", Some("1.2.3")))); - } - - pub(crate) const DESCRIPTOR_WITH_EXT_MESSAGE: [u8; 626] = [ - 10, 168, 2, 10, 11, 86, 97, 108, 117, 101, 46, 112, 114, 111, 116, 111, 18, 21, 97, 114, - 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 86, 97, 108, 117, 101, 34, - 162, 1, 10, 14, 65, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, 18, 36, - 10, 14, 102, 111, 114, 99, 101, 100, 95, 108, 105, 110, 101, 95, 105, 100, 24, 1, 32, 1, - 40, 9, 82, 12, 102, 111, 114, 99, 101, 100, 76, 105, 110, 101, 73, 100, 18, 44, 10, 18, - 102, 111, 114, 99, 101, 100, 95, 99, 114, 101, 97, 116, 105, 118, 101, 95, 105, 100, 24, 2, - 32, 1, 40, 9, 82, 16, 102, 111, 114, 99, 101, 100, 67, 114, 101, 97, 116, 105, 118, 101, - 73, 100, 18, 30, 10, 11, 97, 100, 95, 98, 114, 101, 97, 107, 95, 105, 100, 24, 3, 32, 1, - 40, 9, 82, 9, 97, 100, 66, 114, 101, 97, 107, 73, 100, 18, 28, 10, 9, 115, 101, 115, 115, - 105, 111, 110, 73, 100, 24, 4, 32, 1, 40, 9, 82, 9, 115, 101, 115, 115, 105, 111, 110, 73, - 100, 42, 85, 10, 13, 65, 100, 66, 114, 101, 97, 107, 65, 100, 84, 121, 112, 101, 18, 28, - 10, 24, 77, 73, 83, 83, 73, 78, 71, 95, 65, 68, 95, 66, 82, 69, 65, 75, 95, 65, 68, 95, 84, - 89, 80, 69, 16, 0, 18, 18, 10, 14, 65, 85, 68, 73, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, - 16, 1, 18, 18, 10, 14, 86, 73, 68, 69, 79, 95, 65, 68, 95, 66, 82, 69, 65, 75, 16, 2, 98, - 6, 112, 114, 111, 116, 111, 51, 10, 196, 2, 10, 21, 97, 114, 101, 97, 95, 99, 97, 108, 99, - 117, 108, 97, 116, 111, 114, 46, 112, 114, 111, 116, 111, 18, 15, 97, 114, 101, 97, 95, 99, - 97, 108, 99, 117, 108, 97, 116, 111, 114, 26, 11, 86, 97, 108, 117, 101, 46, 112, 114, 111, - 116, 111, 34, 97, 10, 14, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, - 18, 79, 10, 16, 97, 100, 95, 98, 114, 101, 97, 107, 95, 99, 111, 110, 116, 101, 120, 116, - 24, 1, 32, 3, 40, 11, 50, 37, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, - 111, 114, 46, 86, 97, 108, 117, 101, 46, 65, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, - 101, 120, 116, 82, 14, 97, 100, 66, 114, 101, 97, 107, 67, 111, 110, 116, 101, 120, 116, - 34, 36, 10, 12, 65, 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 18, 20, 10, 5, - 118, 97, 108, 117, 101, 24, 1, 32, 3, 40, 2, 82, 5, 118, 97, 108, 117, 101, 50, 94, 10, 10, - 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 18, 80, 10, 12, 99, 97, 108, 99, 117, 108, - 97, 116, 101, 79, 110, 101, 18, 31, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, - 97, 116, 111, 114, 46, 65, 100, 66, 114, 101, 97, 107, 82, 101, 113, 117, 101, 115, 116, - 26, 29, 46, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 65, - 114, 101, 97, 82, 101, 115, 112, 111, 110, 115, 101, 34, 0, 66, 28, 90, 23, 105, 111, 46, - 112, 97, 99, 116, 47, 97, 114, 101, 97, 95, 99, 97, 108, 99, 117, 108, 97, 116, 111, 114, - 208, 2, 1, 98, 6, 112, 114, 111, 116, 111, 51, - ]; - - #[test] - fn find_message_type_by_name_test() { - let bytes: &[u8] = &DESCRIPTOR_WITH_EXT_MESSAGE; - let buffer = Bytes::from(bytes); - let fds = FileDescriptorSet::decode(buffer).unwrap(); - - expect!(find_message_type_by_name("", &fds)).to(be_err()); - expect!(find_message_type_by_name("Does not exist", &fds)).to(be_err()); - - let (result, _) = find_message_type_by_name("AdBreakRequest", &fds).unwrap(); - expect!(result.name).to(be_some().value("AdBreakRequest")); - - let (result, _) = find_message_type_by_name("AdBreakContext", &fds).unwrap(); - expect!(result.name).to(be_some().value("AdBreakContext")); - } - - #[test] - fn find_nested_type_test() { - let non_message_field = FieldDescriptorProto { - r#type: Some(Type::Bytes as i32), - ..FieldDescriptorProto::default() - }; - let field_with_no_type_name = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - ..FieldDescriptorProto::default() - }; - let field_with_incorrect_type_name = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - type_name: Some("field_with_incorrect_type_name".to_string()), - ..FieldDescriptorProto::default() - }; - let field_with_matching_type_name = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - type_name: Some("CorrectType".to_string()), - ..FieldDescriptorProto::default() - }; - let nested = DescriptorProto { - name: Some("CorrectType".to_string()), - ..DescriptorProto::default() - }; - let message = DescriptorProto { - field: vec![ - non_message_field.clone(), - field_with_no_type_name.clone(), - field_with_incorrect_type_name.clone(), - ], - nested_type: vec![nested.clone()], - ..DescriptorProto::default() - }; - expect!(find_nested_type(&message, &non_message_field)).to(be_none()); - expect!(find_nested_type(&message, &field_with_no_type_name)).to(be_none()); - expect!(find_nested_type(&message, &field_with_incorrect_type_name)).to(be_none()); - expect!(find_nested_type(&message, &field_with_matching_type_name)) - .to(be_some().value(nested)); - } - - #[test] - fn is_map_field_test() { - let non_message_field = FieldDescriptorProto { - r#type: Some(Type::Bytes as i32), - ..FieldDescriptorProto::default() - }; - let non_repeated_field = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - ..FieldDescriptorProto::default() - }; - let repeated_field_with_no_nested_type = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - label: Some(Label::Repeated as i32), - type_name: Some("field_with_incorrect_type_name".to_string()), - ..FieldDescriptorProto::default() - }; - let field_with_non_map_nested_type = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - label: Some(Label::Repeated as i32), - type_name: Some("NonMapType".to_string()), - ..FieldDescriptorProto::default() - }; - let field_with_map_nested_type = FieldDescriptorProto { - r#type: Some(Type::Message as i32), - label: Some(Label::Repeated as i32), - type_name: Some("MapType".to_string()), - ..FieldDescriptorProto::default() - }; - let non_map_nested = DescriptorProto { - name: Some("NonMapType".to_string()), - ..DescriptorProto::default() - }; - let map_nested = DescriptorProto { - name: Some("MapType".to_string()), - options: Some(MessageOptions { - message_set_wire_format: None, - no_standard_descriptor_accessor: None, - deprecated: None, - map_entry: Some(true), - uninterpreted_option: vec![], - }), - ..DescriptorProto::default() - }; - let message = DescriptorProto { - field: vec![ - non_message_field.clone(), - non_repeated_field.clone(), - repeated_field_with_no_nested_type.clone(), - field_with_non_map_nested_type.clone(), - field_with_map_nested_type.clone(), - ], - nested_type: vec![non_map_nested, map_nested], - ..DescriptorProto::default() - }; - expect!(is_map_field(&message, &non_message_field)).to(be_false()); - expect!(is_map_field(&message, &non_repeated_field)).to(be_false()); - expect!(is_map_field(&message, &repeated_field_with_no_nested_type)).to(be_false()); - expect!(is_map_field(&message, &field_with_non_map_nested_type)).to(be_false()); - expect!(is_map_field(&message, &field_with_map_nested_type)).to(be_true()); - } - - #[test] - fn as_hex_test() { - expect!(as_hex(&[])).to(be_equal_to("")); - expect!(as_hex(&[1, 2, 3, 255])).to(be_equal_to("010203ff")); - } - - #[test] - fn find_enum_value_by_name_test() { - let enum1 = EnumDescriptorProto { - name: Some("TestEnum".to_string()), - value: vec![ - EnumValueDescriptorProto { - name: Some("VALUE_ZERO".to_string()), - number: Some(0), - options: None, - }, - EnumValueDescriptorProto { - name: Some("VALUE_ONE".to_string()), - number: Some(1), - options: None, - }, - EnumValueDescriptorProto { - name: Some("VALUE_TWO".to_string()), - number: Some(2), - options: None, - }, - ], - ..EnumDescriptorProto::default() - }; - let fds = FileDescriptorProto { - name: Some("test_enum.proto".to_string()), - package: Some("routeguide.v2".to_string()), - message_type: vec![DescriptorProto { - name: Some("Feature".to_string()), - field: vec![FieldDescriptorProto { - name: Some("result".to_string()), - number: Some(1), - label: Some(1), - r#type: Some(14), - type_name: Some(".routeguide.v2.TestEnum".to_string()), - ..FieldDescriptorProto::default() - }], - ..DescriptorProto::default() - }], - enum_type: vec![enum1.clone()], - ..FileDescriptorProto::default() - }; - let fds2 = FileDescriptorProto { - name: Some("test_enum2.proto".to_string()), - package: Some("routeguide".to_string()), - message_type: vec![DescriptorProto { - name: Some("Feature".to_string()), - field: vec![FieldDescriptorProto { - name: Some("result".to_string()), - number: Some(1), - label: Some(1), - r#type: Some(14), - type_name: Some(".routeguide.TestEnum".to_string()), - ..FieldDescriptorProto::default() - }], - ..DescriptorProto::default() - }], - enum_type: vec![enum1.clone()], - ..FileDescriptorProto::default() - }; - let fds3 = FileDescriptorProto { - name: Some("test_enum3.proto".to_string()), - package: Some("".to_string()), - message_type: vec![DescriptorProto { - name: Some("Feature".to_string()), - field: vec![FieldDescriptorProto { - name: Some("result".to_string()), - number: Some(1), - label: Some(1), - r#type: Some(14), - type_name: Some(".TestEnum".to_string()), - ..FieldDescriptorProto::default() - }], - ..DescriptorProto::default() - }], - enum_type: vec![enum1.clone()], - ..FileDescriptorProto::default() - }; - let fds4 = FileDescriptorProto { - name: Some("test_enum4.proto".to_string()), - package: Some("routeguide.v3".to_string()), - message_type: vec![DescriptorProto { - name: Some("Feature".to_string()), - enum_type: vec![enum1.clone()], - ..DescriptorProto::default() - }], - ..FileDescriptorProto::default() - }; - let descriptors = hashmap! { - "test_enum.proto".to_string() => &fds, - "test_enum2.proto".to_string() => &fds2, - "test_enum3.proto".to_string() => &fds3, - "test_enum4.proto".to_string() => &fds4 - }; - - let result = find_enum_value_by_name(&descriptors, ".routeguide.v2.TestEnum", "VALUE_ONE"); - expect!(result).to(be_some().value((1, enum1.clone()))); - - let result2 = find_enum_value_by_name(&descriptors, ".routeguide.TestEnum", "VALUE_ONE"); - expect!(result2).to(be_some().value((1, enum1.clone()))); - - let result3 = find_enum_value_by_name(&descriptors, ".TestEnum", "VALUE_TWO"); - expect!(result3).to(be_some().value((2, enum1.clone()))); - - let result4 = - find_enum_value_by_name(&descriptors, ".routeguide.v3.Feature.TestEnum", "VALUE_ONE"); - expect!(result4).to(be_some().value((1, enum1.clone()))); - } + let result = find_enum_value_by_name(&descriptors, ".routeguide.v2.TestEnum", "VALUE_ONE"); + expect!(result).to(be_some().value((1, enum1.clone()))); + + let result2 = find_enum_value_by_name(&descriptors, ".routeguide.TestEnum", "VALUE_ONE"); + expect!(result2).to(be_some().value((1, enum1.clone()))); + + let result3 = find_enum_value_by_name(&descriptors, ".TestEnum", "VALUE_TWO"); + expect!(result3).to(be_some().value((2, enum1.clone()))); + + let result4 = find_enum_value_by_name(&descriptors, ".routeguide.v3.Feature.TestEnum", "VALUE_ONE"); + expect!(result4).to(be_some().value((1, enum1.clone()))); + } + + #[test] + fn find_file_descriptor_for_package_test() { + let descriptors = "CpAEChdpbXBvcnRlZC9pbXBvcnRlZC5wcm90bxIIaW1wb3J0ZWQiOQoJUmVjdGFuZ2x\ + lEhQKBXdpZHRoGAEgASgFUgV3aWR0aBIWCgZsZW5ndGgYAiABKAVSBmxlbmd0aCJIChhSZWN0YW5nbGVMb2NhdGlvblJ\ + lcXVlc3QSFAoFd2lkdGgYASABKAVSBXdpZHRoEhYKBmxlbmd0aBgCIAEoBVIGbGVuZ3RoIkgKGVJlY3RhbmdsZUxvY2F0\ + aW9uUmVzcG9uc2USKwoIbG9jYXRpb24YASABKAsyDy5pbXBvcnRlZC5Qb2ludFIIbG9jYXRpb24iQQoFUG9pbnQSGgoIb\ + GF0aXR1ZGUYASABKAVSCGxhdGl0dWRlEhwKCWxvbmdpdHVkZRgCIAEoBVIJbG9uZ2l0dWRlMmUKCEltcG9ydGVkElkKDE\ + dldFJlY3RhbmdsZRIiLmltcG9ydGVkLlJlY3RhbmdsZUxvY2F0aW9uUmVxdWVzdBojLmltcG9ydGVkLlJlY3RhbmdsZUxv\ + Y2F0aW9uUmVzcG9uc2UiAEJqChlpby5ncnBjLmV4YW1wbGVzLmltcG9ydGVkQg1JbXBvcnRlZFByb3RvUAFaPGdpdGh1Y\ + i5jb20vcGFjdC1mb3VuZGF0aW9uL3BhY3QtZ28vdjIvZXhhbXBsZXMvZ3JwYy9pbXBvcnRlZGIGcHJvdG8zCooECg1wcm\ + ltYXJ5LnByb3RvEgdwcmltYXJ5GhdpbXBvcnRlZC9pbXBvcnRlZC5wcm90byJNCglSZWN0YW5nbGUSHwoCbG8YASABKAs\ + yDy5pbXBvcnRlZC5Qb2ludFICbG8SHwoCaGkYAiABKAsyDy5pbXBvcnRlZC5Qb2ludFICaGkiZAoYUmVjdGFuZ2xlTG9j\ + YXRpb25SZXF1ZXN0EgwKAXgYASABKAVSAXgSDAoBeRgCIAEoBVIBeRIUCgV3aWR0aBgDIAEoBVIFd2lkdGgSFgoGbGVuZ\ + 3RoGAQgASgFUgZsZW5ndGgiTQoZUmVjdGFuZ2xlTG9jYXRpb25SZXNwb25zZRIwCglyZWN0YW5nbGUYASABKAsyEi5wcml\ + tYXJ5LlJlY3RhbmdsZVIJcmVjdGFuZ2xlMmIKB1ByaW1hcnkSVwoMR2V0UmVjdGFuZ2xlEiEucHJpbWFyeS5SZWN0YW5nb\ + GVMb2NhdGlvblJlcXVlc3QaIi5wcmltYXJ5LlJlY3RhbmdsZUxvY2F0aW9uUmVzcG9uc2UiAEJnChhpby5ncnBjLmV4YW1\ + wbGVzLnByaW1hcnlCDFByaW1hcnlQcm90b1ABWjtnaXRodWIuY29tL3BhY3QtZm91bmRhdGlvbi9wYWN0LWdvL3YyL2V4Y\ + W1wbGVzL2dycGMvcHJpbWFyeWIGcHJvdG8z"; + let decoded = BASE64.decode(descriptors).unwrap(); + let bytes = Bytes::copy_from_slice(decoded.as_slice()); + let fds = FileDescriptorSet::decode(bytes).unwrap(); + let all: HashMap = fds.file + .iter().map(|des| (des.name.clone().unwrap_or_default(), des)) + .collect(); + + let file_descriptor = &fds.file[0]; + let primary_descriptor = find_file_descriptor_for_package(".primary", file_descriptor, &all).unwrap(); + expect!(primary_descriptor.name).to(be_some().value("primary.proto".to_string())); + let imported_descriptor = find_file_descriptor_for_package("imported", file_descriptor, &all).unwrap(); + expect!(imported_descriptor.name).to(be_some().value("imported/imported.proto".to_string())); + } } diff --git a/src/verification.rs b/src/verification.rs index 4dced55..5c8bc21 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -11,432 +11,352 @@ use maplit::hashmap; use pact_matching::{BodyMatchResult, CoreMatchingContext, DiffConfig, Mismatch}; use pact_models::content_types::ContentType; use pact_models::json_utils::{json_to_num, json_to_string}; -use pact_models::prelude::v4::V4Pact; use pact_models::prelude::OptionalBody; +use pact_models::prelude::v4::V4Pact; use pact_models::v4::message_parts::MessageContents; use pact_models::v4::sync_message::SynchronousMessage; use pact_plugin_driver::proto; use pact_plugin_driver::utils::proto_value_to_string; use pact_verifier::verification_result::VerificationMismatchResult; -use prost_types::{ - DescriptorProto, FileDescriptorSet, MethodDescriptorProto, ServiceDescriptorProto, -}; +use prost_types::{DescriptorProto, FileDescriptorSet, MethodDescriptorProto, ServiceDescriptorProto}; use serde_json::Value; -use tonic::metadata::{Ascii, Binary, MetadataKey, MetadataMap, MetadataValue}; use tonic::{Request, Response, Status}; +use tonic::metadata::{Ascii, Binary, MetadataKey, MetadataMap, MetadataValue}; use tower::ServiceExt; use tracing::{debug, error, instrument, trace, warn}; -use tracing_subscriber::field::debug; use crate::dynamic_message::{DynamicMessage, PactCodec}; use crate::matching::match_service; use crate::message_decoder::decode_message; use crate::metadata::{compare_metadata, grpc_status, MetadataMatchResult}; -use crate::utils::{ - find_message_type_by_name, last_name, lookup_service_descriptors_for_interaction, -}; +use crate::utils::{find_message_type_by_name, last_name, lookup_service_descriptors_for_interaction}; #[derive(Debug)] struct GrpcError { - pub status: Status, + pub status: Status } impl Display for GrpcError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "gRPC request failed {}", self.status) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "gRPC request failed {}", self.status) + } } impl std::error::Error for GrpcError {} /// Verify a gRPC interaction pub async fn verify_interaction( - pact: &V4Pact, - interaction: &SynchronousMessage, - request_body: &OptionalBody, - metadata: &HashMap, - config: &HashMap, + pact: &V4Pact, + interaction: &SynchronousMessage, + request_body: &OptionalBody, + metadata: &HashMap, + config: &HashMap ) -> anyhow::Result<(Vec, Vec)> { - debug!("Verifying interaction {}", interaction); - trace!("interaction={:?}", interaction); - trace!("metadata={:?}", metadata); - trace!("config={:?}", config); + debug!("Verifying interaction {}", interaction); + trace!("interaction={:?}", interaction); + trace!("metadata={:?}", metadata); + trace!("config={:?}", config); - let (file_desc, service_desc, method_desc, _) = - lookup_service_descriptors_for_interaction(interaction, pact)?; - let input_message_name = method_desc.input_type.clone().unwrap_or_default(); - let input_message = - find_message_type_by_name(last_name(input_message_name.as_str()), &file_desc)?.0; - let output_message_name = method_desc.output_type.clone().unwrap_or_default(); - let output_message = - find_message_type_by_name(last_name(output_message_name.as_str()), &file_desc)?.0; - debug!( - "Verifying interaction with input message '{}' and output message '{}'", - input_message_name, output_message_name - ); - let bold = Style::new().bold(); + let (file_desc, service_desc, method_desc, _) = lookup_service_descriptors_for_interaction(interaction, pact)?; + let input_message_name = method_desc.input_type.clone().unwrap_or_default(); + let input_message = find_message_type_by_name(last_name(input_message_name.as_str()), &file_desc)?.0; + let output_message_name = method_desc.output_type.clone().unwrap_or_default(); + let output_message = find_message_type_by_name(last_name(output_message_name.as_str()), &file_desc)?.0; + let bold = Style::new().bold(); - match build_grpc_request(request_body, metadata, &file_desc, &input_message) { - Ok(request) => match make_grpc_request( - request, - config, - metadata, - &file_desc, - &input_message, - &output_message, - interaction, - ) - .await - { - Ok(response) => { - debug!("Received response from gRPC server - {:?}", response); - let response_metadata = response.metadata(); - let body = response.get_ref(); - trace!("gRPC metadata: {:?}", response_metadata); - trace!("gRPC body: {:?}", body); - let (result, verification_output) = verify_response( - body, - response_metadata, - interaction, - &file_desc, - &service_desc, - &method_desc, - )?; + match build_grpc_request(request_body, metadata, &file_desc, &input_message) { + Ok(request) => match make_grpc_request(request, config, metadata, &file_desc, &input_message, &output_message, interaction).await { + Ok(response) => { + debug!("Received response from gRPC server - {:?}", response); + let response_metadata = response.metadata(); + let body = response.get_ref(); + trace!("gRPC metadata: {:?}", response_metadata); + trace!("gRPC body: {:?}", body); + let (result, verification_output) = verify_response(body, response_metadata, interaction, + &file_desc, &service_desc, &method_desc)?; - let status_result = if !result.is_empty() { - Red.paint("FAILED") - } else { - Green.paint("OK") - }; - let mut output = vec![ - format!( - "Given a {}/{} request", - bold.paint(service_desc.name.unwrap_or_default()), - bold.paint(method_desc.name.unwrap_or_default()) - ), - format!( - " with an input {} message", - bold.paint(input_message_name) - ), - format!( - " will return an output {} message [{}]", - bold.paint(output_message_name), - status_result - ), - ]; - output.extend(verification_output); + let status_result = if !result.is_empty() { + Red.paint("FAILED") + } else { + Green.paint("OK") + }; + let mut output = vec![ + format!("Given a {}/{} request", + bold.paint(service_desc.name.unwrap_or_default()), + bold.paint(method_desc.name.unwrap_or_default())), + format!(" with an input {} message", bold.paint(input_message_name)), + format!(" will return an output {} message [{}]", bold.paint(output_message_name), status_result) + ]; + output.extend(verification_output); - Ok((result, output)) - } - Err(err) => { - error!("Received error response from gRPC provider - {:?}", err); - if let Some(received_status) = err.downcast_ref::() { - trace!("gRPC message: {}", received_status.status.message()); - trace!("gRPC metadata: {:?}", received_status.status.metadata()); - let default_contents = MessageContents::default(); - let expected_response = - interaction.response.first().unwrap_or(&default_contents); - if let Some(expected_status) = grpc_status(expected_response) { - let (result, verification_output) = verify_error_response( - expected_response, - &received_status.status, - &interaction.id, - ); - let status_result = if !result.is_empty() { - Red.paint("FAILED") - } else { - Green.paint("OK") - }; - let mut output = vec![ - format!( - "Given a {}/{} request", - bold.paint(service_desc.name.unwrap_or_default()), - bold.paint(method_desc.name.unwrap_or_default()) - ), - format!( - " with an input {} message", - bold.paint(input_message_name) - ), - format!( - " will return an error response {} [{}]", - bold.paint(expected_status.code().to_string()), - status_result - ), - ]; - output.extend(verification_output); - Ok((result, output)) - } else { - Err(anyhow!(format!( - "gRPC error: status {}, message '{}'", - received_status.status.code(), - received_status.status.message() - ))) - } - } else { - Err(anyhow!(err)) - } - } - }, - Err(err) => { - error!("Failed to build gRPC request: {}", err); - Err(anyhow!(err)) + Ok((result, output)) + } + Err(err) => { + error!("Received error response from gRPC provider - {:?}", err); + if let Some(received_status) = err.downcast_ref::() { + trace!("gRPC message: {}", received_status.status.message()); + trace!("gRPC metadata: {:?}", received_status.status.metadata()); + let default_contents = MessageContents::default(); + let expected_response = interaction.response.first() + .unwrap_or(&default_contents); + if let Some(expected_status) = grpc_status(expected_response) { + let (result, verification_output) = verify_error_response(expected_response, + &received_status.status, &interaction.id); + let status_result = if !result.is_empty() { + Red.paint("FAILED") + } else { + Green.paint("OK") + }; + let mut output = vec![ + format!("Given a {}/{} request", + bold.paint(service_desc.name.unwrap_or_default()), + bold.paint(method_desc.name.unwrap_or_default())), + format!(" with an input {} message", bold.paint(input_message_name)), + format!(" will return an error response {} [{}]", bold.paint(expected_status.code().to_string()), status_result) + ]; + output.extend(verification_output); + Ok((result, output)) + } else { + Err(anyhow!(format!("gRPC error: status {}, message '{}'", received_status.status.code(), + received_status.status.message()))) + } + } else { + Err(anyhow!(err)) } + } } + Err(err) => { + error!("Failed to build gRPC request: {}", err); + Err(anyhow!(err)) + } + } } #[instrument] fn verify_error_response( - response: &MessageContents, - actual_status: &Status, - interaction_id: &Option, + response: &MessageContents, + actual_status: &Status, + interaction_id: &Option ) -> (Vec, Vec) { - let mut output = vec![]; - let mut results = vec![]; - if !response.metadata.is_empty() { - output.push(" with metadata".to_string()); - let mut metadata = actual_status.metadata().clone(); - if let Ok(code) = i32::from(actual_status.code()).to_string().parse() { - metadata.insert("grpc-status", code); - } - if !actual_status.message().is_empty() { - if let Ok(message) = actual_status.message().parse() { - metadata.insert("grpc-message", message); - } - } - match verify_metadata(&metadata, response) { - Ok((result, md_output)) => { - if !result.result { - results.push(VerificationMismatchResult::Mismatches { - mismatches: result.mismatches, - interaction_id: interaction_id.clone(), - }); - } - output.extend(md_output); - } - Err(err) => { - results.push(VerificationMismatchResult::Mismatches { - mismatches: vec![Mismatch::MetadataMismatch { - key: "".to_string(), - expected: "".to_string(), - actual: "".to_string(), - mismatch: format!("Failed to verify the message metadata: {}", err), - }], - interaction_id: interaction_id.clone(), - }); - } + let mut output = vec![]; + let mut results = vec![]; + if !response.metadata.is_empty() { + output.push(" with metadata".to_string()); + let mut metadata = actual_status.metadata().clone(); + if let Ok(code) = i32::from(actual_status.code()).to_string().parse() { + metadata.insert("grpc-status", code); + } + if !actual_status.message().is_empty() { + if let Ok(message) = actual_status.message().parse() { + metadata.insert("grpc-message", message); + } + } + match verify_metadata(&metadata, response) { + Ok((result, md_output)) => { + if !result.result { + results.push(VerificationMismatchResult::Mismatches { + mismatches: result.mismatches, + interaction_id: interaction_id.clone() + }); } + output.extend(md_output); + } + Err(err) => { + results.push(VerificationMismatchResult::Mismatches { + mismatches: vec![ Mismatch::MetadataMismatch { + key: "".to_string(), + expected: "".to_string(), + actual: "".to_string(), + mismatch: format!("Failed to verify the message metadata: {}", err) + } ], + interaction_id: interaction_id.clone() + }); + } } - (results, output) + } + (results, output) } fn verify_response( - response_body: &DynamicMessage, - response_metadata: &MetadataMap, - interaction: &SynchronousMessage, - file_desc: &FileDescriptorSet, - service_desc: &ServiceDescriptorProto, - method_desc: &MethodDescriptorProto, + response_body: &DynamicMessage, + response_metadata: &MetadataMap, + interaction: &SynchronousMessage, + file_desc: &FileDescriptorSet, + service_desc: &ServiceDescriptorProto, + method_desc: &MethodDescriptorProto ) -> anyhow::Result<(Vec, Vec)> { - let response = interaction.response.first().cloned().unwrap_or_default(); - if interaction.response.len() > 1 { - warn!("Interaction has more than one response, only comparing the first one"); - } - let expected_body = response.contents.value(); + let response = interaction.response.first().cloned() + .unwrap_or_default(); + if interaction.response.len() > 1 { + warn!("Interaction has more than one response, only comparing the first one"); + } + let expected_body = response.contents.value(); - let mut results = vec![]; - let mut output = vec![]; + let mut results = vec![]; + let mut output = vec![]; - if let Some(mut expected_body) = expected_body { - let ct = ContentType { - main_type: "application".into(), - sub_type: "grpc".into(), - ..ContentType::default() - }; - let mut actual_body = BytesMut::new(); - response_body.write_to(&mut actual_body)?; - match match_service( - service_desc.name.clone().unwrap_or_default().as_str(), - method_desc.name.clone().unwrap_or_default().as_str(), - file_desc, - &mut expected_body, - &mut actual_body.freeze(), - &response - .matching_rules - .rules_for_category("body") - .unwrap_or_default(), - true, - &ct, - ) { - Ok(result) => { - debug!("Match service result: {:?}", result); - match result { - BodyMatchResult::Ok => {} - BodyMatchResult::BodyTypeMismatch { message, .. } => { - results.push(VerificationMismatchResult::Error { - error: message, - interaction_id: interaction.id.clone(), - }); - } - BodyMatchResult::BodyMismatches(mismatches) => { - for (_, mismatches) in mismatches { - results.push(VerificationMismatchResult::Mismatches { - mismatches, - interaction_id: interaction.id.clone(), - }); - } - } - } - } - Err(err) => { - error!("Verifying the response failed with an error - {}", err); - results.push(VerificationMismatchResult::Error { - error: err.to_string(), - interaction_id: interaction.id.clone(), - }) + if let Some(mut expected_body) = expected_body { + let ct = ContentType { + main_type: "application".into(), + sub_type: "grpc".into(), + .. ContentType::default() + }; + let mut actual_body = BytesMut::new(); + response_body.write_to(&mut actual_body)?; + match match_service( + service_desc.name.clone().unwrap_or_default().as_str(), + method_desc.name.clone().unwrap_or_default().as_str(), + file_desc, + &mut expected_body, + &mut actual_body.freeze(), + &response.matching_rules.rules_for_category("body").unwrap_or_default(), + true, + &ct + ) { + Ok(result) => { + debug!("Match service result: {:?}", result); + match result { + BodyMatchResult::Ok => {} + BodyMatchResult::BodyTypeMismatch { message, .. } => { + results.push(VerificationMismatchResult::Error { error: message, interaction_id: interaction.id.clone() }); + } + BodyMatchResult::BodyMismatches(mismatches) => { + for (_, mismatches) in mismatches { + results.push(VerificationMismatchResult::Mismatches { mismatches, interaction_id: interaction.id.clone() }); } + } } + } + Err(err) => { + error!("Verifying the response failed with an error - {}", err); + results.push(VerificationMismatchResult::Error { error: err.to_string(), interaction_id: interaction.id.clone() }) + } } + } - if !response.metadata.is_empty() { - output.push(" with metadata".to_string()); - match verify_metadata(response_metadata, &response) { - Ok((result, md_output)) => { - if !result.result { - results.push(VerificationMismatchResult::Mismatches { - mismatches: result.mismatches, - interaction_id: interaction.id.clone(), - }); - } - output.extend(md_output); - } - Err(err) => { - results.push(VerificationMismatchResult::Mismatches { - mismatches: vec![Mismatch::MetadataMismatch { - key: "".to_string(), - expected: "".to_string(), - actual: "".to_string(), - mismatch: format!("Failed to verify the message metadata: {}", err), - }], - interaction_id: interaction.id.clone(), - }); - } + if !response.metadata.is_empty() { + output.push(" with metadata".to_string()); + match verify_metadata(response_metadata, &response) { + Ok((result, md_output)) => { + if !result.result { + results.push(VerificationMismatchResult::Mismatches { + mismatches: result.mismatches, + interaction_id: interaction.id.clone(), + }); } + output.extend(md_output); + } + Err(err) => { + results.push(VerificationMismatchResult::Mismatches { + mismatches: vec![ Mismatch::MetadataMismatch { + key: "".to_string(), + expected: "".to_string(), + actual: "".to_string(), + mismatch: format!("Failed to verify the message metadata: {}", err) + } ], + interaction_id: interaction.id.clone() + }); + } } + } - Ok((results, output)) + Ok((results, output)) } #[instrument(level = "trace")] fn verify_metadata( - metadata: &MetadataMap, - response: &MessageContents, + metadata: &MetadataMap, + response: &MessageContents ) -> anyhow::Result<(MetadataMatchResult, Vec)> { - let rules = response - .matching_rules - .rules_for_category("metadata") - .unwrap_or_default(); - let plugin_config = hashmap! {}; - let context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys, &rules, &plugin_config); - compare_metadata(&response.metadata, metadata, &context) + let rules = response.matching_rules.rules_for_category("metadata").unwrap_or_default(); + let plugin_config = hashmap!{}; + let context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys, + &rules, &plugin_config); + compare_metadata(&response.metadata, metadata, &context) } async fn make_grpc_request( - request: Request, - config: &HashMap, - metadata: &HashMap, - file_desc: &FileDescriptorSet, - input_desc: &DescriptorProto, - output_desc: &DescriptorProto, - interaction: &SynchronousMessage, + request: Request, + config: &HashMap, + metadata: &HashMap, + file_desc: &FileDescriptorSet, + input_desc: &DescriptorProto, + output_desc: &DescriptorProto, + interaction: &SynchronousMessage ) -> anyhow::Result> { - let host = config - .get("host") - .map(json_to_string) - .unwrap_or_else(|| "[::1]".to_string()); - let port = json_to_num(config.get("port").cloned()).unwrap_or(8080); - let dest = format!("http://{}:{}", host, port); + let host = config.get("host") + .map(json_to_string) + .unwrap_or_else(|| "[::1]".to_string()); + let port = json_to_num(config.get("port").cloned()) + .unwrap_or(8080); + let dest = format!("http://{}:{}", host, port); - let request_path_data = metadata.get("request-path").ok_or_else(|| { - anyhow!("INTERNAL ERROR: request-path is not set in the request metadata") - })?; - let request_path = match &request_path_data.value { - Some(data) => match data { - proto::metadata_value::Value::NonBinaryValue(value) => { - proto_value_to_string(value).unwrap_or_default() - } - _ => { - return Err(anyhow!( - "INTERNAL ERROR: request-path is not set correctly in the request metadata" - )) - } - }, - None => { - return Err(anyhow!( - "INTERNAL ERROR: request-path is not set in the request metadata" - )) - } - }; - let path = http::uri::PathAndQuery::try_from(request_path)?; + let request_path_data = metadata.get("request-path") + .ok_or_else(|| anyhow!("INTERNAL ERROR: request-path is not set in the request metadata"))?; + let request_path = match &request_path_data.value { + Some(data) => match data { + proto::metadata_value::Value::NonBinaryValue(value) => proto_value_to_string(value).unwrap_or_default(), + _ => return Err(anyhow!("INTERNAL ERROR: request-path is not set correctly in the request metadata")) + } + None => return Err(anyhow!("INTERNAL ERROR: request-path is not set in the request metadata")) + }; + let path = http::uri::PathAndQuery::try_from(request_path)?; - debug!("Connecting to channel {}", dest); - let mut conn = tonic::transport::Endpoint::new(dest)?.connect().await?; - conn.ready().await?; + debug!("Connecting to channel {}", dest); + let mut conn = tonic::transport::Endpoint::new(dest)?.connect().await?; + conn.ready().await?; - debug!("Making gRPC request to {}", path); - let codec = PactCodec::new(file_desc, output_desc, input_desc, interaction); - let mut grpc = tonic::client::Grpc::new(conn); - grpc.unary(request, path, codec).await.map_err(|err| { - error!("gRPC request failed {:?}", err); - anyhow!(GrpcError { status: err }) + debug!("Making gRPC request to {}", path); + let codec = PactCodec::new(file_desc, output_desc, input_desc, interaction); + let mut grpc = tonic::client::Grpc::new(conn); + grpc.unary(request, path, codec).await + .map_err(|err| { + error!("gRPC request failed {:?}", err); + anyhow!(GrpcError { status: err }) }) } fn build_grpc_request( - body: &OptionalBody, - metadata: &HashMap, - file_desc: &FileDescriptorSet, - input_desc: &DescriptorProto, + body: &OptionalBody, + metadata: &HashMap, + file_desc: &FileDescriptorSet, + input_desc: &DescriptorProto ) -> anyhow::Result> { - let mut bytes = body.value().unwrap_or_default(); - let message_fields = decode_message(&mut bytes, input_desc, file_desc)?; - let mut request = Request::new(DynamicMessage::new(&message_fields, file_desc)); - let request_metadata = request.metadata_mut(); - for (key, md) in metadata { - if key != "request-path" { - if let Some(value) = &md.value { - match value { - proto::metadata_value::Value::NonBinaryValue(value) => { - let str_value = proto_value_to_string(value).unwrap_or_default(); - match str_value.parse::>() { - Ok(value) => match key.parse::>() { - Ok(key) => { - request_metadata.insert(key, value.clone()); - } - Err(err) => { - warn!("Protobuf metadata key '{}' is not valid - {}", key, err); - } - }, - Err(err) => { - warn!( - "Could not parse Protobuf metadata value for key '{}' - {}", - key, err - ); - } - } - } - proto::metadata_value::Value::BinaryValue(value) => { - match key.parse::>() { - Ok(key) => { - request_metadata.insert_bin(key, MetadataValue::from_bytes(value)); - } - Err(err) => { - warn!("Protobuf metadata key '{}' is not valid - {}", key, err); - } - } - } + let mut bytes = body.value().unwrap_or_default(); + let message_fields = decode_message(&mut bytes, input_desc, file_desc)?; + let mut request = Request::new(DynamicMessage::new(&message_fields, file_desc)); + let request_metadata = request.metadata_mut(); + for (key, md) in metadata { + if key != "request-path" { + if let Some(value) = &md.value { + match value { + proto::metadata_value::Value::NonBinaryValue(value) => { + let str_value = proto_value_to_string(value).unwrap_or_default(); + match str_value.parse::>() { + Ok(value) => match key.parse::>() { + Ok(key) => { + request_metadata.insert(key, value.clone()); } + Err(err) => { + warn!("Protobuf metadata key '{}' is not valid - {}", key, err); + } + } + Err(err) => { + warn!("Could not parse Protobuf metadata value for key '{}' - {}", key, err); + } + } + } + proto::metadata_value::Value::BinaryValue(value) => match key.parse::>() { + Ok(key) => { + request_metadata.insert_bin(key, MetadataValue::from_bytes(value)); + } + Err(err) => { + warn!("Protobuf metadata key '{}' is not valid - {}", key, err); } + } } + } } - Ok(request) + } + Ok(request) }