Skip to content

Commit

Permalink
Add custom objectMapper in JsonProvider (#12)
Browse files Browse the repository at this point in the history
* feat: add custom objectMapper in JsonProvider

* chore: add test for extra keys in request body

* chore: update provider registration

* feat: make registerProvider through objects protected

* feat: enable custom json providers through interface

* chore: update readme

* chore: update readme
  • Loading branch information
nikhilT-d11 authored Mar 29, 2024
1 parent a47953b commit 66ff867
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 55 deletions.
30 changes: 29 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,35 @@ public class TestRoute {
- Refer to [`com.dream11.rest.injector.GuiceInjector`](src/test/java/com/dream11/rest/injector/GuiceInjector.java) for an example

## Custom Providers
Annotate your provider classes with `@Provider` to automatically register them with resteasy deployment
- Annotate your provider classes with `@Provider` to automatically register them with resteasy deployment

- Alternatively, if your provider class necessitates a constructor with parameters, you can override the following method in
[AbstractRestVerticle](src/main/java/com/dream11/rest/AbstractRestVerticle.java) to register the provider object
```java
public class RestVerticle extends AbstractRestVerticle {

@Override
protected List<Object> getProviderObjects() {
List<Object> providerObjects = super.getProviderObjects();
// add your provider object with custom constructor to the list
return providerObjects;
}
}
```

### Custom Json Provider
You can create custom json providers by
- Implement the `JsonProvider` interface, defined in [JsonProvider](src/main/java/com/dream11/rest/provider/JsonProvider.java) and then,
- Override the following method in [AbstractRestVerticle](src/main/java/com/dream11/rest/AbstractRestVerticle.java)
```java
public class RestVerticle extends AbstractRestVerticle {

@Override
protected JsonProvider getJsonProvider() {
// return your custom json provider
}
}
```

## Examples
Please refer [tests](/src/test/java/com/dream11/rest) for an example application
Expand Down
22 changes: 19 additions & 3 deletions src/main/java/com/dream11/rest/AbstractRestVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import com.dream11.rest.filter.TimeoutFilter;
import com.dream11.rest.provider.JsonProvider;
import com.dream11.rest.provider.ParamConverterProvider;
import com.dream11.rest.provider.impl.JacksonProvider;
import com.dream11.rest.util.AnnotationUtil;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.jackson.DatabindCodec;
import io.vertx.rxjava3.core.AbstractVerticle;
import io.vertx.rxjava3.core.Context;
import io.vertx.rxjava3.core.RxHelper;
Expand Down Expand Up @@ -56,6 +58,10 @@ protected RequestResponseFilter getReqResFilter() {
return new LoggerFilter();
}

protected JsonProvider getJsonProvider() {
return new JacksonProvider(DatabindCodec.mapper());
}

@Override
public Completable rxStart() {
return this.startHttpServer().doOnSuccess(server -> this.httpServer = server).ignoreElement();
Expand Down Expand Up @@ -116,23 +122,33 @@ protected VertxResteasyDeployment buildResteasyDeployment() {
deployment.start();
List<Class<?>> routes = AnnotationUtil.getClassesWithAnnotation(packageName, Path.class);
log.info("JAX-RS routes : " + routes.size());
ResteasyProviderFactory resteasyProviderFactory = deployment.getProviderFactory();
this.getProviders().forEach(resteasyProviderFactory::register);
this.registerProviders(deployment.getProviderFactory());

// not using deployment.getRegistry().addPerInstanceResource because it creates new instance of resource for each request
routes.forEach(route -> deployment.getRegistry().addSingletonResource(this.getInjector().getInstance(route)));
return deployment;
}

private void registerProviders(ResteasyProviderFactory resteasyProviderFactory) {
this.getProviderObjects().forEach(resteasyProviderFactory::register);
this.getProviders().forEach(resteasyProviderFactory::register);
}

protected List<Class<?>> getProviders() {
List<Class<?>> providers = new ArrayList<>();
providers.add(TimeoutFilter.class);
providers.add(ValidationExceptionMapper.class);
providers.add(GenericExceptionMapper.class);
providers.add(WebApplicationExceptionMapper.class);
providers.add(JsonProvider.class);
providers.add(ParamConverterProvider.class);
providers.add(this.getReqResFilter().getClass());
providers.addAll(AnnotationUtil.getClassesWithAnnotation(packageName, Provider.class));
return providers;
}

protected List<Object> getProviderObjects() {
List<Object> providerObjects = new ArrayList<>();
providerObjects.add(this.getJsonProvider());
return providerObjects;
}
}
52 changes: 1 addition & 51 deletions src/main/java/com/dream11/rest/provider/JsonProvider.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,3 @@
package com.dream11.rest.provider;

import com.dream11.rest.annotation.TypeValidationError;
import com.dream11.rest.exception.RestException;
import com.fasterxml.jackson.databind.JsonMappingException;
import io.vertx.core.json.jackson.DatabindCodec;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import lombok.SneakyThrows;
import org.apache.http.HttpStatus;
import org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider;

@Provider
@Consumes({"application/json", "application/*+json", "text/json"})
@Produces({"application/json", "application/*+json", "text/json"})
public class JsonProvider extends ResteasyJackson2Provider {

public JsonProvider() {
this.setMapper(DatabindCodec.mapper());
}

@SneakyThrows
@Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) {
try {
return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
} catch (JsonMappingException e) {
List<JsonMappingException.Reference> referenceList = e.getPath();
if (!referenceList.isEmpty()) {
String fieldName = referenceList.get(0).getFieldName();
if (fieldName != null) {
TypeValidationError typeValidationError = type.getDeclaredField(fieldName).getAnnotation(TypeValidationError.class);
if (typeValidationError != null) {
throw new RestException(typeValidationError.code(), typeValidationError.message(),
typeValidationError.httpStatusCode(), e);
}
}
}
throw new RestException("INVALID_REQUEST", e.getMessage(), HttpStatus.SC_BAD_REQUEST, e);
} catch (IOException e) {
throw new RestException("INVALID_REQUEST", e.getMessage(), HttpStatus.SC_BAD_REQUEST, e);
}
}
}
public interface JsonProvider {}
54 changes: 54 additions & 0 deletions src/main/java/com/dream11/rest/provider/impl/JacksonProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.dream11.rest.provider.impl;

import com.dream11.rest.annotation.TypeValidationError;
import com.dream11.rest.exception.RestException;
import com.dream11.rest.provider.JsonProvider;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import lombok.SneakyThrows;
import org.apache.http.HttpStatus;
import org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider;

@Provider
@Consumes({"application/json", "application/*+json", "text/json"})
@Produces({"application/json", "application/*+json", "text/json"})
public class JacksonProvider extends ResteasyJackson2Provider implements JsonProvider {

public JacksonProvider(ObjectMapper mapper) {
this.setMapper(mapper);
}

@SneakyThrows
@Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) {
try {
return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
} catch (JsonMappingException e) {
List<JsonMappingException.Reference> referenceList = e.getPath();
if (!referenceList.isEmpty()) {
String fieldName = referenceList.get(0).getFieldName();
if (fieldName != null) {
TypeValidationError typeValidationError = type.getDeclaredField(fieldName).getAnnotation(TypeValidationError.class);
if (typeValidationError != null) {
throw new RestException(typeValidationError.code(), typeValidationError.message(),
typeValidationError.httpStatusCode(), e);
}
}
}
throw new RestException("INVALID_REQUEST", e.getMessage(), HttpStatus.SC_BAD_REQUEST, e);
} catch (IOException e) {
throw new RestException("INVALID_REQUEST", e.getMessage(), HttpStatus.SC_BAD_REQUEST, e);
}
}
}
12 changes: 12 additions & 0 deletions src/test/java/com/dream11/rest/RestApiIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ void positiveBodyTest() {
assertThat(response.statusCode()).isEqualTo(200);
}

@Test
void extraFieldsTest() {
// arrange
String path = String.format("%s", Constants.VALIDATION_ROUTE_PATH);
JsonObject body = new JsonObject().put("resourceId", "1").put("extraKey", "extraValue");
// act
HttpResponse<Buffer> response = this.makePostRequest(path, body)
.blockingGet();
// assert
assertThat(response.statusCode()).isEqualTo(200);
}

@Test
void routeNotFoundTest() {
// arrange
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/com/dream11/rest/verticle/RestVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import com.dream11.rest.ClassInjector;
import com.dream11.rest.Constants;
import com.dream11.rest.injector.GuiceInjector;
import com.dream11.rest.provider.JsonProvider;
import com.dream11.rest.provider.impl.JacksonProvider;
import com.dream11.rest.util.SharedDataUtil;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.http.HttpServerOptions;

public class RestVerticle extends AbstractRestVerticle {
Expand All @@ -18,4 +22,9 @@ public RestVerticle() {
protected ClassInjector getInjector() {
return SharedDataUtil.getInstance(this.vertx.getDelegate(), GuiceInjector.class);
}

@Override
protected JsonProvider getJsonProvider() {
return new JacksonProvider(new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
}
}

0 comments on commit 66ff867

Please sign in to comment.