micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.08k stars 1.07k forks source link

URI Template Reserved Expansion {+path,x}/here #11188

Closed zx-max closed 1 month ago

zx-max commented 1 month ago

Expected Behavior

Given this uri template: {+path,x}/here as described in: rfc6570 section-3.2.3

If a write a method in a controller who use it

@Get("/section-323{+path,x}/here")
void reserved_expansion_with_multiple_variables(@Nullable String path, @Nullable String x) {
    ...
}

And i send a request with the example values: path=/foo/bar x=1024

When the controller receive the http request, for the variables, i expect this values : path=/foo/bar x=1024

Actual Behaviour

The values assigned to the variables are: path = / and x = foo/bar,1024

Steps To Reproduce

to reproduce, just run the test here:

test on git hub

https://github.com/zx-max/micronaut-tutorial/blob/main/uri-templates/demo/src/main/java/http/client/UriTemplateExpanderSpecController.java

Environment Information

I use window, jdk 21, the project has been created from command line with this command:

mn  -v create-app   `
 http.client.demo `
     --build=gradle_kotlin  `
     --lang=java  `
     --java-version=21 `
     --test=junit  `
     --features=http-client

Example Application

https://github.com/zx-max/micronaut-tutorial/tree/main/uri-templates/demo

Version

4.4.2

nedelva commented 1 month ago

@zx-max , I believe what's happening here is that you ran into a limitation of the binding mechanism. The annotations @Get, @PathVariable etc. can only bind one variable from each template expression. Take for example this contrived example:

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;

@Controller
public class Section323Controller {

    /* works the same if you remove the arguments' annotations */
    @Get("1)/{+path},{x}/here")
    public String getExpansionWithMultipleVars(@PathVariable("path") String path, @PathVariable("x") String x) {
        return "1) path=" + path + " AND x=" + x ;
    }

    @Get("2)/{+path,x}/here")
    public String getExpansionWithOneVar(@PathVariable("path") String path, String x) {
        return "2) path=" + path + " AND x=" + x ;
    }

    @Get("3)/{+path,x}/here")
    public String getExpansionWithNoVar(String path, String x) {
        return "3) path=" + path + " AND x=" + x ;
    }
}

A simple test for it could be:

import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MutableHttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
class Section323ControllerTest {
    @Inject
    @Client("/")
    HttpClient client;

    @Test
    void testTwoPathVars() {

        MutableHttpRequest<String> request = HttpRequest.GET("1)/foo/bar,1024/here");
        HttpResponse<Object> httpResponse = client.toBlocking().exchange(request);
        assertEquals(HttpStatus.OK, httpResponse.getStatus());
        assertEquals("1) path=foo/bar AND x=1024", httpResponse.getBody(Argument.STRING).get());
    }

    @Test
    void testOnePathVar() {
        MutableHttpRequest<String> request = HttpRequest.GET("2)/foo/bar,1024/here");
        HttpResponse<Object> httpResponse = client.toBlocking().exchange(request);
        assertEquals(HttpStatus.OK, httpResponse.getStatus());
        /* fails; the result is "2) path=f AND x=oo/bar,1024" */
        assertEquals("2) path=foo/bar AND x=1024", httpResponse.getBody(Argument.STRING).get());
    }

    @Test
    void testNoPathVar() {
        MutableHttpRequest<String> request = HttpRequest.GET("3)/foo/bar,1024/here");
        HttpResponse<Object> httpResponse = client.toBlocking().exchange(request);
        assertEquals(HttpStatus.OK, httpResponse.getStatus());
        /* fails; the result is 3) path=f AND x=oo/bar,1024" */
        assertEquals("3) path=foo/bar AND x=1024", httpResponse.getBody(Argument.STRING).get());
    }
}

Bottom line: you cannot have a template like {var1,var2,var3}. To have it working with Micronaut you need one expression per variable, something like {var1}{var2}{var3}.

zx-max commented 1 month ago

Thanks a lot !!! I added your example in the demo and i have fixed the template in the @Get.