Skip to content
This repository was archived by the owner on Sep 22, 2022. It is now read-only.

Commit 47b436c

Browse files
committed
closes #26
1 parent 26902b8 commit 47b436c

File tree

6 files changed

+78
-36
lines changed

6 files changed

+78
-36
lines changed

README.md

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![Stability: Maintenance](https://masterminds.github.io/stability/maintenance.svg)](https://masterminds.github.io/stability/maintenance.html)
22
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/adven27/grpc-wiremock?label=build&logo=docker)](https://hub.docker.com/repository/docker/adven27/grpc-wiremock/builds)
3-
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/adven27/grpc-wiremock/1.3.1?logo=docker)](https://hub.docker.com/repository/docker/adven27/grpc-wiremock/general)
3+
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/adven27/grpc-wiremock/1.3.2?logo=docker)](https://hub.docker.com/repository/docker/adven27/grpc-wiremock/general)
44

55
# Overview
66
grpc-wiremock is a **mock server** for **GRPC** services implemented as a wrapper around the [WireMock](http://wiremock.org) http server.
@@ -26,7 +26,7 @@ curl -X POST http://localhost:8888/__admin/mappings \
2626
"url": "/BalanceService/getUserBalance",
2727
"headers": {"withAmount": {"matches": "\\d+\\.?\\d*"} },
2828
"bodyPatterns" : [ {
29-
"equalToJson" : { "id": "1", "currency": "EUR" }
29+
"equalToJson" : { "userId": "1", "currency": "EUR" }
3030
} ]
3131
},
3232
"response": {
@@ -212,7 +212,6 @@ Also in docker-compose:
212212
```
213213
<sub>*gzip compression supported by default</sub>
214214

215-
216215
### 6. Use in load testing
217216

218217
To increase performance some Wiremock related options may be tuned either directly or by enabling the "load" profile.
@@ -228,3 +227,23 @@ docker run \
228227
-e WIREMOCK_ASYNC-RESPONSE-THREADS=10 \
229228
adven27/grpc-wiremock
230229
```
230+
231+
### 7. Preserving proto field names in stubs
232+
233+
By default, stub mappings must have proto fields references in lowerCamlCase, e.g. proto field `user_id` must be referenced as:
234+
235+
```json
236+
{
237+
"request": {
238+
"method": "POST",
239+
"url": "/BalanceService/getUserBalance",
240+
"bodyPatterns": [{"equalToJson": { "userId": "1" }}]
241+
}
242+
}
243+
```
244+
245+
To preserve proto field names the following env variable could be used:
246+
247+
```posh
248+
docker run -e JSON_PRESERVING_PROTO_FIELD_NAMES=true adven27/grpc-wiremock
249+
```

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
id 'org.springframework.boot' version '2.5.5'
2+
id 'org.springframework.boot' version '2.5.6'
33
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
44
id 'java'
55
id 'com.google.protobuf' version '0.8.16'

example/proto/wallet.proto

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ service BalanceService {
1515
}
1616

1717
message UserBalanceRequest {
18-
uint64 id = 1;
18+
uint64 user_id = 1;
1919
string currency = 2;
2020
}
2121

@@ -47,7 +47,7 @@ message TransactionResponse {
4747
uint64 userId = 2;
4848
string currency = 3;
4949
common.proto.ProtoBigDecimal amount = 4;
50-
common.proto.ProtoTimestamp transactionDate = 5;
50+
common.proto.ProtoTimestamp transaction_date = 5;
5151
}
5252

5353
message SearchTransactionRequest {

src/main/java/io/adven/grpc/wiremock/HttpMock.java

+10-29
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
import com.github.tomakehurst.wiremock.WireMockServer;
44
import com.github.tomakehurst.wiremock.standalone.CommandLineOptions;
5-
import com.google.protobuf.AbstractMessage;
6-
import com.google.protobuf.InvalidProtocolBufferException;
75
import com.google.protobuf.Message;
86
import com.google.protobuf.MessageOrBuilder;
9-
import com.google.protobuf.util.JsonFormat;
107
import io.adven.grpc.wiremock.configurer.WiremockConfigurer;
118
import org.slf4j.Logger;
129
import org.slf4j.LoggerFactory;
@@ -16,7 +13,6 @@
1613
import java.io.IOException;
1714
import java.io.InputStream;
1815
import java.io.UncheckedIOException;
19-
import java.lang.reflect.InvocationTargetException;
2016
import java.net.URI;
2117
import java.net.http.HttpClient;
2218
import java.net.http.HttpRequest;
@@ -33,11 +29,13 @@ public class HttpMock {
3329
private static final Logger LOG = LoggerFactory.getLogger(HttpMock.class);
3430
private static final String PREFIX = "wiremock_";
3531
private final WiremockConfigurer configurer;
32+
private final ProtoJsonConverter converter;
3633
private WireMockServer server;
3734
private final HttpClient httpClient = HttpClient.newHttpClient();
3835

39-
public HttpMock(WiremockConfigurer configurer) {
36+
public HttpMock(WiremockConfigurer configurer, ProtoJsonConverter converter) {
4037
this.configurer = configurer;
38+
this.converter = converter;
4139
}
4240

4341
public void start() {
@@ -90,7 +88,8 @@ public Response request(String path, Object message, Map<String, String> headers
9088
.headers(headers.entrySet().stream().flatMap(e -> Stream.of(e.getKey(), e.getValue())).toArray(String[]::new))
9189
.build(),
9290
HttpResponse.BodyHandlers.ofInputStream()
93-
)
91+
),
92+
converter
9493
);
9594
}
9695

@@ -100,14 +99,16 @@ public Response request(String path, Object message) throws IOException, Interru
10099

101100
public static final class Response {
102101
private final HttpResponse<InputStream> httpResponse;
102+
private final ProtoJsonConverter converter;
103103

104-
public Response(HttpResponse<InputStream> httpResponse) {
104+
public Response(HttpResponse<InputStream> httpResponse, ProtoJsonConverter converter) {
105105
this.httpResponse = httpResponse;
106+
this.converter = converter;
106107
}
107108

108109
public Message getMessage(Class<?> aClass) {
109110
if (httpResponse.statusCode() == 200) {
110-
return ProtoJsonUtil.fromJson(getBody(), aClass);
111+
return converter.fromJson(getBody(), aClass);
111112
}
112113
throw new BadHttpResponseException(httpResponse.statusCode(), getBody());
113114
}
@@ -130,26 +131,6 @@ private boolean isGzip() {
130131
}
131132

132133
private HttpRequest.BodyPublisher asJson(Object arg) throws IOException {
133-
return HttpRequest.BodyPublishers.ofString(ProtoJsonUtil.toJson((MessageOrBuilder) arg));
134-
}
135-
136-
private static final class ProtoJsonUtil {
137-
static String toJson(MessageOrBuilder messageOrBuilder) throws IOException {
138-
return JsonFormat.printer().print(messageOrBuilder);
139-
}
140-
141-
@SuppressWarnings({"unchecked", "rawtypes"})
142-
static <T extends Message> T fromJson(String json, Class<?> clazz) {
143-
try {
144-
LOG.info("Converting to {} json:\n{}", clazz, json);
145-
AbstractMessage.Builder builder = (AbstractMessage.Builder) clazz.getMethod("newBuilder").invoke(null);
146-
JsonFormat.parser().merge(json, builder);
147-
T result = (T) builder.build();
148-
LOG.info("Grpc response:\n{}", result);
149-
return result;
150-
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InvalidProtocolBufferException e) {
151-
throw new IllegalArgumentException("Failed to convert " + json + " to " + clazz, e);
152-
}
153-
}
134+
return HttpRequest.BodyPublishers.ofString(converter.toJson((MessageOrBuilder) arg));
154135
}
155136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.adven.grpc.wiremock;
2+
3+
import com.google.protobuf.AbstractMessage;
4+
import com.google.protobuf.InvalidProtocolBufferException;
5+
import com.google.protobuf.Message;
6+
import com.google.protobuf.MessageOrBuilder;
7+
import com.google.protobuf.util.JsonFormat;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.stereotype.Component;
12+
13+
import java.io.IOException;
14+
import java.lang.reflect.InvocationTargetException;
15+
16+
@Component
17+
public class ProtoJsonConverter {
18+
private static final Logger LOG = LoggerFactory.getLogger(HttpMock.class);
19+
private final JsonFormat.Printer printer;
20+
21+
public ProtoJsonConverter(@Value("${json.preserving.proto.field.names:false}") boolean preservingProtoFieldNames) {
22+
printer = preservingProtoFieldNames ? JsonFormat.printer().preservingProtoFieldNames() : JsonFormat.printer();
23+
}
24+
25+
public String toJson(MessageOrBuilder messageOrBuilder) throws IOException {
26+
return printer.print(messageOrBuilder);
27+
}
28+
29+
@SuppressWarnings({"unchecked", "rawtypes"})
30+
public <T extends Message> T fromJson(String json, Class<?> clazz) {
31+
try {
32+
LOG.info("Converting to {} json:\n{}", clazz, json);
33+
AbstractMessage.Builder builder = (AbstractMessage.Builder) clazz.getMethod("newBuilder").invoke(null);
34+
JsonFormat.parser().merge(json, builder);
35+
T result = (T) builder.build();
36+
LOG.info("Grpc response:\n{}", result);
37+
return result;
38+
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InvalidProtocolBufferException e) {
39+
throw new IllegalArgumentException("Failed to convert " + json + " to " + clazz, e);
40+
}
41+
}
42+
}

src/main/resources/application.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ grpc:
2626
---
2727
spring.config.activate.on-profile: load
2828
#
29-
logging.level.io.adven.grpc.wiremock.HttpMock: warn
29+
logging.level.io.adven.grpc: warn

0 commit comments

Comments
 (0)