Optional params in REST API request using Jersey 2.21

≯℡__Kan透↙ 提交于 2019-11-28 12:09:32

So after some dabbling around with some of the answers in Optional @PathParam in Jax-RS, the problem is that using this

@Path("/myMethod{id: (/\\d+)?}") 
public Response get(@PathParam("id") int id) {}

causes the / to be in the capture group. So when Jersey tries to parse /1 it will get an exception and send a 404. We could use a String, but then it gets ugly, as we need to get rid of the leading / and parse it ourselves.

@Path("/myMethod{id: (/\\d+)?}") 
public Response get(@PathParam("id") String id) {
    id = id.replace("/", "");
    int parsed = Integer.parseInt(id);
}

The other solution I came up with (the one that works for the OP), is to separate the / from the digits into two different path expressions, so that the leading / is not captured in the actual id and doesn't fail in parsing

@Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
public Response get(@PathParam("id") int id) {}

The {noop: (/)?} captures the optional /. And the {id: ((?<=/)\\d+)?} uses a positive lookbehind, saying that the numbers (\\d+) are allowed if and only if there is a / before it ((?<=/)). This is necessary as the / is optional. If we didn't use this assertion, then /myMethod123 would be allowed.

Here is a complete test case using Jersey Test Framework

public class OptionalParamTest extends JerseyTest {

    @Path("optional")
    public static class Resource {
        @GET
        @Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
        public String get(@PathParam("id") int id) {
            return String.valueOf(id);
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(Resource.class);
    }

    @Test
    public void should_return_id_1() {
        Response response = target("optional/method/1").request().get();
        System.out.println("status=" + response.getStatus());
        assertEquals("1", response.readEntity(String.class));
    }

    @Test
    public void should_return_id_0_with_no_id() {
        Response response = target("optional/method").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("0", response.readEntity(String.class));
    }

    @Test
    public void should_return_404_with_numbers_and_no_slash() {
        Response response = target("optional/method12").request().get();
        assertEquals(404, response.getStatus());
    } 

    @Test
    public void should_return_404_with_numbers_and_letters() {
        Response response = target("optional/method/12b").request().get();
        assertEquals(404, response.getStatus());
    }

    @Test
    public void should_return_404_with_only_letters() {
        Response response = target("optional/method/ab").request().get();
        assertEquals(404, response.getStatus());
    } 
}

Here's the dependency for the test

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>

EDIT

For the tests, it would be better to use a boxed Integer instead of an int as the method parameter. With the former you would be able to do a null check, instead of receiving the default 0 for the primitive.

There is a way easier way to do this:

@GET
@Path("myMethod/{id}")
public String myMethod(@PathParam("id") Integer id) {
}

@GET
@Path("myMethod")
public String myMethod() {
  return myMethod(null);
}

No tricky regex required.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!