From 73befa702f8e175b2aac88e0b1db241d1e623449 Mon Sep 17 00:00:00 2001 From: Kai Moritz Date: Wed, 20 Nov 2019 16:02:48 +0100 Subject: [PATCH] Implemented an ExchangeFilterFunction, to support Spring WebFlux * Guckst du hier: https://stackoverflow.com/a/48984852/247276 --- pom.xml | 13 +- .../GraphApiExchangeFilterFunction.java | 49 ++++ ...ExchangeFilterFunctionIntegrationTest.java | 271 ++++++++++++++++++ 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunction.java create mode 100644 src/test/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunctionIntegrationTest.java diff --git a/pom.xml b/pom.xml index b1fc524..073e90f 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,8 @@ org.springframework - spring-webmvc + spring-webflux + true org.springframework.boot @@ -110,6 +111,16 @@ mockwebserver test + + io.projectreactor + reactor-test + test + + + org.eclipse.jetty + jetty-reactive-httpclient + test + javax.servlet javax.servlet-api diff --git a/src/main/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunction.java b/src/main/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunction.java new file mode 100644 index 0000000..34ca5a7 --- /dev/null +++ b/src/main/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunction.java @@ -0,0 +1,49 @@ +package de.juplo.facebook.errors; + + +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + + +/** + * An {@link ExchangeFilterFunction} + * @author Kai Moritz + */ +public class GraphApiExchangeFilterFunction implements ExchangeFilterFunction +{ + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) + { + return + next + .exchange(request) + .flatMap(response -> + { + return + HttpStatus.Series.CLIENT_ERROR.equals(response.statusCode().series()) + ? response + .bodyToMono(String.class) + .map(errorBody -> GraphApiException.create( + response.statusCode(), + response.headers().asHttpHeaders(), + errorBody.getBytes())) + .map(e -> + e.getClass().equals(ErrorResponseParsingErrorException.class) + ? WebClientResponseException + .create( + e.getStatus().value(), + e.getStatus().getReasonPhrase(), + e.getHeaders(), + null, + null) + : e) + .flatMap(e -> Mono.error(e)) + : Mono.just(response); + }); + } +} diff --git a/src/test/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunctionIntegrationTest.java b/src/test/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunctionIntegrationTest.java new file mode 100644 index 0000000..53b2e6a --- /dev/null +++ b/src/test/java/de/juplo/facebook/errors/GraphApiExchangeFilterFunctionIntegrationTest.java @@ -0,0 +1,271 @@ +package de.juplo.facebook.errors; + + +import de.juplo.facebook.errors.GraphApiException.Type; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.reactive.JettyClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException.BadRequest; +import org.springframework.web.reactive.function.client.WebClientResponseException.InternalServerError; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + + +/** + * + * @author Kai Moritz + */ +public class GraphApiExchangeFilterFunctionIntegrationTest +{ + private static final Logger LOG = + LoggerFactory.getLogger(GraphApiExchangeFilterFunctionIntegrationTest.class); + + private MockWebServer server; + private WebClient webClient; + + + @Before + public void setup() + { + server = new MockWebServer(); + webClient = + WebClient + .builder() + .clientConnector(new JettyClientHttpConnector()) + .baseUrl(server.url("/").toString()) + .filter(new GraphApiExchangeFilterFunction()) + .build(); + } + + @After + public void shutdown() throws Exception + { + this.server.shutdown(); + } + + + @Test + public void testNoError() + { + Mono result; + + + //server + // .enqueue(new MockResponse() + // .setResponseCode(HttpStatus.CONTINUE.value()) + // .setHeader("Content-Type", "application/json") + // .setBody("Hallo Welt!")); + // + //result = + // webClient + // .get() + // .uri("/egal") + // .retrieve() + // .bodyToMono(String.class); + // + //StepVerifier + // .create(result) + // .expectNext("Hallo Welt!") + // .expectComplete() + // .verify(); + + + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .setHeader("Content-Type", "text/plain") + .setBody("Hallo Welt!")); + + result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier + .create(result) + .expectNext("Hallo Welt!") + .expectComplete() + .verify(); + + + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.TEMPORARY_REDIRECT.value()) + .setHeader("Location", server.url("/woanders")) + .setHeader("Content-Type", "text/plain") + .setBody("Jetzt doch woanders...")); + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .setHeader("Content-Type", "text/plain") + .setBody("Hallo Welt!")); + + result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier + .create(result) + .expectNext("Hallo Welt!") + .expectComplete() + .verify(); + + + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .setHeader("Content-Type", "text/plain") + .setBody("Hallo Welt!")); + + result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier + .create(result) + .expectError(InternalServerError.class) + .verify(); + } + + @Test + public void testValidError() + { + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.BAD_REQUEST.value()) + .setHeader("Content-Type", "application/json") + .setBody( + "{\n" + + " \"error\":\n" + + " {\n" + + " \"message\": \"(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.\",\n" + + " \"type\": \"OAuthException\",\n" + + " \"code\": 613\n" + + " }\n" + + "}")); + + Mono result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier.create(result).expectErrorSatisfies(throwable -> + { + assertEquals(RateLimitExceededException.class, throwable.getClass()); + RateLimitExceededException e = (RateLimitExceededException)throwable; + LOG.debug("{}", e.toString()); + assertEquals(new Integer(613), e.getCode()); + assertEquals("(#613) Calls to stream have exceeded the rate of 600 calls per 600 seconds.", e.getMessage()); + assertEquals(Type.OAuthException, e.getType()); + }) + .verify(); + } + + @Test + public void testUnmappedError() + { + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.BAD_REQUEST.value()) + .setHeader("Content-Type", "application/json") + .setBody( + "{\n" + + " \"error\":\n" + + " {\n" + + " \"message\": \"This error does not exist.\",\n" + + " \"type\": \"NonexistentTypeException\",\n" + + " \"code\": 999999999\n" + + " }\n" + + "}")); + + + Mono result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier.create(result).expectErrorSatisfies(throwable -> + { + assertEquals(UnmappedErrorException.class, throwable.getClass()); + UnmappedErrorException e = (UnmappedErrorException)throwable; + LOG.debug("{}", e.toString()); + assertEquals(new Integer(999999999), e.getCode()); + assertEquals("This error does not exist.", e.getMessage()); + try + { + Type type = e.getType(); + LOG.error("unknown type: {}", type); + fail("unmapped type was resolved by enum: " + type); + } + catch (IllegalArgumentException ee) {} + }) + .verify(); + } + + @Test + public void testInvlalidError() + { + Mono result; + + + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.BAD_REQUEST.value()) + .setHeader("Content-Type", "application/json") + .setBody( + "{\n" + + " \"error\":\n" + + " {\n" + + " \"message\": \"Not a Graph-Api-Exception.\",\n" + + " \"type\": \"Whatever\",\n" + + " \"code\": \"some string\"\n" + + " }\n" + + "}")); + + result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier.create(result).expectError(BadRequest.class).verify(); + + + server + .enqueue(new MockResponse() + .setResponseCode(HttpStatus.BAD_REQUEST.value()) + .setHeader("Content-Type", "text/plain") + .setBody("Hallo Welt!")); + + result = + webClient + .get() + .uri("/egal") + .retrieve() + .bodyToMono(String.class); + + StepVerifier.create(result).expectError(BadRequest.class).verify(); + } +} -- 2.20.1