Repository: micronaut-projects/micronaut-core
File: http-server-tck/src/main/java/io/micronaut/http/server/tck/tests/filter/RequestFilterCompletableFutureFutureProceedTest.java (Java)

Code before:
                        .status(HttpStatus.ACCEPTED)
                        .build()))
                .run();

        TestScenario.builder()
                .specName(SPEC_NAME)
                .request(HttpRequest.GET("/foobar"))
                .assertion((server, request) -> AssertionUtils.assertThrows(server, request, HttpResponseAssertion.builder()
                        .status(HttpStatus.UNAUTHORIZED)
                        .build()))
                .run();
    }

    /*
    //tag::clazz[]
    @ServerFilter(ServerFilter.MATCH_ALL_PATTERN)
    class CsrfFilter {
    //end::clazz[]
    */
    @Requires(property = "spec.name", value = SPEC_NAME)
    @ServerFilter(ServerFilter.MATCH_ALL_PATTERN)
    static class FooBarFilter {
    //tag::methods[]
        @RequestFilter
        @Nullable
        public CompletableFuture<@Nullable HttpResponse<?>> filter(@NonNull HttpRequest<?> request) {
            if (request.getHeaders().contains("X-FOOBAR")) {
                // proceed
                return CompletableFuture.completedFuture(null);
            } else {
                return CompletableFuture.completedFuture(HttpResponse.unauthorized());
            }
        }
    }
    //end::methods[]

    @Requires(property = "spec.name", value = SPEC_NAME)
    @Controller("/foobar")
    static class FooBarController {
        @Get
        @Status(HttpStatus.ACCEPTED)
        void index() {
            // no-op
        }
    }
}


Review comment (suggestion):
Please note that if you remove the `@Nullable` annotation. It does not work. This PR ensures the users finds the correct return type incantation. He needs to use `CompletableFuture<@Nullable HttpResponse<?>>` not `CompletableFuture<HttpResponse<?>>`. 


```suggestion
        public CompletableFuture<HttpResponse<?>> filter(@NonNull HttpRequest<?> request) {
```

Code after:

@SuppressWarnings({
        "java:S5960", // We're allowed assertions, as these are used in tests only
        "checkstyle:MissingJavadocType",
        "checkstyle:DesignForExtension"
})
public class RequestFilterCompletableFutureFutureProceedTest {
    public static final String SPEC_NAME = "RequestFilterCompleteableFutureProceedTest";

    @Test
    public void requestFilterProceedWithCompletableFuture() throws IOException {
        TestScenario.builder()
                .specName(SPEC_NAME)
                .request(HttpRequest.GET("/foobar").header("X-FOOBAR", "123"))
                .assertion((server, request) -> AssertionUtils.assertDoesNotThrow(server, request, HttpResponseAssertion.builder()
                        .status(HttpStatus.ACCEPTED)
                        .build()))
                .run();

        TestScenario.builder()
                .specName(SPEC_NAME)
                .request(HttpRequest.GET("/foobar"))
                .assertion((server, request) -> AssertionUtils.assertThrows(server, request, HttpResponseAssertion.builder()
                        .status(HttpStatus.UNAUTHORIZED)
                        .build()))
                .run();
    }

    /*
    //tag::clazz[]
    @ServerFilter(ServerFilter.MATCH_ALL_PATTERN)
    class FooBarFilter {
    //end::clazz[]
    */
    @Requires(property = "spec.name", value = SPEC_NAME)
    @ServerFilter(ServerFilter.MATCH_ALL_PATTERN)
    static class FooBarFilter {
    //tag::methods[]
        @RequestFilter
        CompletableFuture<@Nullable HttpResponse<?>> filter(@NonNull HttpRequest<?> request) {
            if (request.getHeaders().contains("X-FOOBAR")) {
                // proceed
                return CompletableFuture.completedFuture(null);
            } else {
                return CompletableFuture.completedFuture(HttpResponse.unauthorized());
            }
        }
    }
    //end::methods[]

    @Requires(property = "spec.name", value = SPEC_NAME)