From 364e9f420b4d78eedca2755bda7056917c7d7712 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 30 May 2023 11:13:20 -0500 Subject: [PATCH] Update to support both JSON and multipart form bodies for create and update. --- .../javalin/QJavalinImplementation.java | 40 +++++++++ .../javalin/QJavalinImplementationTest.java | 82 ++++++++++++++++++- 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 77aaf73a..9c8174eb 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -654,6 +654,46 @@ public class QJavalinImplementation *******************************************************************************/ private static void setRecordValuesForInsertOrUpdate(Context context, QTableMetaData tableMetaData, QRecord record) throws IOException { + String contentType = Objects.requireNonNullElse(context.header("content-type"), ""); + boolean isContentTypeJson = contentType.toLowerCase().contains("json"); + + try + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // if the caller said they've sent JSON, or if they didn't supply a content-type, then try to read the body // + // as JSON. if it throws, we'll continue by trying to read form params, but if it succeeds, we'll return. // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(isContentTypeJson || !StringUtils.hasContent(contentType)) + { + Map map = context.bodyAsClass(Map.class); + for(Map.Entry entry : map.entrySet()) + { + String fieldName = ValueUtils.getValueAsString(entry.getKey()); + Object value = entry.getValue(); + + if(StringUtils.hasContent(String.valueOf(value))) + { + record.setValue(fieldName, (Serializable) value); + } + else if("".equals(value)) + { + /////////////////////////////////////////////////////////////////////////////////////////////////// + // if frontend sent us an empty string - put a null in the record's value map. // + // this could potentially be changed to be type-specific (e.g., store an empty-string for STRING // + // fields, but null for INTEGER, etc) - but, who really wants empty-string in database anyway? // + /////////////////////////////////////////////////////////////////////////////////////////////////// + record.setValue(fieldName, null); + } + } + + return; + } + } + catch(Exception e) + { + LOG.info("Error trying to read body as map", e, logPair("contentType", contentType)); + } + ///////////////////////// // process form params // ///////////////////////// diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java index 0fa2e777..722cba48 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java @@ -391,11 +391,13 @@ class QJavalinImplementationTest extends QJavalinTestBase /******************************************************************************* - ** test an insert + ** test an insert - posting the data as a JSON object. + ** + ** This was the original supported version, but multipart form was added in May 2023 ** *******************************************************************************/ @Test - public void test_dataInsert() + public void test_dataInsertJson() { Map body = new HashMap<>(); body.put("firstName", "Bobby"); @@ -425,11 +427,45 @@ class QJavalinImplementationTest extends QJavalinTestBase /******************************************************************************* - ** test an update + ** test an insert - posting a multipart form. ** *******************************************************************************/ @Test - public void test_dataUpdate() + public void test_dataInsertMultipartForm() + { + HttpResponse response = Unirest.post(BASE_URL + "/data/person") + .header("Content-Type", "application/json") + .multiPartContent() + .field("firstName", "Bobby") + .field("lastName", "Hull") + .field("email", "bobby@hull.com") + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertTrue(jsonObject.has("records")); + JSONArray records = jsonObject.getJSONArray("records"); + assertEquals(1, records.length()); + JSONObject record0 = records.getJSONObject(0); + assertTrue(record0.has("values")); + assertEquals("person", record0.getString("tableName")); + JSONObject values0 = record0.getJSONObject("values"); + assertTrue(values0.has("firstName")); + assertEquals("Bobby", values0.getString("firstName")); + assertTrue(values0.has("id")); + assertEquals(7, values0.getInt("id")); + } + + + + /******************************************************************************* + ** test an update - posting the data as a JSON object. + ** + ** This was the original supported version, but multipart form was added in May 2023 + ** + *******************************************************************************/ + @Test + public void test_dataUpdateJson() { Map body = new HashMap<>(); body.put("firstName", "Free"); @@ -465,6 +501,44 @@ class QJavalinImplementationTest extends QJavalinTestBase + /******************************************************************************* + ** test an update - posting the data as a multipart form + ** + *******************************************************************************/ + @Test + public void test_dataUpdateMultipartForm() + { + HttpResponse response = Unirest.patch(BASE_URL + "/data/person/4") + .multiPartContent() + .field("firstName", "Free") + .field("birthDate", "") + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertTrue(jsonObject.has("records")); + JSONArray records = jsonObject.getJSONArray("records"); + assertEquals(1, records.length()); + JSONObject record0 = records.getJSONObject(0); + assertTrue(record0.has("values")); + assertEquals("person", record0.getString("tableName")); + JSONObject values0 = record0.getJSONObject("values"); + assertEquals(4, values0.getInt("id")); + assertEquals("Free", values0.getString("firstName")); + + /////////////////////////////////////////////////////////////////// + // re-GET the record, and validate that birthDate was nulled out // + /////////////////////////////////////////////////////////////////// + response = Unirest.get(BASE_URL + "/data/person/4").asString(); + assertEquals(200, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertTrue(jsonObject.has("values")); + JSONObject values = jsonObject.getJSONObject("values"); + assertFalse(values.has("birthDate")); + } + + + /******************************************************************************* ** test a delete **