diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index df35e7f6..fadf47ea 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -66,7 +66,7 @@ jobs: touch actix-consumer/build.rs touch stub-consumer/build.rs cargo build - - run: cargo nextest run --verbose + - run: cargo nextest run --verbose --all-features - run: cargo test --doc hack: diff --git a/book/src/grpc.md b/book/src/grpc.md index 4b466ac4..8f123bae 100644 --- a/book/src/grpc.md +++ b/book/src/grpc.md @@ -12,7 +12,8 @@ The API looks like this: "protoFile": "path/to/grpc.proto", // protobuf file where gRPC service & protobuf messages are defined "grpcRequest": { "message": "Pet", // name of the body's message in 'protoFile' - "path": "createDog", // name of the gRPC service to mock, supports Regex + "service": "PetStore", // (optional) name of the gRPC service to mock, supports Regex + "method": "createDog", // (optional) name of the gRPC method to mock, supports Regex "bodyPatterns": [ { "equalToJson": { // literally the same matchers as in http @@ -28,7 +29,9 @@ The API looks like this: "body": { // literally the same as in http, supports templating too "id": 1234, "name": "{{jsonPath request.body '$.name'}}", - "race": "{{jsonPath request.body '$.race'}}" + "race": "{{jsonPath request.body '$.race'}}", + "action": "{{request.method}}", // only 2 differences with standard templates + "service": "{{request.service}}" }, "transformers": [ // required for response templating "response-template" diff --git a/lib/src/error.rs b/lib/src/error.rs index 383883bf..338a897c 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -63,6 +63,8 @@ pub enum StubrError { ProtoMessageNotFound(String, std::path::PathBuf), #[error("A protobuf 'message' has to be defined in stub")] MissingProtoMessage, + #[error("Unexpected invalid gRPC request")] + InvalidGrpcRequest, } impl From for handlebars::RenderError { diff --git a/lib/src/model/grpc/request/method.rs b/lib/src/model/grpc/request/method.rs new file mode 100644 index 00000000..79b3077b --- /dev/null +++ b/lib/src/model/grpc/request/method.rs @@ -0,0 +1,58 @@ +use crate::wiremock::{Match, Request}; +use crate::{StubrError, StubrResult}; + +pub struct GrpcMethodMatcher(regex::Regex); + +impl GrpcMethodMatcher { + pub fn try_new(path: &str) -> StubrResult { + Ok(Self(regex::Regex::new(path)?)) + } +} + +pub struct GrpcSvcMatcher(regex::Regex); + +impl GrpcSvcMatcher { + pub fn try_new(path: &str) -> StubrResult { + Ok(Self(regex::Regex::new(path)?)) + } +} + +pub(crate) struct GrpcMethod<'a>(pub(crate) &'a str); + +impl<'a> From<&'a str> for GrpcMethod<'a> { + fn from(value: &'a str) -> Self { + Self(value) + } +} + +pub(crate) struct GrpcSvc<'a>(pub(crate) &'a str); + +impl<'a> TryFrom<&'a str> for GrpcSvc<'a> { + type Error = StubrError; + + fn try_from(value: &'a str) -> StubrResult { + let svc = value.split('.').last().ok_or(StubrError::InvalidGrpcRequest)?; + Ok(Self(svc)) + } +} + +impl Match for GrpcMethodMatcher { + fn matches(&self, request: &Request) -> bool { + parse_path(request) + .map(|(method, _)| self.0.is_match(method.0)) + .unwrap_or_default() + } +} + +impl Match for GrpcSvcMatcher { + fn matches(&self, request: &Request) -> bool { + parse_path(request).map(|(_, svc)| self.0.is_match(svc.0)).unwrap_or_default() + } +} + +pub(crate) fn parse_path(request: &Request) -> StubrResult<(GrpcMethod, GrpcSvc)> { + let mut paths = request.url.path_segments().ok_or(StubrError::InvalidGrpcRequest)?; + let svc: GrpcSvc = paths.next().ok_or(StubrError::InvalidGrpcRequest)?.try_into()?; + let method: GrpcMethod = paths.next().ok_or(StubrError::InvalidGrpcRequest)?.into(); + Ok((method, svc)) +} diff --git a/lib/src/model/grpc/request/mod.rs b/lib/src/model/grpc/request/mod.rs index 4e901e70..92497446 100644 --- a/lib/src/model/grpc/request/mod.rs +++ b/lib/src/model/grpc/request/mod.rs @@ -3,7 +3,6 @@ use std::{hash::Hash, path::PathBuf}; use protobuf::reflect::MessageDescriptor; use crate::{ - error::StubrResult, model::{ grpc::proto::parse_message_descriptor, request::{ @@ -12,7 +11,7 @@ use crate::{ }, }, wiremock::MockBuilder, - StubrError, + StubrError, StubrResult, }; pub mod binary_eq; @@ -21,7 +20,7 @@ pub mod eq_relaxed; pub mod json_path; pub mod json_path_contains; pub mod json_path_eq; -pub mod path; +pub mod method; #[derive(Debug, Clone, Hash, Default, serde::Serialize, serde::Deserialize)] #[serde(default, rename_all = "camelCase")] @@ -29,9 +28,12 @@ pub struct GrpcRequestStub { /// Name of the message definition within protobuf #[serde(skip_serializing_if = "Option::is_none")] pub message: Option, + /// Name of the gRPC method + #[serde(skip_serializing_if = "Option::is_none")] + pub method: Option, /// Name of the gRPC service #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, + pub service: Option, /// request body matchers #[serde(skip_serializing_if = "Option::is_none")] pub body_patterns: Option>, @@ -40,8 +42,11 @@ pub struct GrpcRequestStub { impl GrpcRequestStub { pub fn try_new(request: &GrpcRequestStub, proto_file: Option<&PathBuf>) -> StubrResult { let mut mock = MockBuilder::from(&HttpMethodStub(Verb::Post)); - if let Some(path) = request.path.as_ref() { - mock = mock.and(path::GrpcPathMatcher::try_new(path)?); + if let Some(method) = request.method.as_ref() { + mock = mock.and(method::GrpcMethodMatcher::try_new(method)?); + } + if let Some(svc) = request.service.as_ref() { + mock = mock.and(method::GrpcSvcMatcher::try_new(svc)?); } if let Some(matchers) = request.body_patterns.as_ref() { let proto_file = proto_file.ok_or(StubrError::MissingProtoFile)?; diff --git a/lib/src/model/grpc/request/path.rs b/lib/src/model/grpc/request/path.rs deleted file mode 100644 index 2be2c65d..00000000 --- a/lib/src/model/grpc/request/path.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::wiremock::{Match, Request}; -use crate::StubrResult; - -pub struct GrpcPathMatcher(regex::Regex); - -impl GrpcPathMatcher { - pub fn try_new(path: &str) -> StubrResult { - Ok(Self(regex::Regex::new(path)?)) - } - - pub fn parse_svc_name(request: &Request) -> &str { - const GRPC_PREFIX: &str = "grpc.Grpc"; - let mut paths = request.url.path_segments().expect("Invalid gRPC request"); - let grpc_prefix = paths - .next() - .unwrap_or_else(|| panic!("gRPC request should have prefix '{GRPC_PREFIX}'")); - assert_eq!( - grpc_prefix, GRPC_PREFIX, - "expected first path segment to be '{}' but was '{}'", - GRPC_PREFIX, grpc_prefix - ); - paths.next().expect("gRPC request does not have a service name") - } -} - -impl Match for GrpcPathMatcher { - fn matches(&self, request: &Request) -> bool { - self.0.is_match(Self::parse_svc_name(request)) - } -} diff --git a/lib/src/model/response/template/data.rs b/lib/src/model/response/template/data.rs index d1edb2ee..9dd3af42 100644 --- a/lib/src/model/response/template/data.rs +++ b/lib/src/model/response/template/data.rs @@ -1,5 +1,4 @@ use crate::{wiremock::Request as WiremockRequest, StubrResult}; -use http_types::Method; use serde_json::Value; use super::req_ext::{Headers, Queries, RequestExt}; @@ -12,30 +11,46 @@ pub struct HandlebarsData<'a> { pub is_verify: bool, } +#[derive(Debug, Clone, serde::Serialize)] +#[serde(untagged)] +pub enum MethodData<#[cfg(feature = "grpc")] 'a> { + Http(http_types::Method), + #[cfg(feature = "grpc")] + Grpc(&'a str), +} + #[derive(serde::Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RequestData<'a> { path: &'a str, + #[cfg(not(feature = "grpc"))] + method: MethodData, + #[cfg(feature = "grpc")] + method: MethodData<'a>, path_segments: Option>, url: &'a str, port: Option, - method: Method, body: Option, query: Option>, headers: Option>, + #[serde(rename = "service")] + #[cfg(feature = "grpc")] + grpc_service: Option<&'a str>, } impl Default for RequestData<'_> { fn default() -> Self { Self { path: "", + method: MethodData::Http(http_types::Method::Get), path_segments: None, url: "", port: None, - method: Method::Get, body: None, query: None, headers: None, + #[cfg(feature = "grpc")] + grpc_service: None, } } } @@ -45,15 +60,18 @@ impl<'a> RequestData<'a> { pub fn try_from_grpc_request(req: &'a WiremockRequest, md: &protobuf::reflect::MessageDescriptor) -> StubrResult { let body = crate::model::grpc::request::proto_to_json_str(req.body.as_slice(), md)?; let body = serde_json::from_str(&body)?; + let (grpc_method, grpc_svc) = crate::model::grpc::request::method::parse_path(req)?; Ok(Self { - path: crate::model::grpc::request::path::GrpcPathMatcher::parse_svc_name(req), + path: "", path_segments: None, + method: MethodData::Grpc(grpc_method.0), url: "", port: None, - method: Method::Post, body: Some(body), query: None, headers: None, + #[cfg(feature = "grpc")] + grpc_service: Some(grpc_svc.0), }) } } @@ -65,10 +83,11 @@ impl<'a> From<&'a WiremockRequest> for RequestData<'a> { path_segments: req.path_segments(), url: req.uri(), port: req.url.port(), - method: req.method, + method: MethodData::Http(req.method), body: req.body(), query: req.queries(), headers: req.headers(), + ..Default::default() } } } @@ -81,16 +100,17 @@ impl<'a> From<&'a mut http_types::Request> for RequestData<'a> { path_segments: req.path_segments(), url: req.uri(), port: req.url().port(), - method: req.method(), + method: MethodData::Http(req.method()), body, query: req.queries(), headers: req.headers(), + ..Default::default() } } } #[cfg(test)] -mod request_data_tests { +mod tests { use std::{borrow::Cow, collections::HashMap, str::FromStr}; use http_types::{ @@ -146,9 +166,9 @@ mod request_data_tests { #[test] fn should_take_request_method() { let req = request("https://localhost", Some(Method::Get), &[], None); - assert_eq!(RequestData::from(&req).method, Method::Get); + assert!(matches!(RequestData::from(&req).method, MethodData::Http(Method::Get))); let req = request("https://localhost", Some(Method::Post), &[], None); - assert_eq!(RequestData::from(&req).method, Method::Post); + assert!(matches!(RequestData::from(&req).method, MethodData::Http(Method::Post))); } #[test] @@ -292,9 +312,9 @@ mod request_data_tests { #[test] fn should_take_request_method() { let mut req = request("https://localhost", Some(Method::Get), &[], None); - assert_eq!(RequestData::from(&mut req).method, Method::Get); + assert!(matches!(RequestData::from(&mut req).method, MethodData::Http(Method::Get))); let mut req = request("https://localhost", Some(Method::Post), &[], None); - assert_eq!(RequestData::from(&mut req).method, Method::Post); + assert!(matches!(RequestData::from(&mut req).method, MethodData::Http(Method::Post))); } #[test] diff --git a/lib/tests/grpc/mod.rs b/lib/tests/grpc/mod.rs index 2d371797..b0f79f7b 100644 --- a/lib/tests/grpc/mod.rs +++ b/lib/tests/grpc/mod.rs @@ -14,6 +14,7 @@ pub mod resp; #[async_trait::async_trait(? Send)] pub trait GrpcConnect { async fn connect(&self) -> grpc_client::GrpcClient; + async fn connect_other(&self) -> grpc_other_client::GrpcOtherClient; } #[async_trait::async_trait(? Send)] @@ -21,4 +22,8 @@ impl GrpcConnect for stubr::Stubr { async fn connect(&self) -> grpc_client::GrpcClient { grpc::grpc_client::GrpcClient::connect(self.uri()).await.unwrap() } + + async fn connect_other(&self) -> grpc_other_client::GrpcOtherClient { + grpc::grpc_other_client::GrpcOtherClient::connect(self.uri()).await.unwrap() + } } diff --git a/lib/tests/grpc/protos/grpc.proto b/lib/tests/grpc/protos/grpc.proto index 0527dabc..bd8e3123 100644 --- a/lib/tests/grpc/protos/grpc.proto +++ b/lib/tests/grpc/protos/grpc.proto @@ -32,7 +32,13 @@ service Grpc { rpc respTemplate (Template) returns (Template) {} } +service GrpcOther { + rpc reqPathEq (EmptyOther) returns (EmptyOther) {} + rpc reqPathEqRegex (EmptyOther) returns (EmptyOther) {} +} + message Empty {} +message EmptyOther {} // see https://developers.google.com/protocol-buffers/docs/proto3#scalar message Scalar { diff --git a/lib/tests/grpc/req/path.rs b/lib/tests/grpc/req/path.rs index 3b3124b9..a3c2c6d8 100644 --- a/lib/tests/grpc/req/path.rs +++ b/lib/tests/grpc/req/path.rs @@ -1,80 +1,151 @@ use crate::grpc::*; use asserhttp::grpc::*; -#[tokio::test] -#[stubr::mock("grpc/req/path/eq.json")] -async fn should_match_path() { - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_eq(req) - .await - .expect_status_ok() - .expect_body(Empty::default()); -} +mod endpoint { + use super::*; -#[tokio::test] -#[stubr::mock("grpc/req/path/eq.json")] -async fn should_fail_when_path_mismatch() { - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_not_eq(req) - .await - .expect_status_error(Code::NotFound); -} + #[tokio::test] + #[stubr::mock("grpc/req/method/eq.json")] + async fn should_match_method() { + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); + } + + #[tokio::test] + #[stubr::mock("grpc/req/method/eq.json")] + async fn should_fail_when_method_mismatch() { + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_not_eq(req) + .await + .expect_status_error(Code::NotFound); + } + + #[tokio::test] + #[stubr::mock("grpc/req/method/absent.json")] + async fn should_match_when_no_method_defined() { + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); + + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_not_eq(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); + } -#[tokio::test] -#[stubr::mock("grpc/req/path/no-path.json")] -async fn should_match_when_no_path_defined() { - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_eq(req) - .await - .expect_status_ok() - .expect_body(Empty::default()); + #[tokio::test] + #[stubr::mock("grpc/req/method/regex.json")] + async fn should_match_regex() { + // both methods match `reqPathEq(.*)` + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_eq_regex(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_not_eq(req) - .await - .expect_status_ok() - .expect_body(Empty::default()); + // `reqPathNotEq` does not match `reqPathEq(.*)` + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_not_eq(req) + .await + .expect_status_error(Code::NotFound); + } } -#[tokio::test] -#[stubr::mock("grpc/req/path/regex.json")] -async fn should_match_regex() { - // both paths match `reqPathEq(.*)` - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_eq(req) - .await - .expect_status_ok() - .expect_body(Empty::default()); - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_eq_regex(req) - .await - .expect_status_ok() - .expect_body(Empty::default()); +mod svc { + use super::*; + + #[tokio::test] + #[stubr::mock("grpc/req/svc/eq.json")] + async fn should_match_svc_name() { + let req = tonic::Request::new(EmptyOther::default()); + stubr + .connect_other() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(EmptyOther::default()); + } + + #[tokio::test] + #[stubr::mock("grpc/req/svc/eq.json")] + async fn should_fail_when_svc_mismatch() { + let req = tonic::Request::new(Empty::default()); + stubr.connect().await.req_path_eq(req).await.expect_status_error(Code::NotFound); + } + + #[tokio::test] + #[stubr::mock("grpc/req/svc/absent.json")] + async fn should_match_when_no_svc_defined() { + let req = tonic::Request::new(EmptyOther::default()); + stubr + .connect_other() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(EmptyOther::default()); + + let req = tonic::Request::new(Empty::default()); + stubr + .connect() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(Empty::default()); + } - // `reqPathNotEq` does not match `reqPathEq(.*)` - let req = tonic::Request::new(Empty::default()); - stubr - .connect() - .await - .req_path_not_eq(req) - .await - .expect_status_error(Code::NotFound); + #[tokio::test] + #[stubr::mock("grpc/req/svc/regex.json")] + async fn should_match_regex() { + let req = tonic::Request::new(EmptyOther::default()); + stubr + .connect_other() + .await + .req_path_eq(req) + .await + .expect_status_ok() + .expect_body(EmptyOther::default()); + let req = tonic::Request::new(EmptyOther::default()); + stubr + .connect_other() + .await + .req_path_eq_regex(req) + .await + .expect_status_ok() + .expect_body(EmptyOther::default()); + } } diff --git a/lib/tests/grpc/resp/body.rs b/lib/tests/grpc/resp/body.rs index b9db9c51..2a6f0bdd 100644 --- a/lib/tests/grpc/resp/body.rs +++ b/lib/tests/grpc/resp/body.rs @@ -182,9 +182,9 @@ pub mod template { } #[tokio::test] - #[stubr::mock("grpc/resp/body/template-path.json")] - async fn should_template_request_path() { - // should return request path + #[stubr::mock("grpc/resp/body/template-method.json")] + async fn should_template_request_method() { + // should return request method let req = tonic::Request::new(Template { name: "alice".to_string() }); stubr .connect() @@ -196,4 +196,18 @@ pub mod template { name: "respTemplate".to_string(), }); } + + #[tokio::test] + #[stubr::mock("grpc/resp/body/template-service.json")] + async fn should_template_request_service() { + // should return request method + let req = tonic::Request::new(Template { name: "alice".to_string() }); + stubr + .connect() + .await + .resp_template(req) + .await + .expect_status_ok() + .expect_body(Template { name: "Grpc".to_string() }); + } } diff --git a/lib/tests/resp/template/url.rs b/lib/tests/resp/template/url.rs index 9d8d113d..b60a6f73 100644 --- a/lib/tests/resp/template/url.rs +++ b/lib/tests/resp/template/url.rs @@ -1,6 +1,5 @@ use asserhttp::*; use serde_json::json; -use surf::get; use stubr::Config; @@ -9,7 +8,7 @@ use crate::utils::*; #[async_std::test] #[stubr::mock("resp/template/url/path.json")] async fn should_template_request_path() { - get(stubr.path_query("/api/path", "name", "beltram")) + surf::get(stubr.path_query("/api/path", "name", "beltram")) .await .expect_status_ok() .expect_body_text_eq("/api/path") @@ -19,7 +18,7 @@ async fn should_template_request_path() { #[async_std::test] #[stubr::mock("resp/template/url/url.json")] async fn should_template_request_url() { - get(stubr.path_query("/api/path", "name", "beltram")) + surf::get(stubr.path_query("/api/path", "name", "beltram")) .await .expect_status_ok() .expect_body_text_eq("/api/path?name=beltram") @@ -34,7 +33,7 @@ async fn should_template_request_port() { ..Default::default() }; let stubr = Stubr::start_with("tests/stubs/resp/template/url/port.json", cfg).await; - get(stubr.path("/api/port")) + surf::get(stubr.path("/api/port")) .await .expect_status_ok() .expect_body_text_eq("59000") @@ -94,7 +93,7 @@ async fn should_template_request_method_lowercase() { #[async_std::test] #[stubr::mock("resp/template/url/path-segments.json")] async fn should_template_request_path_segments() { - get(stubr.path("/one/two/three")) + surf::get(stubr.path("/one/two/three")) .await .expect_status_ok() .expect_body_text_eq("two") @@ -107,12 +106,12 @@ mod path_segment_types { #[async_std::test] #[stubr::mock("resp/template/url/path-segments-type.json")] async fn should_template_request_path_segments_int() { - get(stubr.path("/path/segments/1")) + surf::get(stubr.path("/path/segments/1")) .await .expect_status_ok() .expect_content_type_json() .expect_body_json_eq(json!({"path": 1})); - get(stubr.path("/path/segments/-1")) + surf::get(stubr.path("/path/segments/-1")) .await .expect_status_ok() .expect_content_type_json() @@ -122,12 +121,12 @@ mod path_segment_types { #[async_std::test] #[stubr::mock("resp/template/url/path-segments-type.json")] async fn should_template_request_path_segments_boolean() { - get(stubr.path("/path/segments/true")) + surf::get(stubr.path("/path/segments/true")) .await .expect_status_ok() .expect_content_type_json() .expect_body_json_eq(json!({"path": true})); - get(stubr.path("/path/segments/false")) + surf::get(stubr.path("/path/segments/false")) .await .expect_status_ok() .expect_content_type_json() @@ -137,7 +136,7 @@ mod path_segment_types { #[async_std::test] #[stubr::mock("resp/template/url/path-segments-type.json")] async fn should_template_request_path_segments_null() { - get(stubr.path("/path/segments/null")) + surf::get(stubr.path("/path/segments/null")) .await .expect_status_ok() .expect_content_type_json() diff --git a/lib/tests/stubs/grpc/req/path/no-path.json b/lib/tests/stubs/grpc/req/method/absent.json similarity index 100% rename from lib/tests/stubs/grpc/req/path/no-path.json rename to lib/tests/stubs/grpc/req/method/absent.json diff --git a/lib/tests/stubs/grpc/req/path/eq.json b/lib/tests/stubs/grpc/req/method/eq.json similarity index 78% rename from lib/tests/stubs/grpc/req/path/eq.json rename to lib/tests/stubs/grpc/req/method/eq.json index 16aaac6a..9fcd15c2 100644 --- a/lib/tests/stubs/grpc/req/path/eq.json +++ b/lib/tests/stubs/grpc/req/method/eq.json @@ -1,7 +1,7 @@ { "protoFile": "tests/grpc/protos/grpc.proto", "grpcRequest": { - "path": "reqPathEq" + "method": "reqPathEq" }, "grpcResponse": {} } diff --git a/lib/tests/stubs/grpc/req/method/regex.json b/lib/tests/stubs/grpc/req/method/regex.json new file mode 100644 index 00000000..82093f4b --- /dev/null +++ b/lib/tests/stubs/grpc/req/method/regex.json @@ -0,0 +1,7 @@ +{ + "protoFile": "tests/grpc/protos/grpc.proto", + "grpcRequest": { + "method": "reqPathEq(.*)" + }, + "grpcResponse": {} +} diff --git a/lib/tests/stubs/grpc/req/svc/absent.json b/lib/tests/stubs/grpc/req/svc/absent.json new file mode 100644 index 00000000..4b0ba352 --- /dev/null +++ b/lib/tests/stubs/grpc/req/svc/absent.json @@ -0,0 +1,5 @@ +{ + "protoFile": "tests/grpc/protos/grpc.proto", + "grpcRequest": {}, + "grpcResponse": {} +} diff --git a/lib/tests/stubs/grpc/req/svc/eq.json b/lib/tests/stubs/grpc/req/svc/eq.json new file mode 100644 index 00000000..1a94e8a7 --- /dev/null +++ b/lib/tests/stubs/grpc/req/svc/eq.json @@ -0,0 +1,7 @@ +{ + "protoFile": "tests/grpc/protos/grpc.proto", + "grpcRequest": { + "service": "GrpcOther" + }, + "grpcResponse": {} +} diff --git a/lib/tests/stubs/grpc/req/path/regex.json b/lib/tests/stubs/grpc/req/svc/regex.json similarity index 77% rename from lib/tests/stubs/grpc/req/path/regex.json rename to lib/tests/stubs/grpc/req/svc/regex.json index 6658c89d..0666665d 100644 --- a/lib/tests/stubs/grpc/req/path/regex.json +++ b/lib/tests/stubs/grpc/req/svc/regex.json @@ -1,7 +1,7 @@ { "protoFile": "tests/grpc/protos/grpc.proto", "grpcRequest": { - "path": "reqPathEq(.*)" + "service": "Grpc(.*)" }, "grpcResponse": {} } diff --git a/lib/tests/stubs/grpc/resp/body/template-path.json b/lib/tests/stubs/grpc/resp/body/template-method.json similarity index 84% rename from lib/tests/stubs/grpc/resp/body/template-path.json rename to lib/tests/stubs/grpc/resp/body/template-method.json index 83456ea4..0811d147 100644 --- a/lib/tests/stubs/grpc/resp/body/template-path.json +++ b/lib/tests/stubs/grpc/resp/body/template-method.json @@ -4,7 +4,7 @@ "grpcResponse": { "message": "Template", "body": { - "name": "{{request.path}}" + "name": "{{request.method}}" }, "transformers": [ "response-template" diff --git a/lib/tests/stubs/grpc/resp/body/template-service.json b/lib/tests/stubs/grpc/resp/body/template-service.json new file mode 100644 index 00000000..e90358d3 --- /dev/null +++ b/lib/tests/stubs/grpc/resp/body/template-service.json @@ -0,0 +1,13 @@ +{ + "protoFile": "tests/grpc/protos/grpc.proto", + "grpcRequest": {}, + "grpcResponse": { + "message": "Template", + "body": { + "name": "{{request.service}}" + }, + "transformers": [ + "response-template" + ] + } +} diff --git a/schemas/stubr.schema.json b/schemas/stubr.schema.json index e9db6be1..39a80462 100644 --- a/schemas/stubr.schema.json +++ b/schemas/stubr.schema.json @@ -411,8 +411,16 @@ "description": "Name of the Protobuf definition in 'protoFile'", "type": "string" }, - "path": { - "description": "Name of the gRPC service", + "method": { + "description": "Name of the gRPC method to match against", + "type": "string", + "format": "regex", + "x-intellij-language-injection": { + "language": "RegExp" + } + }, + "service": { + "description": "Name of the gRPC service to match against", "type": "string", "format": "regex", "x-intellij-language-injection": {