From 7625e593d201a9aab9bbcc71872b8a3af22f2b0e Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 2 May 2023 15:08:06 -0500 Subject: [PATCH] Fix merge conlifct w/ bulk insert; add warnings to single-insert; add tests re: insert warnings --- .../qqq/api/actions/ApiImplementation.java | 17 +++- .../qqq/api/javalin/QJavalinApiHandler.java | 87 ------------------- .../java/com/kingsrook/qqq/api/TestUtils.java | 34 ++++++++ .../api/javalin/QJavalinApiHandlerTest.java | 31 ++++++- 4 files changed, 79 insertions(+), 90 deletions(-) diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/ApiImplementation.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/ApiImplementation.java index 73441566..675d37b8 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/ApiImplementation.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/ApiImplementation.java @@ -382,6 +382,13 @@ public class ApiImplementation LinkedHashMap outputRecord = new LinkedHashMap<>(); outputRecord.put(table.getPrimaryKeyField(), insertOutput.getRecords().get(0).getValue(table.getPrimaryKeyField())); + + List warnings = insertOutput.getRecords().get(0).getWarnings(); + if(CollectionUtils.nullSafeHasContents(warnings)) + { + outputRecord.put("warning", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings)); + } + return (outputRecord); } @@ -456,13 +463,21 @@ public class ApiImplementation LinkedHashMap outputRecord = new LinkedHashMap<>(); response.add(outputRecord); - List errors = record.getErrors(); + List errors = record.getErrors(); + List warnings = record.getWarnings(); if(CollectionUtils.nullSafeHasContents(errors)) { outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode()); outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage()); outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors)); } + else if(CollectionUtils.nullSafeHasContents(warnings)) + { + outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode()); + outputRecord.put("statusText", HttpStatus.Code.CREATED.getMessage()); + outputRecord.put("warning", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings)); + outputRecord.put(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField())); + } else { outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode()); diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java index e5eeb534..5dbee240 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java @@ -902,93 +902,6 @@ public class QJavalinApiHandler List> response = ApiImplementation.bulkInsert(apiInstanceMetaData, version, tableApiName, context.body()); - setupSession(context, insertInput, version, apiInstanceMetaData); - QJavalinAccessLogger.logStart("apiBulkInsert", logPair("table", tableName)); - - insertInput.setTableName(tableName); - - PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT); - - ///////////////// - // build input // - ///////////////// - try - { - if(!StringUtils.hasContent(context.body())) - { - throw (new QBadRequestException("Missing required POST body")); - } - - ArrayList recordList = new ArrayList<>(); - insertInput.setRecords(recordList); - - JSONTokener jsonTokener = new JSONTokener(context.body().trim()); - JSONArray jsonArray = new JSONArray(jsonTokener); - - for(int i = 0; i < jsonArray.length(); i++) - { - JSONObject jsonObject = jsonArray.getJSONObject(i); - recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false)); - } - - if(jsonTokener.more()) - { - throw (new QBadRequestException("Body contained more than a single JSON array.")); - } - - if(recordList.isEmpty()) - { - throw (new QBadRequestException("No records were found in the POST body")); - } - } - catch(QBadRequestException qbre) - { - throw (qbre); - } - catch(Exception e) - { - throw (new QBadRequestException("Body could not be parsed as a JSON array: " + e.getMessage(), e)); - } - - ////////////// - // execute! // - ////////////// - InsertAction insertAction = new InsertAction(); - InsertOutput insertOutput = insertAction.execute(insertInput); - - /////////////////////////////////////// - // process records to build response // - /////////////////////////////////////// - List> response = new ArrayList<>(); - for(QRecord record : insertOutput.getRecords()) - { - LinkedHashMap outputRecord = new LinkedHashMap<>(); - response.add(outputRecord); - - List errors = record.getErrors(); - List warnings = record.getWarnings(); - if(CollectionUtils.nullSafeHasContents(errors)) - { - outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode()); - outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage()); - outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors)); - } - else if(CollectionUtils.nullSafeHasContents(warnings)) - { - outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode()); - outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage()); - outputRecord.put("error", "Warning inserting " + table.getLabel() + ", some data may have been inserted: " + StringUtils.joinWithCommasAndAnd(warnings)); - } - else - { - outputRecord.put("statusCode", HttpStatus.Code.CREATED.getCode()); - outputRecord.put("statusText", HttpStatus.Code.CREATED.getMessage()); - outputRecord.put(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField())); - } - } - - QJavalinAccessLogger.logEndSuccess(logPair("recordCount", insertInput.getRecords().size())); - context.status(HttpStatus.Code.MULTI_STATUS.getCode()); String resultString = JsonUtils.toJson(response); context.result(resultString); diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java index 4ddeb37f..0ba12b72 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java @@ -31,6 +31,8 @@ import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer; +import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; @@ -40,6 +42,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; @@ -50,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey; import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; /******************************************************************************* @@ -136,6 +141,33 @@ public class TestUtils + /******************************************************************************* + ** + *******************************************************************************/ + public static class PersonPreInsertCustomizer extends AbstractPreInsertCustomizer + { + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List apply(List records) + { + for(QRecord record : CollectionUtils.nonNullList(records)) + { + if(!record.getValueString("firstName").matches(".*[a-z].*")) + { + record.addWarning("First name does not contain any letters..."); + } + } + + return (records); + } + + } + + + /******************************************************************************* ** Define the 'person' table used in standard tests. *******************************************************************************/ @@ -161,6 +193,8 @@ public class TestUtils .withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY)) .withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY)); + table.withCustomizer(TableCustomizers.PRE_INSERT_RECORD.getRole(), new QCodeReference(PersonPreInsertCustomizer.class, QCodeUsage.CUSTOMIZER)); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // make some changes to this table in the "main" api (but leave it like the backend in the ALTERNATIVE_API_NAME) // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java index a98d1a80..34832bdb 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java @@ -556,6 +556,25 @@ class QJavalinApiHandlerTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInsert201WithWarning() throws QException + { + HttpResponse response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/") + .body(""" + {"firstName": "--"} + """) + .asString(); + assertEquals(HttpStatus.CREATED_201, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + assertEquals(1, jsonObject.getInt("id")); + assertTrue(jsonObject.has("warning")); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -677,13 +696,14 @@ class QJavalinApiHandlerTest extends BaseTest {"firstName": "Moe", "email": "moe@moes.com"}, {"firstName": "Barney", "email": "barney@moes.com"}, {"firstName": "CM", "email": "boss@snpp.com"}, - {"firstName": "Waylon", "email": "boss@snpp.com"} + {"firstName": "Waylon", "email": "boss@snpp.com"}, + {"firstName": "--", "email": "dashdash@simpsons.com"} ] """) .asString(); assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus()); JSONArray jsonArray = new JSONArray(response.getBody()); - assertEquals(4, jsonArray.length()); + assertEquals(5, jsonArray.length()); assertEquals(HttpStatus.CREATED_201, jsonArray.getJSONObject(0).getInt("statusCode")); assertEquals(1, jsonArray.getJSONObject(0).getInt("id")); @@ -697,6 +717,10 @@ class QJavalinApiHandlerTest extends BaseTest assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode")); assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error")); + assertEquals(HttpStatus.CREATED_201, jsonArray.getJSONObject(4).getInt("statusCode")); + assertEquals(4, jsonArray.getJSONObject(4).getInt("id")); + assertTrue(jsonArray.getJSONObject(4).has("warning")); + QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1); assertEquals("Moe", record.getValueString("firstName")); @@ -707,6 +731,9 @@ class QJavalinApiHandlerTest extends BaseTest assertEquals("CM", record.getValueString("firstName")); record = getRecord(TestUtils.TABLE_NAME_PERSON, 4); + assertEquals("--", record.getValueString("firstName")); + + record = getRecord(TestUtils.TABLE_NAME_PERSON, 5); assertNull(record); }