diff --git a/README.md b/README.md index 6784db1..ce48eb9 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,16 @@ The deserialization and serialization strategies are based on two http headers: - **Content-Type** for deserialization - **Accept** for serialization +The current deserializers support: + + - **JSON** request body deserialization for **Content-Type: application/json** + - **XML** request body deserialization for **Content-Type: text/xml** + + and the current serializers support: + + - **JSON** response body serializer for **Accept: application/json** + - **XML** response body serializer for **Accept: text/xml** + If you want to provide your own request body deserializer, the method *resolveDeserializerStrategy* should be overridden: ```java @Override @@ -306,62 +316,62 @@ Eg.: ``` project -└───src -│ └───main -│ └───resources -│ lambda-spec.json +????????????src +??? ????????????main +??? ????????????resources +??? lambda-spec.json ``` ```json { - "context": { - "awsRequestId": "", - "logGroupName": "", - "logStreamName": "", - "functionName": "", - "functionVersion": "", - "invokedFunctionArn": "", - "identity": null, - "clientContext": { - "client": { - "installationId": "", - "appTitle": "", - "appVersionName": "", - "appVersionCode": "", - "appPackageName": "" - }, - "custom": { - - }, - "environment": { + "context": { + "awsRequestId": "", + "logGroupName": "", + "logStreamName": "", + "functionName": "", + "functionVersion": "", + "invokedFunctionArn": "", + "identity": null, + "clientContext": { + "client": { + "installationId": "", + "appTitle": "", + "appVersionName": "", + "appVersionCode": "", + "appPackageName": "" + }, + "custom": { - } }, - "remainingTimeInMillis": 30, - "memoryLimitInMB": 128, - "logger": null + "environment": { + + } + }, + "remainingTimeInMillis": 30, + "memoryLimitInMB": 128, + "logger": null }, "request":{ - "path": "/users", - "pathParameters": { + "path": "/users", + "pathParameters": { - }, - "queryStringParameters": { + }, + "queryStringParameters": { - }, - "resource": "users", - "stageVariables": { + }, + "resource": "users", + "stageVariables": { - }, - "method":"POST", - "headers": { + }, + "method":"POST", + "headers": { - }, - "body": { - "name": "my name", - "message": "This is my message" - } + }, + "body": { + "name": "my name", + "message": "This is my message" } + } } ``` @@ -387,7 +397,7 @@ public class LambdaHandler extends AbstractRequestHandler + 4.0.0 br.com.tdsis lambda-forest - 1.0.0 + 1.1.0-SNAPSHOT jar lambda-forest @@ -28,6 +27,7 @@ 5.2.1.Final 2.2.4 2.2.4 + 2.9.0 4.12 1.10.19 1.6.7 @@ -62,7 +62,8 @@ scm:git:git://github.com/tdsis/lambda-forest.git scm:git:ssh://github.com:tdsis/lambda-forest.git http://github.com/tdsis/lambda-forest/tree/master - + 1.1.0 + @@ -116,6 +117,11 @@ javax.el ${javax.el.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.dataformat.xm.version} + junit junit diff --git a/src/main/java/br/com/tdsis/lambda/forest/domain/LambdaRequestSpec.java b/src/main/java/br/com/tdsis/lambda/forest/domain/LambdaRequestSpec.java index 2f456dd..eefa3b6 100644 --- a/src/main/java/br/com/tdsis/lambda/forest/domain/LambdaRequestSpec.java +++ b/src/main/java/br/com/tdsis/lambda/forest/domain/LambdaRequestSpec.java @@ -26,7 +26,7 @@ public class LambdaRequestSpec { private String path; private String method; private Map headers; - private Map body; + private Object body; private Map pathParameters; private Map queryStringParameters; private Map stageVariables; @@ -111,7 +111,7 @@ public void setHeaders(Map headers) { * * @return body The request body */ - public Map getBody() { + public Object getBody() { return body; } @@ -120,7 +120,7 @@ public Map getBody() { * * @param body The request body */ - public void setBody(Map body) { + public void setBody(Object body) { this.body = body; } diff --git a/src/main/java/br/com/tdsis/lambda/forest/http/DeserializerStrategy.java b/src/main/java/br/com/tdsis/lambda/forest/http/DeserializerStrategy.java index 883f217..21564e6 100644 --- a/src/main/java/br/com/tdsis/lambda/forest/http/DeserializerStrategy.java +++ b/src/main/java/br/com/tdsis/lambda/forest/http/DeserializerStrategy.java @@ -7,6 +7,7 @@ import br.com.tdsis.lambda.forest.http.handler.AbstractRequestHandler; import br.com.tdsis.lambda.forest.json.JsonRequestBodyDeserializerStrategy; +import br.com.tdsis.lambda.forest.xml.XmlRequestBodyDeserializerStrategy; /** * The DeserializerStrategy enum @@ -16,6 +17,7 @@ * The current supported content type deserializers are: *
    *
  • {@code JsonRequestBodyDeserializerStrategy} for application/json
  • + *
  • {@code XmlRequestBodyDeserializerStrategy} for text/xml
  • *
*

* If a specific content type deserializer is not registered in this enum the @@ -28,9 +30,14 @@ public enum DeserializerStrategy { /** - * The Request body deserializer for content type application/json + * The request body deserializer for content type application/json */ - APPLICATION_JSON(ContentType.APPLICATION_JSON.getMimeType(), new JsonRequestBodyDeserializerStrategy()); + APPLICATION_JSON(ContentType.APPLICATION_JSON.getMimeType(), new JsonRequestBodyDeserializerStrategy()), + + /** + * The request body deserializer for contente type text/xml + */ + TEXT_XML(ContentType.TEXT_XML.getMimeType(), new XmlRequestBodyDeserializerStrategy()); private String contentType; private RequestBodyDeserializerStrategy deserializer; diff --git a/src/main/java/br/com/tdsis/lambda/forest/http/SerializerStrategy.java b/src/main/java/br/com/tdsis/lambda/forest/http/SerializerStrategy.java index bb6bce7..4bf13d7 100644 --- a/src/main/java/br/com/tdsis/lambda/forest/http/SerializerStrategy.java +++ b/src/main/java/br/com/tdsis/lambda/forest/http/SerializerStrategy.java @@ -7,6 +7,7 @@ import br.com.tdsis.lambda.forest.http.handler.AbstractRequestHandler; import br.com.tdsis.lambda.forest.json.JsonResponseBodySerializerStrategy; +import br.com.tdsis.lambda.forest.xml.XmlResponseBodySerializerStrategy; /** * The SerializerStrategy enum @@ -16,6 +17,7 @@ * The current supported serializers are: *

    *
  • {@code JsonResponseBodySerializerStrategy} for application/json
  • + *
  • {@code XmlResponseBodySerializerStrategy} for text/xml
  • *
*

* If a specific serializer is not registered in this enum the @@ -30,9 +32,13 @@ public enum SerializerStrategy { /** * The response body serializer for application/json */ - APPLICATION_JSON(ContentType.APPLICATION_JSON.getMimeType(), new JsonResponseBodySerializerStrategy()); - + APPLICATION_JSON(ContentType.APPLICATION_JSON.getMimeType(), new JsonResponseBodySerializerStrategy()), + /** + * The response body serializer for text/xml + */ + TEXT_XML(ContentType.TEXT_XML.getMimeType(), new XmlResponseBodySerializerStrategy()); + private String contentType; private ResponseBodySerializerStrategy serializer; diff --git a/src/main/java/br/com/tdsis/lambda/forest/util/LambdaRunner.java b/src/main/java/br/com/tdsis/lambda/forest/util/LambdaRunner.java index 48e8d72..a609b5f 100644 --- a/src/main/java/br/com/tdsis/lambda/forest/util/LambdaRunner.java +++ b/src/main/java/br/com/tdsis/lambda/forest/util/LambdaRunner.java @@ -2,10 +2,12 @@ import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -73,6 +75,32 @@ public RunnerResult printHeader(){ return this; } + /** + * + * Prints the execution response entity (headers, status code and response body). Pretty print body + * + * @return {@link RunnerResult} + * + * + */ + public RunnerResult prettyPrint(){ + try { + if(response == null){ + print(null); + return this; + } + Map resp = MAPPER.readValue(MAPPER.writeValueAsString(response), new TypeReference>() {}); + if(response.getBody() != null){ + Object body = MAPPER.readValue(response.getBody(), Object.class); + resp.put("body", body); + } + print(resp); + } catch (IOException e) { + e.printStackTrace(System.out); + } + return this; + } + /** * * Prints the execution response body @@ -155,8 +183,11 @@ private static HttpRequest buildHttpRequest(LambdaRequestSpec requestSpec) throw String resource = requestSpec.getResource(); String path = requestSpec.getPath(); - String method = requestSpec.getMethod(); - String json = MAPPER.writeValueAsString(requestSpec.getBody()); + String method = requestSpec.getMethod(); + + Object body = requestSpec.getBody(); + String json = body instanceof String ? body.toString() : MAPPER.writeValueAsString(body); + Map headers = requestSpec.getHeaders(); Map pathParameters = requestSpec.getPathParameters(); Map queryStringParameters = requestSpec.getQueryStringParameters(); diff --git a/src/main/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategy.java b/src/main/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategy.java new file mode 100644 index 0000000..3231f75 --- /dev/null +++ b/src/main/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategy.java @@ -0,0 +1,52 @@ +package br.com.tdsis.lambda.forest.xml; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import br.com.tdsis.lambda.forest.domain.DefaultResponseError; +import br.com.tdsis.lambda.forest.http.RequestBodyDeserializerStrategy; +import br.com.tdsis.lambda.forest.http.exception.BadRequestException; +import br.com.tdsis.lambda.forest.http.exception.HttpException; +import br.com.tdsis.lambda.forest.http.exception.InternalServerErrorException; + +/** + * The XML request body deserializer strategy + * + * @author nmelo + */ +public class XmlRequestBodyDeserializerStrategy implements RequestBodyDeserializerStrategy { + + public static final String INVALID_XML_MESSAGE = "Invalid XML"; + public static final String INVALID_XML_ATTRIBUTE = "Invalid XML attribute: %s"; + + @Override + @SuppressWarnings("unchecked") + public T deserialize(String body, Class entityClass) throws HttpException { + XmlMapper mapper = new XmlMapper(); + + try { + + return (T) mapper.readValue(body, entityClass); + + } catch (JsonParseException e) { + throw new BadRequestException(new DefaultResponseError(INVALID_XML_MESSAGE)); + + } catch (JsonMappingException e) { + String message = INVALID_XML_MESSAGE; + + if (e instanceof UnrecognizedPropertyException){ + message = String.format(INVALID_XML_ATTRIBUTE, + ((UnrecognizedPropertyException) e).getPropertyName()); + } + + throw new BadRequestException(new DefaultResponseError(message)); + + } catch (Exception e) { + throw new InternalServerErrorException(e.getMessage(), e); + } + + } + +} diff --git a/src/main/java/br/com/tdsis/lambda/forest/xml/XmlResponseBodySerializerStrategy.java b/src/main/java/br/com/tdsis/lambda/forest/xml/XmlResponseBodySerializerStrategy.java new file mode 100644 index 0000000..fb1b935 --- /dev/null +++ b/src/main/java/br/com/tdsis/lambda/forest/xml/XmlResponseBodySerializerStrategy.java @@ -0,0 +1,38 @@ +package br.com.tdsis.lambda.forest.xml; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import br.com.tdsis.lambda.forest.http.ResponseBodySerializerStrategy; +import br.com.tdsis.lambda.forest.http.exception.HttpException; +import br.com.tdsis.lambda.forest.http.exception.InternalServerErrorException; + +/** + * The XML response body serializer strategy + * + * @author nmelo + */ +public class XmlResponseBodySerializerStrategy implements ResponseBodySerializerStrategy { + + @Override + public String serialize(Object entity) throws HttpException { + String xml = null; + + try { + + XmlMapper mapper = new XmlMapper(); + mapper.setSerializationInclusion(Include.NON_NULL); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + xml = mapper.writeValueAsString(entity); + + } catch (JsonProcessingException e) { + throw new InternalServerErrorException(e.getMessage(), e); + } + + return xml; + } + +} diff --git a/src/test/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategyTest.java b/src/test/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategyTest.java new file mode 100644 index 0000000..9747695 --- /dev/null +++ b/src/test/java/br/com/tdsis/lambda/forest/xml/XmlRequestBodyDeserializerStrategyTest.java @@ -0,0 +1,51 @@ +package br.com.tdsis.lambda.forest.xml; + +import org.junit.Assert; +import org.junit.Test; + +import br.com.tdsis.lambda.forest.http.exception.BadRequestException; +import br.com.tdsis.lambda.forest.http.exception.HttpException; +import br.com.tdsis.lambda.forest.http.exception.InternalServerErrorException; +import br.com.tdsis.lambda.forest.http.handler.UserRequestTest; +import br.com.tdsis.lambda.forest.json.JsonRequestBodyDeserializerStrategy; + +public class XmlRequestBodyDeserializerStrategyTest { + + @Test(expected = BadRequestException.class) + public void shouldThrowBadRequestException() throws HttpException { + XmlRequestBodyDeserializerStrategy deserializer = new XmlRequestBodyDeserializerStrategy(); + deserializer.deserialize("any", UserRequestTest.class); + } + + @Test(expected = BadRequestException.class) + public void shouldThrowBadRequestExceptionWithUnrecognizedAttribute() throws HttpException { + XmlRequestBodyDeserializerStrategy deserializer = new XmlRequestBodyDeserializerStrategy(); + deserializer.deserialize("any", UserRequestTest.class); + } + + @Test(expected = BadRequestException.class) + public void shouldThrowBadRequestExceptionWithUnknownAttribute() throws HttpException { + JsonRequestBodyDeserializerStrategy deserializer = new JsonRequestBodyDeserializerStrategy(); + deserializer.deserialize("any", UserRequestTest.class); + } + + @Test(expected = InternalServerErrorException.class) + public void shouldThrowInternalServerErrorException() throws HttpException { + XmlRequestBodyDeserializerStrategy deserializer = new XmlRequestBodyDeserializerStrategy(); + deserializer.deserialize(null, UserRequestTest.class); + } + + @Test + public void shouldDeserializeBean() throws HttpException { + XmlRequestBodyDeserializerStrategy deserializer = new XmlRequestBodyDeserializerStrategy(); + UserRequestTest bean = deserializer + .deserialize("Any Name

Any Address
", + UserRequestTest.class); + + Assert.assertNotNull(bean); + Assert.assertEquals("Any Name", bean.getName()); + Assert.assertEquals("Any Address", bean.getAddress()); + } + + +}