diff --git a/qqq-middleware-lambda/pom.xml b/qqq-middleware-lambda/pom.xml index b9b3291b..fda13437 100644 --- a/qqq-middleware-lambda/pom.xml +++ b/qqq-middleware-lambda/pom.xml @@ -33,7 +33,10 @@ - + + + 0.10 + 0.10 @@ -115,11 +118,6 @@ *:* META-INF/* - @@ -133,65 +131,6 @@ - - - com.github.seanroy - lambda-maven-plugin - 2.3.4 - - qqq-middleware-lambda/target/qqq-middleware-lambda-0.6.0-SNAPSHOT.jar - LATEST - - - us-east-1 - - - nutrifresh-dkelkhoff-test-bucket - true - true - - [ - { - "functionName": "anotherLambda-from-pom", - "description": "TODO", - "handler": "com.kingsrook.qqq.lambda.AnotherHandler::handleRequest", - "timeout": 1000, - "memorySize": 512, - "keepAlive": 5, - "environmentVariables": { "DEPLOYMENT_MODE": "test" } - } - ] - - - - - javax.xml.bind - jaxb-api - 2.3.0 - - - com.sun.xml.bind - jaxb-core - 2.3.0 - - - com.sun.xml.bind - jaxb-impl - 2.3.0 - - - javax.activation - javax.activation-api - 1.2.0 - - - commons-codec - commons-codec - 1.11 - - - - diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java similarity index 69% rename from qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java rename to qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java index deb03c0a..61ed6ff9 100644 --- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java @@ -44,22 +44,39 @@ import org.json.JSONObject; /******************************************************************************* - ** QQQ base class for lambda handlers. Meant to be sub-classed, where QQQ apps - ** can just override `handleJsonRequest`, and avoid seeing the lambda-ness of - ** lambda. + ** Abstract base class for any and all QQQ lambda handlers. + ** + ** This class provides the method `handleRequest(InputStream, OutputStream, Context)`, + ** which is what gets invoked by AWS Lambda. In there, we parse the data from + ** the inputStream to build a QLambdaRequest - which is then passed to: + ** + ** `handleRequest(QLambdaRequest)` - which would be meant for implementing in a + ** subclass. ** - ** Such subclasses can then have easy standalone unit tests - just testing their - ** logic, and not the lambda-ness. *******************************************************************************/ -public class QBasicLambdaHandler implements RequestStreamHandler +public abstract class QAbstractLambdaHandler implements RequestStreamHandler { private static Logger LOG; // = LogManager.getLogger(QBasicLambdaHandler.class); - private Context context; + protected Context context; @SuppressWarnings("checkstyle:MemberName") - protected final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error"); - protected final QLambdaResponse OK = new QLambdaResponse(200); + public static final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error"); + public static final QLambdaResponse OK = new QLambdaResponse(200); + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QAbstractLambdaHandler() + { + /////////////////////////////////////////////////////////////////////// + // tell log4j to use a config that won't fail when running in lambda // + /////////////////////////////////////////////////////////////////////// + Configurator.initialize(null, "qqq-lambda-log4j2.xml"); + } @@ -70,20 +87,15 @@ public class QBasicLambdaHandler implements RequestStreamHandler { this.context = context; - /////////////////////////////////////////////////////////////////////// - // tell log4j to use a config that won't fail when running in lambda // - /////////////////////////////////////////////////////////////////////// - Configurator.initialize(null, "qqq-lambda-log4j2.xml"); - String requestId = "unknown"; try { String input = IOUtils.toString(inputStream, "UTF-8"); log("Full Input: " + input); - ////////////////////////////////////////////////////////////////////////////////// - // parse the input as json - then pull parts out of it that we know to look for // - ////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// + // parse the Lambda input as json - then pull parts out of it that we know to look for // + ///////////////////////////////////////////////////////////////////////////////////////// JSONObject inputJsonObject; try { @@ -95,35 +107,14 @@ public class QBasicLambdaHandler implements RequestStreamHandler return; } - JSONObject headers = inputJsonObject.optJSONObject("headers"); - String contentType = headers != null ? headers.optString("content-type") : null; - String path = inputJsonObject.optString("rawPath"); - String queryString = inputJsonObject.optString("rawQueryString"); - JSONObject requestContext = inputJsonObject.optJSONObject("requestContext"); - requestId = requestContext.optString("requestId"); + QLambdaRequest request = new QLambdaRequest(inputJsonObject); + requestId = request.getRequestContext().optString("requestId"); - if("application/json".equals(contentType)) - { - String body = inputJsonObject.optString("body"); - - JSONObject bodyJsonObject; - try - { - bodyJsonObject = JsonUtils.toJSONObject(body); - } - catch(JSONException je) - { - writeResponse(outputStream, requestId, new QLambdaResponse(400, "Unable to parse request body as JSON: " + je.getMessage())); - return; - } - - QLambdaResponse response = handleJsonRequest(new QLambdaRequest(headers, path, queryString, bodyJsonObject)); - writeResponse(outputStream, requestId, response); - } - else - { - writeResponse(outputStream, requestId, new QLambdaResponse(400, "Unsupported content-type: " + contentType)); - } + ///////////////////////////////////////////////////////////////////////////////////// + // pass the request downstream, to get back a response we can write back to Lambda // + ///////////////////////////////////////////////////////////////////////////////////// + QLambdaResponse response = handleRequest(request); + writeResponse(outputStream, requestId, response); } catch(QUserFacingException ufe) { @@ -137,10 +128,17 @@ public class QBasicLambdaHandler implements RequestStreamHandler + /******************************************************************************* + ** + *******************************************************************************/ + protected abstract QLambdaResponse handleRequest(QLambdaRequest request) throws QException; + + + /******************************************************************************* ** Meant to be overridden by subclasses, to provide functionality, if needed. *******************************************************************************/ - protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException + protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException { log(this.getClass().getSimpleName() + " did not override handleJsonRequest - so noop and return 200."); return (OK); @@ -171,12 +169,23 @@ public class QBasicLambdaHandler implements RequestStreamHandler + /******************************************************************************* + ** Write to the cloudwatch logs. + *******************************************************************************/ + protected void log(String message, Throwable t) + { + log(message); + log(t); + } + + + /******************************************************************************* ** *******************************************************************************/ - protected void log(Throwable e) + protected void log(Throwable t) { - if(e == null) + if(t == null) { return; } @@ -185,13 +194,13 @@ public class QBasicLambdaHandler implements RequestStreamHandler { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); + t.printStackTrace(pw); log(sw + "\n"); } else { initLOG(); - LOG.warn("Exception", e); + LOG.warn("Exception", t); } } @@ -204,7 +213,7 @@ public class QBasicLambdaHandler implements RequestStreamHandler { if(LOG == null) { - LOG = LogManager.getLogger(QBasicLambdaHandler.class); + LOG = LogManager.getLogger(QAbstractLambdaHandler.class); } } @@ -213,7 +222,7 @@ public class QBasicLambdaHandler implements RequestStreamHandler /******************************************************************************* ** *******************************************************************************/ - private void writeResponse(OutputStream outputStream, String requestId, QLambdaResponse response) throws IOException + protected void writeResponse(OutputStream outputStream, String requestId, QLambdaResponse response) throws IOException { QLambdaResponse.Body body = response.getBody(); body.setRequestId(requestId); diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java new file mode 100644 index 00000000..e3c1d5f3 --- /dev/null +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java @@ -0,0 +1,85 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.lambda; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.lambda.model.QLambdaRequest; +import com.kingsrook.qqq.lambda.model.QLambdaResponse; +import org.json.JSONException; +import org.json.JSONObject; + + +/******************************************************************************* + ** QQQ base class for "Custom" lambda handlers. e.g., completely custom code + ** to run in your QQQ app, outside of tables or processes (for those standard + ** use-cases, see QStandardLambdaHandler). + ** + ** Subclasses here can just override `handleJsonRequest`, and avoid seeing the + ** lambda-ness of lambda. + ** + ** Such subclasses can then have easy standalone unit tests - just testing their + ** logic, and not the lambda-ness. + *******************************************************************************/ +public class QBaseCustomLambdaHandler extends QAbstractLambdaHandler +{ + + /******************************************************************************* + ** + *******************************************************************************/ + protected QLambdaResponse handleRequest(QLambdaRequest request) throws QException + { + String contentType = request.getHeaders().optString("content-type"); + if("application/json".equals(contentType)) + { + JSONObject bodyJsonObject; + try + { + bodyJsonObject = JsonUtils.toJSONObject(request.getBody()); + } + catch(JSONException je) + { + return (new QLambdaResponse(400, "Unable to parse request body as JSON: " + je.getMessage())); + } + + return (handleJsonRequest(request, bodyJsonObject)); + } + else + { + return (new QLambdaResponse(400, "Unsupported content-type: " + contentType)); + } + } + + + + /******************************************************************************* + ** Meant to be overridden by subclasses, to provide functionality, if needed. + *******************************************************************************/ + @Override + protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException + { + log(this.getClass().getSimpleName() + " did not override handleJsonRequest - so noop and return 200."); + return (OK); + } + +} diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java new file mode 100644 index 00000000..5b29d6a4 --- /dev/null +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java @@ -0,0 +1,290 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.lambda; + + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager; +import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException; +import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; +import com.kingsrook.qqq.lambda.model.QLambdaRequest; +import com.kingsrook.qqq.lambda.model.QLambdaResponse; +import org.json.JSONObject; + + +/******************************************************************************* + ** Class to provide QQQ Standard table & process actions via AWS Lambda. + ** + ** Right now, an application is responsible for: + ** - in a constructor, calling setQInstance. + ** - overriding setupSession to do a default system-session... + *******************************************************************************/ +public class QStandardLambdaHandler extends QAbstractLambdaHandler +{ + protected QInstance qInstance; + + + + /******************************************************************************* + ** Setter for qInstance + ** + *******************************************************************************/ + public void setQInstance(QInstance qInstance) + { + this.qInstance = qInstance; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + protected void setupSession(QLambdaRequest request, AbstractActionInput actionInput) + { + actionInput.setSession(new QSession()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + protected QLambdaResponse handleRequest(QLambdaRequest request) throws QException + { + String path = request.getPath(); + String[] pathParts = path.split("/"); + + String httpMethod = "unknown"; + if(request.getRequestContext().has("http")) + { + JSONObject httpObject = request.getRequestContext().getJSONObject("http"); + httpMethod = httpObject.optString("method"); + } + + if(path.matches("/processes/\\w+/init/?")) + { + return (processInit(request, pathParts[2])); + } + if(path.matches("/metaData/?")) + { + // todo return (metaData(request)); + } + if(path.matches("/metaData/table/\\w+/?")) + { + // todo return (tableMetaData(request)); + } + if(path.matches("/metaData/process/\\w+/?")) + { + // todo return (processMetaData(request)); + } + if(path.matches("/data/table/\\w+/?") || path.matches("/data/table/\\w+/query/?")) + { + // todo return (dataQuery(request)); + } + if(path.matches("/data/table/\\w+/count/?")) + { + // todo return (dataCount(request)); + } + if(path.matches("/data/table/\\w+/export/?")) + { + // todo return (dataExportWithoutFilename(request)); + } + if(path.matches("/data/table/\\w+/export/\\w+/?")) + { + // todo return (dataExportWithFilename(request)); + } + if(path.matches("/data/table/\\w+/prossibleValues/\\w+/?")) + { + // todo return (possibleValues(request)); + } + if(path.matches("/data/table/\\w+/\\w+/?")) + { + if("GET".equals(httpMethod)) + { + // todo return (dataGet(request)); + } + else if("PATCH".equals(httpMethod)) + { + // todo return (dataUpdate(request)); + } + else if("PUT".equals(httpMethod)) + { + // todo return (dataUpdate(request)); + } + else if("DELETE".equals(httpMethod)) + { + // todo return (dataDelete(request)); + } + else + { + // todo return (new QLambdaResponse(405, "Unrecognized method: " + httpMethod)); + } + } + if(path.matches("/widget/\\w+/?")) + { + // todo return (widget(request)); + } + else + { + return (new QLambdaResponse(404, "Unrecognized path: " + path)); + } + + return (GENERIC_SERVER_ERROR); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private QLambdaResponse processInit(QLambdaRequest request, String processName) + { + String processUUID = null; + String startAfterStep = null; + + Map resultForCaller = new HashMap<>(); + QLambdaResponse response; + + try + { + if(processUUID == null) + { + processUUID = UUID.randomUUID().toString(); + } + resultForCaller.put("processUUID", processUUID); + + log(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]" + : "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]"); + + RunProcessInput runProcessInput = new RunProcessInput(qInstance); + setupSession(request, runProcessInput); + runProcessInput.setProcessName(processName); + runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK); + runProcessInput.setProcessUUID(processUUID); + runProcessInput.setStartAfterStep(startAfterStep); + populateRunProcessRequestWithValuesFromContext(request, runProcessInput); + + //////////////////////////////////////// + // run the process as an async action // + //////////////////////////////////////// + Integer timeout = 60_000; // getTimeoutMillis(context); + RunProcessOutput runProcessOutput = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) -> + { + runProcessInput.setAsyncJobCallback(callback); + return (new RunProcessAction().execute(runProcessInput)); + }); + + log("Process result error? " + runProcessOutput.getException()); + for(QFieldMetaData outputField : qInstance.getProcess(runProcessInput.getProcessName()).getOutputFields()) + { + log("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName())); + } + + serializeRunProcessResultForCaller(resultForCaller, runProcessOutput); + } + catch(JobGoingAsyncException jgae) + { + resultForCaller.put("jobUUID", jgae.getJobUUID()); + } + catch(Exception e) + { + ////////////////////////////////////////////////////////////////////////////// + // our other actions in here would do: handleException(context, e); // + // which would return a 500 to the client. // + // but - other process-step actions, they always return a 200, just with an // + // optional error message - so - keep all of the processes consistent. // + ////////////////////////////////////////////////////////////////////////////// + serializeRunProcessExceptionForCaller(resultForCaller, e); + } + + response = new QLambdaResponse(200); + response.getBody().setBody(resultForCaller); + + return (response); + } + + + + /******************************************************************************* + ** Whether a step finished synchronously or asynchronously, return its data + ** to the caller the same way. + *******************************************************************************/ + private void serializeRunProcessResultForCaller(Map resultForCaller, RunProcessOutput runProcessOutput) + { + if(runProcessOutput.getException().isPresent()) + { + //////////////////////////////////////////////////////////////// + // per code coverage, this path may never actually get hit... // + //////////////////////////////////////////////////////////////// + serializeRunProcessExceptionForCaller(resultForCaller, runProcessOutput.getException().get()); + } + resultForCaller.put("values", runProcessOutput.getValues()); + runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void serializeRunProcessExceptionForCaller(Map resultForCaller, Exception exception) + { + QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(exception, QUserFacingException.class); + + if(userFacingException != null) + { + log("User-facing exception in process", userFacingException); + resultForCaller.put("error", userFacingException.getMessage()); + resultForCaller.put("userFacingError", userFacingException.getMessage()); + } + else + { + Throwable rootException = ExceptionUtils.getRootException(exception); + log("Uncaught Exception in process", exception); + resultForCaller.put("error", "Error message: " + rootException.getMessage()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void populateRunProcessRequestWithValuesFromContext(QLambdaRequest request, RunProcessInput runProcessInput) + { + runProcessInput.addValue("body", request.getBody()); + // todo - a lot more... + } + +} diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java index 73cded36..5e4b0f32 100644 --- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java @@ -23,22 +23,23 @@ package com.kingsrook.qqq.lambda.examples; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.lambda.QBasicLambdaHandler; +import com.kingsrook.qqq.lambda.QBaseCustomLambdaHandler; import com.kingsrook.qqq.lambda.model.QLambdaRequest; import com.kingsrook.qqq.lambda.model.QLambdaResponse; +import org.json.JSONObject; /******************************************************************************* ** *******************************************************************************/ -public class ExampleLambdaHandler extends QBasicLambdaHandler +public class ExampleLambdaHandler extends QBaseCustomLambdaHandler { /******************************************************************************* ** *******************************************************************************/ @Override - protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException + protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException { log("In subclass: " + getClass().getSimpleName()); return (OK); diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java index 2f926ce6..b067f9a8 100644 --- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java @@ -33,14 +33,29 @@ public class QLambdaRequest private JSONObject headers; private String path; private String queryString; - private JSONObject body; + private String body; + private JSONObject requestContext; /******************************************************************************* ** *******************************************************************************/ - public QLambdaRequest(JSONObject headers, String path, String queryString, JSONObject body) + public QLambdaRequest(JSONObject inputJsonObject) + { + this.headers = inputJsonObject.optJSONObject("headers"); + this.path = inputJsonObject.optString("rawPath"); + this.queryString = inputJsonObject.optString("rawQueryString"); + this.body = inputJsonObject.optString("body"); + this.requestContext = inputJsonObject.optJSONObject("requestContext"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QLambdaRequest(JSONObject headers, String path, String queryString, String body) { this.headers = headers; this.path = path; @@ -87,8 +102,19 @@ public class QLambdaRequest ** Getter for body ** *******************************************************************************/ - public JSONObject getBody() + public String getBody() { return body; } + + + + /******************************************************************************* + ** Getter for requestContext + ** + *******************************************************************************/ + public JSONObject getRequestContext() + { + return requestContext; + } } \ No newline at end of file diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java index 24f1734f..c8dfc8ac 100644 --- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java +++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java @@ -132,8 +132,9 @@ public class QLambdaResponse *******************************************************************************/ public static class Body { - private String requestId; - private String errorMessage; + private String requestId; + private String errorMessage; + private Map body; @@ -197,6 +198,28 @@ public class QLambdaResponse { return errorMessage; } + + + + /******************************************************************************* + ** Getter for body + ** + *******************************************************************************/ + public Map getBody() + { + return body; + } + + + + /******************************************************************************* + ** Setter for body + ** + *******************************************************************************/ + public void setBody(Map body) + { + this.body = body; + } } } diff --git a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java similarity index 82% rename from qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java rename to qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java index c766968f..935f616d 100644 --- a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java +++ b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java @@ -45,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* ** Unit test for com.kingsrook.qqq.lambda.AnotherHandler *******************************************************************************/ -class QBasicLambdaHandlerTest +class QBaseCustomLambdaHandlerTest { /******************************************************************************* @@ -108,6 +108,21 @@ class QBasicLambdaHandlerTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNoBodyInputStringNonJsonContentType() throws IOException + { + String inputString = getNoBodyInputString(); + String outputString = runHandleRequest(inputString); + JSONObject outputJson = JsonUtils.toJSONObject(outputString); + assertTrue(outputJson.has("errorMessage")); + assertThat(outputJson.getString("errorMessage")).contains("Unsupported content-type:"); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -118,10 +133,10 @@ class QBasicLambdaHandlerTest ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Context context = new MockContext(); - new QBasicLambdaHandler() + new QBaseCustomLambdaHandler() { @Override - protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException + protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException { log(new QException("Test Exception")); return (OK); @@ -139,7 +154,7 @@ class QBasicLambdaHandlerTest InputStream inputStream = new ByteArrayInputStream(inputString.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Context context = new MockContext(); - new QBasicLambdaHandler().handleRequest(inputStream, outputStream, context); + new QBaseCustomLambdaHandler().handleRequest(inputStream, outputStream, context); String outputString = outputStream.toString(StandardCharsets.UTF_8); System.out.println(outputString); return outputString; @@ -174,7 +189,7 @@ class QBasicLambdaHandlerTest @Test void testHandleRequest() throws QException { - QLambdaResponse response = new QBasicLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "/", "", new JSONObject())); + QLambdaResponse response = new QBaseCustomLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "/", "", ""), new JSONObject()); assertEquals(200, response.getStatusCode()); } @@ -351,4 +366,47 @@ class QBasicLambdaHandlerTest """); } + + + private String getNoBodyInputString() + { + return (""" + { + "version": "2.0", + "routeKey": "$default", + "rawPath": "/", + "rawQueryString": "", + "headers": { + "x-amzn-tls-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256", + "x-amzn-tls-version": "TLSv1.2", + "x-amzn-trace-id": "Root=1-6349b9a1-4d3785872159571e302a3015", + "x-forwarded-proto": "https", + "host": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp.lambda-url.us-east-1.on.aws", + "x-forwarded-port": "443", + "x-forwarded-for": "24.217.225.229", + "accept": "*/*", + "user-agent": "curl/7.79.1" + }, + "requestContext": { + "accountId": "anonymous", + "apiId": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp", + "domainName": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp.lambda-url.us-east-1.on.aws", + "domainPrefix": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp", + "http": { + "method": "GET", + "path": "/", + "protocol": "HTTP/1.1", + "sourceIp": "24.217.225.229", + "userAgent": "curl/7.79.1" + }, + "requestId": "82b80656-5f05-46eb-bc2a-7b8328d095d4", + "routeKey": "$default", + "stage": "$default", + "time": "14/Oct/2022:19:33:53 +0000", + "timeEpoch": 1665776033574 + }, + "isBase64Encoded": false + } + """); + } } \ No newline at end of file diff --git a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java index 76aa9e93..72787612 100644 --- a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java +++ b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java @@ -42,7 +42,7 @@ class ExampleLambdaHandlerTest @Test void test() throws QException { - QLambdaResponse qLambdaResponse = new ExampleLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "", "", new JSONObject())); + QLambdaResponse qLambdaResponse = new ExampleLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "", "", ""), new JSONObject()); assertEquals(200, qLambdaResponse.getStatusCode()); }