question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

OpenAPI plugin: 'non-static' and/or 'wrapped Handler' fields annotation HowTo

See original GitHub issue

Hi, I am familiarizing myself with the OpenAPI plugin API, notably using annotations and may need some nudge/guidance on how to use the annotation API for non-static and wrapped Handler endpoints.

Inspired by the tutorial and documentation snippets, I crafted a MVP using a Handler that is defined via a final static (case 1), final-only (case 2) and one with a custom/wrapped Handler implementation that internally branches depending on the context’s ‘Accept’ header:

// [..] standard imports [..]
public class Test {
    private static final String ENDPOINT_TEST1 = "/test1";
    private static final String ENDPOINT_TEST2 = "/test2";
    private static final String ENDPOINT_TEST3 = "/test3";

    public Test() {
        Javalin instance = RestServer.getInstance();
        instance.routes(() -> {
            get(ENDPOINT_TEST1, serveTestPage1);
            get(ENDPOINT_TEST2, serveTestPage2);
            get(ENDPOINT_TEST3, serveTestPage3);
        });
    }

    @OpenApi(description = "GET endpoint test V1", operationId = "serveTestPage", summary = "to serve", 
            tags = { "Test" }, path = ENDPOINT_TEST1, method = HttpMethod.GET, 
            responses = { @OpenApiResponse(status = "200", content = @OpenApiContent(type = "text/html")) })
    private static final Handler serveTestPage1 = ctx -> { // works OK
        ctx.result("serveTestPage1 path = " + ctx.path());
        // [serving request based on ctx]
    };

    @OpenApi(description = "GET endpoint test V2", operationId = "serveTestPage2", summary = "to serve", 
            tags = { "Test" }, path = ENDPOINT_TEST2, method = HttpMethod.GET, 
            responses = { @OpenApiResponse(status = "200", content = @OpenApiContent(type = "text/html")) })
    private final Handler serveTestPage2 = ctx -> { // note: w/o 'static' modifier
        ctx.result("serveTestPage2 path = " + ctx.path());
        // [serving request based on ctx]
    };

    @OpenApi(description = "GET endpoint test V3", operationId = "serveTestPage3", summary = "to serve", 
            tags = { "Test" }, path = ENDPOINT_TEST3, method = HttpMethod.GET, 
            responses = { @OpenApiResponse(status = "200", content = @OpenApiContent(type = "text/html")) })
    private final Handler serveTestPage3 = new CombinedHandler(ctx -> { // note: wrapped Handler implementation
        ctx.result("serveTestPage3 path = " + ctx.path());
        // [serving request based on ctx] serving BINARY/JSON/YML based on user 'Accept' header request
    });

    /**
     * little helper class to allow regular GET and SSE initialization via the same end-point
     */
    public class CombinedHandler implements Handler {
        private final Handler getHandler;

        public CombinedHandler(Handler getHandler) {
            this.getHandler = getHandler; // end-user/service specific Handler code
        }

        @Override
        public void handle(Context ctx) throws Exception {
            // if (MimeType.EVENT_STREAM.toString().equals(ctx.header(Header.ACCEPT))) {
            if (MimeType.EVENT_STREAM.equals(RestServer.getRequestedMimeProtocol(ctx)) || ctx.queryParam("sse") != null) {
                // [..] handle SSE initialisation requests
                ctx.result("handle SSE init process path = " + ctx.path());
                return;
            }

            getHandler.handle(ctx);
        }
    }
}

All endpoints serve the correct responses. However, while the endpoint swagger documentation seems to work for the standard case 1 (doc shows up in ‘Test’ category’, it fails and triggers the default

“A default response was added to the documentation of […]”

action (and ending up in the ‘default’ category) for endpoints ENDPOINT_TEST2 (not static) and ENDPOINT_TEST3 (wrapped/branching custom Handler implementation).

What am I missing? Have I moved outside the boundary of what the annotation API allows?

Any hint would be much appreciated!

Many thanks in advance.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:16 (16 by maintainers)

github_iconTop GitHub Comments

1reaction
MrThreepwoodcommented, May 19, 2020

That’s essentially what I was getting at. Since your annotation isn’t on the thing that’s being passed, and the thing that’s being passed can’t directly reference the annotation, you’re now just looking for any open api annotation that matches your path. At that point, why bother annotating the variable that stores the lambda? You could put it anywhere. This also doesn’t work if the lambda being bassed doesn’t originate in the class the annotation is stored in since the variable you’re annotating could store a lambda generated anywhere (though I don’t know why you’d do that).

This actually begins to a look a lot like annotation based routing. You’re functionally declaring the route handler with javalin’s get, but then expecting the documentation to be found for that same function being passed by looking for an annotation that specifies the path and method. At that point it would make a lot more sense to me to bind the routes by passing the entire Controller and inspecting all annotations.

I would argue that the methodReferenceOfNonStaticJavaHandler is highly counter intuitive and unexpected as well. It’s unfortunate that Java doesn’t keep function annotations (or even function name) when passing by reference, but it doesn’t and I wouldn’t expect this library to attempt to bypass that.

It makes more sense to me to expect people to use the DocumentedHandler implementation to work around the limitations of Java.

That said, I normally don’t do much Javalin development in Java anymore, so perhaps others don’t consider this as counterintuitive.

1reaction
sealedtxcommented, May 17, 2020

Hi @tipsy, I reviewed this issue and dived into source code. After debug I faced with a strange situation which I don’t know how to explain, because I am not an expert in Java reflection. As I noticed the reason why non-static lambda field is ignored is inside ReflectionUtil.lambdaField where ReflectionUtil.javaFieldName is called: https://github.com/tipsy/javalin/blob/1872e25a266432e0156aceb4b32d4c12c197b187/javalin/src/main/java/io/javalin/core/util/ReflectionUtil.kt#L55-L60

I have two fields in my test class:

    public static final Handler testStatic = ctx -> { 
        ctx.result("testStatic path");
    };

    public final Handler testNonStatic = ctx -> {
        ctx.result("testNonStatic path");
    };

Here inside javaFieldName - Field.get(it) works for static lambda field, but non-static throws IllegalArgumentException: https://github.com/tipsy/javalin/blob/1872e25a266432e0156aceb4b32d4c12c197b187/javalin/src/main/java/io/javalin/core/util/ReflectionUtil.kt#L11

Can not set final io.javalin.http.Handler field Test.testNonStatic to Test$$Lambda$15/0x0000000100098040

I think the key for solving this problem - is the reason why javaFieldName for non-static lambda fields cannot be retrieved from declaredFields. Need advice from someone with more expertise in Java reflection.

Read more comments on GitHub >

github_iconTop Results From Across the Web

OpenAPI plugin: 'non-static' and/or 'wrapped Handler' fields ...
Hi, I am familiarizing myself with the OpenAPI plugin API, notably using annotations and may need some nudge/guidance on how to use the ......
Read more >
Customizing OpenAPI Generator Templates
OpenAPI Generator has been designed to allow for the easy extension or even creation of a NEW generator. This lab will take a...
Read more >
IntelliJ IDEA 2021.2 (212.4746.92 build) Release Notes
AlreadyDisposedException: Cannot create com.intellij.openapi.externalSystem. ... Android, Bug, IDEA-254379, Get rid of 2 kotlin plugins in IDEA project.
Read more >
There's No Reason to Write OpenAPI By Hand
An OpenAPI annotation framework provides a bunch of keywords that help the API developer describe the interface of the HTTP request and response ......
Read more >
How to add particular annotation for field basing on Collection ...
I am using OpenAPI and mustache to add proper @EventField annotation for fields in my DTOs. So far I have mustache logic like:...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found