Skip to content
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

ParamConverter for Optional<T> doesn't support custom types #4798

Closed
joschi opened this issue May 18, 2021 · 0 comments · Fixed by #4799
Closed

ParamConverter for Optional<T> doesn't support custom types #4798

joschi opened this issue May 18, 2021 · 0 comments · Fixed by #4799

Comments

@joschi
Copy link
Contributor

joschi commented May 18, 2021

The ParamConverter implementation for the Java Optional<T> container which was introduced in Jersey 2.34 (and Jersey 3.0.1) doesn't support handling custom types.

With Optional<T> being a container similar to Collection<T> (and its derivates), I would expect it to support custom types for which a ParamConverterProvider and ParamConverter exist.

Unfortunately this is not the case. org.glassfish.jersey.internal.inject.ParamConverters.OptionalProvider only supports the other types defined in org.glassfish.jersey.internal.inject.ParamConverters.AggregatedProvider but not any other type of parameters for which a ParamConverterProvider and ParamConverter have been registered.

Here's an example (using the Jersey testing framework) with a custom ParamConverterProvider and ParamConverter for java.time.Instant:

import org.glassfish.jersey.internal.inject.ExtractorException;
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 javax.ws.rs.ext.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.Optional;

import static org.junit.Assert.assertEquals;

public class CustomParamConverterOptionalTest extends JerseyTest {
    @Override
    protected Application configure() {
        return new ResourceConfig(TestResource.class)
                .register(InstantParamConverterProvider.class);
    }

    @Test
    public void testInstant() {
        String paramMissing = target("/instant")
                .request().get(String.class);
        assertEquals("null", paramMissing);

        String optionalParamMissing = target("/optional")
                .request().get(String.class);
        assertEquals("default", optionalParamMissing);

        String paramProvided = target("/instant").queryParam("i", "2021-05-18T22:49:00Z")
                .request().get(String.class);
        assertEquals("2021-05-18T22:49:00Z", paramProvided);

        String optionalParamProvided = target("/optional").queryParam("i", "2021-05-18T22:49:00Z")
                .request().get(String.class); // 💥 Fails with: javax.ws.rs.NotFoundException: HTTP 404 Not Found
        assertEquals("2021-05-18T22:49:00Z", optionalParamProvided);
    }

    @Produces(MediaType.TEXT_PLAIN)
    @Path("/")
    public static class TestResource {
        @GET
        @Path("/optional")
        public String optionalInt(@QueryParam("i") Optional<Instant> i) {
            if (i == null) {
                return "null";
            }
            return i.map(Instant::toString).orElse("default");
        }

        @GET
        @Path("/instant")
        public String optionalInt(@QueryParam("i") Instant i) {
            if (i == null) {
                return "null";
            }
            return i.toString();
        }
    }

    @Provider
    public static class InstantParamConverterProvider implements ParamConverterProvider {
        @Override
        public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
            if (rawType.equals(Instant.class)) {
                return new ParamConverter<T>() {
                    @Override
                    public T fromString(String value) {
                        if (value == null) {
                            throw new IllegalArgumentException();
                        }
                        try {
                            return rawType.cast(Instant.parse(value));
                        } catch (Exception e) {
                            throw new ExtractorException(e);
                        }
                    }

                    @Override
                    public String toString(T value) {
                        if (value == null) {
                            throw new IllegalArgumentException();
                        }
                        return value.toString();
                    }
                };
            } else {
                return null;
            }
        }
    }
}

Refs #4651
Refs #4690
Refs #4790

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant