-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a ParamConverterProvider for java.util.Optional parameters #4651
Comments
@jansupol wow thanks for doing this! Much appreciated. |
@jansupol @jbescos Thanks for implementing this upstream! Too bad you didn't implement providers for the other optional classes such as In the upstream implementation, I think there was an unfortunate decision to return jersey/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java Lines 282 to 286 in 52c8aa4
This means that users will have to check all In my humble opinion this is counter-intuitive to the idiomatic usages of the Unfortunately the test cases don't cover the erroneous cases: Lines 79 to 87 in 52c8aa4
In order to properly cover the error cases, the resource method would have to be changed as follows: Original: Lines 47 to 51 in 52c8aa4
Changed version with correct error handling: @GET
@Path("/fromInteger")
public Response fromInteger(@QueryParam(PARAM_NAME) Optional<Integer> data) {
if (data == null) {
// parameter is present but couldn't be converted to an integer
return Response.status(400, "Invalid integer parameter");
}
return Response.ok(data.orElse(0)).build();
} The version with the correct error handling doesn't provide any advantage over using a plain @GET
@Path("/fromInteger")
public Response fromInteger(@QueryParam(PARAM_NAME) @DefaultValue("0") Integer data) {
if (data == null) {
// parameter is present but couldn't be converted to an integer
return Response.status(400, "Invalid integer parameter");
}
return Response.ok(data).build();
} |
Hi @joshchi, thanks for your feedback, but I have few questions.
With that you cannot change the default value with runtime data. For example, lets suppose the input is a Date, you can not use by default new Date(). Or maybe the value is coming from a property, database, etc. For the other observation: For clarification, there are 3 scenarios:
Jersey iterates through all ParamConverters till it finds one that is not null. Basically if the ParamConverter does not know how to convert it, it returns null and it lets the next candidate to handle it. From this point of view I think it is doing it well. I agree user should never check optional == null, but for the case you mention, I think Jersey should fail before reaching the resource with a 400 error, instead of passing an Optional.empty() as argument. |
@jbescos Thanks for your response!
You're right, Jersey would let the request fail and not return This being said, the behavior seems a bit inconsistent across providers, e. g. import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
public class OptionalTest extends JerseyTest {
@Override
protected Application configure() {
return new ResourceConfig(TestResource.class);
}
@Test
public void testOptionalInteger() {
String paramEmpty = target("/int").request().get(String.class);
assertEquals("default", paramEmpty);
String paramProvided = target("/int").queryParam("i", 42).request().get(String.class);
assertEquals("i was 42", paramProvided);
Response response = target("/int").queryParam("i", "not-a-number").request().get();
assertEquals(404, response.getStatus());
}
@Test
public void testOptionalBoolean() {
String paramEmpty = target("/bool").request().get(String.class);
assertEquals("default", paramEmpty);
String paramProvided = target("/bool").queryParam("b", "true").request().get(String.class);
assertEquals("b was true", paramProvided);
String paramInvalid = target("/bool").queryParam("b", "not-a-number").request().get(String.class);
assertEquals("b was false", paramInvalid);
}
@Produces(MediaType.TEXT_PLAIN)
@Path("/")
public static class TestResource {
@GET
@Path("/int")
public String optionalInt(@QueryParam("i") Optional<Integer> i) {
if (i == null) {
return "null";
}
return i.map(param -> "i was " + param).orElse("default");
}
@GET
@Path("/bool")
public String optionalBool(@QueryParam("b") Optional<Boolean> b) {
if (b == null) {
return "null";
}
return b.map(param -> "b was " + param).orElse("default");
}
}
} |
That is probably because Integer.parseInteger fails with exception when the input is not a number. But for the case of Boolean.parseBoolean, it returns false in case the input is not "true". I guess if you remove the Optional from the test resources it should behave in the same way, passing the boolean as false when the input is "not-a-number". |
@jbescos Sorry to bring this up again but it looks like an empty input string ( Same example as before but this time with a check for the behavior when passing an empty string for the
This seems to be true for all numeric types ( |
@joschi, from the implementation point of view it is consistent with the next behavior when there is no Optional:
It will return 200 and the It seems Jersey is validating that the Integer is not empty before parseInt it. Although it looks inconsistent with the other test you pointed out before:
Why Jersey validates the empty string but it is not validating when the string is not a number?. This is other issue that would require some discussion and analysis. Maybe it is written in the specification that it must behave in that way. Feel free to open a new issue to address this and we will check it. |
Which leaves us again with (#4651 (comment)):
This being said, I think I'll follow up with your suggestion to open a new issue. 👍
|
Would it be possible to include a
ParamCovnerterProvider
that supportsOptional<X>
types?I notice DropWizard have some: https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/optional/OptionalParamBinder.java - but presumably this would be a generally useful feature!
The text was updated successfully, but these errors were encountered: