Repository: vert-x3/vertx-web
Issue: ResponsePredicateResult::response returns null when requiresBody is false

### Version

`3.9.3`

### Context

I have a very simple `ErrorConverter` whose `ResponsePredicateResult` is returning `null` for `response()` when `requiresBody()` is set to `false`. It works fine (valid `HttpResponse` instance) if `requiresBody()` is set to `true`.

I would expect to be able to assess all response properties _except_ `body()` by having `requiresBody` set to `false`.

### Do you have a reproducer?

~~No, if the report doesn't point to an obvious culprit I can see if I can create one.~~

https://github.com/pnsantos/vertx-predicate-npe-repro

### Steps to reproduce

This is my error converter:
```java
public class MyConverter implements ErrorConverter {

    @Override
    public Throwable apply(ResponsePredicateResult result) {
        int code = result.response().statusCode();    <--- result.response() is null

        (...)
    }
}
```

And this is how I'm creating the response predicate:
```
ResponsePredicate.create(ResponsePredicate.SC_SUCCESS, new MyConverter())
```

On "success" cases everything works fine. When the predicate fails (4xx/5xx responses), and the converter is called, if `requiresBody` is set to `true` everything works as expected. However if `requiresBody` is `false` the following exception happens (line 21 is where I'm doing (`result.response().statusCode()`):
```
java.lang.NullPointerException
  at com.example.error.MyErrorConverter.apply(MyErrorConverter.java:21)
  at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.failOnPredicate(PredicateInterceptor.java:88)
  at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:55)
  at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:32)
  at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:272)
  at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:282)
  at io.vertx.ext.web.client.impl.HttpContext.receiveResponse(HttpContext.java:230)  
  at io.vertx.ext.web.client.impl.HttpContext.lambda$handleSendRequest$7(HttpContext.java:420)
  at io.vertx.core.impl.FutureImpl.dispatch(FutureImpl.java:105)
  at io.vertx.core.impl.FutureImpl.tryComplete(FutureImpl.java:150)
  at io.vertx.core.http.impl.HttpClientRequestImpl.handleResponse(HttpClientRequestImpl.java:391)
  at io.vertx.core.http.impl.HttpClientRequestBase.checkHandleResponse(HttpClientRequestBase.java:159)
  at io.vertx.core.http.impl.HttpClientRequestBase.handleResponse(HttpClientRequestBase.java:140)
  at io.vertx.core.http.impl.Http1xClientConnection.handleResponseBegin(Http1xClientConnection.java:623)
  at io.vertx.core.http.impl.Http1xClientConnection.handleHttpMessage(Http1xClientConnection.java:593)
  at io.vertx.core.http.impl.Http1xClientConnection.handleMessage(Http1xClientConnection.java:575)
  at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
  at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
  at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:229)
  at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:163)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
  at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
  at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
  at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)  
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1518)
  at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1267)
  at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1314)
  at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
  at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440)
  at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
  at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
  at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
  at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
  at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
  at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
  at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
  at java.lang.Thread.run(Thread.java:748)
```
I confirmed via logs that `result.response()` is returning `null`.

### Extra

```
netty: 4.1.49.Final
---
openjdk version "1.8.0_265"
OpenJDK Runtime Environment (Zulu 8.48.0.53-CA-linux64) (build 1.8.0_265-b11)
OpenJDK 64-Bit Server VM (Zulu 8.48.0.53-CA-linux64) (build 25.265-b11, mixed mode)
```


Changed files:
vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/predicate/PredicateInterceptor.java

diff --git a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/predicate/PredicateInterceptor.java b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/predicate/PredicateInterceptor.java
index a4f5afe5e..8273ea14b 100644
--- a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/predicate/PredicateInterceptor.java
+++ b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/predicate/PredicateInterceptor.java
@@ -44,7 +44,7 @@ public class PredicateInterceptor implements Handler<HttpContext<?>> {
         for (ResponsePredicate expectation : expectations) {
           ResponsePredicateResultImpl predicateResult;
           try {
-            predicateResult = (ResponsePredicateResultImpl) expectation.apply(responseCopy(resp, httpContext,null));
+            predicateResult = (ResponsePredicateResultImpl) expectation.apply(responseCopy(resp, httpContext, null));
           } catch (Exception e) {
             httpContext.fail(e);
             return;
@@ -52,6 +52,7 @@ public class PredicateInterceptor implements Handler<HttpContext<?>> {
           if (!predicateResult.succeeded()) {
             ErrorConverter errorConverter = expectation.errorConverter();
             if (!errorConverter.requiresBody()) {
+              predicateResult.setHttpResponse(responseCopy(resp, httpContext, null));
               failOnPredicate(httpContext, errorConverter, predicateResult);
             } else {
               resp.bodyHandler(buffer -> {
diff --git a/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java b/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
index 7e2396f6b..59052c32c 100644
--- a/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
+++ b/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
@@ -1775,6 +1775,29 @@ public class WebClientTest extends WebClientTestBase {
     });
   }
 
+  @Test
+  public void testExpectCustomExceptionWithStatusCode() throws Exception {
+    UUID uuid = UUID.randomUUID();
+    int statusCode = 400;
+
+    ResponsePredicate predicate = ResponsePredicate.create(ResponsePredicate.SC_SUCCESS, ErrorConverter.create(result -> {
+        int code = result.response().statusCode();
+        return new CustomException(uuid, String.valueOf(code));
+      }));
+
+    testExpectation(true, req -> req.expect(predicate), httpServerResponse -> {
+      httpServerResponse
+        .setStatusCode(statusCode)
+        .end(TestUtils.randomBuffer(2048));
+    }, ar -> {
+      Throwable cause = ar.cause();
+      assertThat(cause, instanceOf(CustomException.class));
+      CustomException customException = (CustomException) cause;
+      assertEquals(String.valueOf(statusCode), customException.getMessage());
+      assertEquals(uuid, customException.tag);
+    });
+  }
+
   @Test
   public void testExpectFunctionThrowsException() throws Exception {
     ResponsePredicate predicate = ResponsePredicate.create(r -> {