From f2f4cdf807fd28cc501423644dc2583c8cd0c00c Mon Sep 17 00:00:00 2001 From: ian4h Date: Mon, 25 Jul 2022 09:10:00 -0700 Subject: [PATCH] Allows for configuring a custom ObjectMapper (#47) * Allow for configuring a custom ObjectMapper This change modifies the OpaClient builder to optionally take a custom ObjectMapper. --- build.gradle.kts | 1 + .../com/bisnode/opa/client/OpaClient.java | 12 ++- .../opa/client/OpaClientBuilderSpec.groovy | 78 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/test/groovy/com/bisnode/opa/client/OpaClientBuilderSpec.groovy diff --git a/build.gradle.kts b/build.gradle.kts index 6f31f31..b3688b0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { testImplementation("org.codehaus.groovy:groovy-all:2.5.15") testImplementation("org.spockframework:spock-core:1.3-groovy-2.5") testImplementation("com.github.tomakehurst:wiremock-jre8:2.31.0") + testImplementation("net.bytebuddy:byte-buddy:1.12.12") } repositories { diff --git a/src/main/java/com/bisnode/opa/client/OpaClient.java b/src/main/java/com/bisnode/opa/client/OpaClient.java index cfd2411..ffbe7a7 100644 --- a/src/main/java/com/bisnode/opa/client/OpaClient.java +++ b/src/main/java/com/bisnode/opa/client/OpaClient.java @@ -12,10 +12,12 @@ import com.bisnode.opa.client.query.QueryForDocumentRequest; import com.bisnode.opa.client.rest.ObjectMapperFactory; import com.bisnode.opa.client.rest.OpaRestClient; +import com.fasterxml.jackson.databind.ObjectMapper; import java.lang.reflect.ParameterizedType; import java.net.http.HttpClient; import java.util.Objects; +import java.util.Optional; /** * Opa client featuring {@link OpaDataApi}, {@link OpaQueryApi} and {@link OpaPolicyApi} @@ -72,6 +74,7 @@ public void createOrUpdatePolicy(OpaPolicy policy) { */ public static class Builder { private OpaConfiguration opaConfiguration; + private ObjectMapper objectMapper; /** * @param url URL including protocol and port @@ -81,12 +84,19 @@ public Builder opaConfiguration(String url) { return this; } + public Builder objectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + return this; + } + public OpaClient build() { Objects.requireNonNull(opaConfiguration, "build() called without opaConfiguration provided"); HttpClient httpClient = HttpClient.newBuilder() .version(opaConfiguration.getHttpVersion()) .build(); - OpaRestClient opaRestClient = new OpaRestClient(opaConfiguration, httpClient, ObjectMapperFactory.getInstance().create()); + ObjectMapper objectMapper = Optional.ofNullable(this.objectMapper) + .orElseGet(ObjectMapperFactory.getInstance()::create); + OpaRestClient opaRestClient = new OpaRestClient(opaConfiguration, httpClient, objectMapper); return new OpaClient(new OpaQueryClient(opaRestClient), new OpaDataClient(opaRestClient), new OpaPolicyClient(opaRestClient)); } } diff --git a/src/test/groovy/com/bisnode/opa/client/OpaClientBuilderSpec.groovy b/src/test/groovy/com/bisnode/opa/client/OpaClientBuilderSpec.groovy new file mode 100644 index 0000000..e0024cc --- /dev/null +++ b/src/test/groovy/com/bisnode/opa/client/OpaClientBuilderSpec.groovy @@ -0,0 +1,78 @@ +package com.bisnode.opa.client + + +import com.bisnode.opa.client.query.QueryForDocumentRequest +import com.bisnode.opa.client.rest.ContentType +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.tomakehurst.wiremock.WireMockServer +import spock.lang.Shared +import spock.lang.Specification + +import static com.bisnode.opa.client.rest.ContentType.Values.APPLICATION_JSON +import static com.github.tomakehurst.wiremock.client.WireMock.* + +class OpaClientBuilderSpec extends Specification { + + private static int PORT = 8181 + private static String url = "http://localhost:$PORT" + + @Shared + private WireMockServer wireMockServer = new WireMockServer(PORT) + + def setupSpec() { + wireMockServer.start() + } + + def cleanupSpec() { + wireMockServer.stop() + } + + def 'should configure OpaClient with custom ObjectMapper'() { + + given: + def objectMapper = Spy(ObjectMapper) + def path = 'someDocument' + def endpoint = "/v1/data/$path" + wireMockServer + .stubFor(post(urlEqualTo(endpoint)) + .withHeader(ContentType.HEADER_NAME, equalTo(APPLICATION_JSON)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(ContentType.HEADER_NAME, APPLICATION_JSON) + .withBody('{"result": {"authorized": true}}'))) + def opaClient = OpaClient.builder() + .opaConfiguration(url) + .objectMapper(objectMapper) + .build(); + + when: + opaClient.queryForDocument(new QueryForDocumentRequest([shouldPass: true], path), Object.class) + + then: + 1 * objectMapper.writeValueAsString(_) + } + + def 'should revert to default ObjectMapper if null ObjectMapper supplied'() { + given: + def path = 'someDocument' + def endpoint = "/v1/data/$path" + wireMockServer + .stubFor(post(urlEqualTo(endpoint)) + .withHeader(ContentType.HEADER_NAME, equalTo(APPLICATION_JSON)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(ContentType.HEADER_NAME, APPLICATION_JSON) + .withBody('{"result": {"authorized": true}}'))) + def opaClient = OpaClient.builder() + .opaConfiguration(url) + .objectMapper(null) + .build(); + + when: + def result = opaClient.queryForDocument(new QueryForDocumentRequest([shouldPass: true], path), Map.class) + + then: + result != null + result.get("authorized") == true + } +}