mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
More error handling from customizers
This commit is contained in:
@ -473,8 +473,16 @@ public class ApiImplementation
|
||||
List<QWarningMessage> warnings = record.getWarnings();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||
if(areAnyErrorsBadRequest(errors))
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.INTERNAL_SERVER_ERROR.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
outputRecord.put("error", "Error inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(errors));
|
||||
}
|
||||
else if(CollectionUtils.nullSafeHasContents(warnings))
|
||||
@ -577,6 +585,7 @@ public class ApiImplementation
|
||||
UpdateOutput updateOutput = updateAction.execute(updateInput);
|
||||
|
||||
List<QErrorMessage> errors = updateOutput.getRecords().get(0).getErrors();
|
||||
// todo - do we want, if there were warnings, to return a 200 w/ a body w/ the warnings? maybe...
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
if(areAnyErrorsNotFound(errors))
|
||||
@ -686,26 +695,38 @@ public class ApiImplementation
|
||||
}
|
||||
|
||||
List<QErrorMessage> errors = record.getErrors();
|
||||
|
||||
HttpStatus.Code statusCode;
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
outputRecord.put("error", "Error updating " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(errors));
|
||||
if(areAnyErrorsNotFound(errors))
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.NOT_FOUND.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.NOT_FOUND.getMessage());
|
||||
statusCode = HttpStatus.Code.NOT_FOUND;
|
||||
}
|
||||
else if(areAnyErrorsBadRequest(errors))
|
||||
{
|
||||
statusCode = HttpStatus.Code.BAD_REQUEST;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.BAD_REQUEST.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.BAD_REQUEST.getMessage());
|
||||
statusCode = HttpStatus.Code.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outputRecord.put("statusCode", HttpStatus.Code.NO_CONTENT.getCode());
|
||||
outputRecord.put("statusText", HttpStatus.Code.NO_CONTENT.getMessage());
|
||||
statusCode = HttpStatus.Code.NO_CONTENT;
|
||||
|
||||
List<QWarningMessage> warnings = record.getWarnings();
|
||||
if(CollectionUtils.nullSafeHasContents(warnings))
|
||||
{
|
||||
outputRecord.put("warning", "Warning updating " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(warnings));
|
||||
}
|
||||
}
|
||||
|
||||
outputRecord.put("statusCode", statusCode.getCode());
|
||||
outputRecord.put("statusText", statusCode.getMessage());
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -740,6 +761,11 @@ public class ApiImplementation
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with " + table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
else if(areAnyErrorsBadRequest(errors))
|
||||
{
|
||||
throw (new QBadRequestException("Error deleting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(errors)));
|
||||
}
|
||||
// todo - do we want, if there were warnings, to return a 200 w/ a body w/ the warnings? maybe...
|
||||
else
|
||||
{
|
||||
throw (new QException("Error deleting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(errors)));
|
||||
|
@ -31,7 +31,9 @@ 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.AbstractPreDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
|
||||
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;
|
||||
@ -54,6 +56,7 @@ 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.model.statusmessages.BadInputStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
@ -156,7 +159,7 @@ public class TestUtils
|
||||
{
|
||||
for(QRecord record : CollectionUtils.nonNullList(records))
|
||||
{
|
||||
if(!record.getValueString("firstName").matches(".*[a-z].*"))
|
||||
if(record.getValueString("firstName") != null && !record.getValueString("firstName").matches(".*[a-z].*"))
|
||||
{
|
||||
record.addWarning(new QWarningMessage("First name does not contain any letters..."));
|
||||
}
|
||||
@ -180,6 +183,7 @@ public class TestUtils
|
||||
.withBackendName(MEMORY_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("email"))
|
||||
.withCustomizer(TableCustomizers.PRE_DELETE_RECORD, new QCodeReference(PersonPreDeleteCustomizer.class))
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
@ -241,6 +245,7 @@ public class TestUtils
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_ORDER)
|
||||
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD.getRole(), new QCodeReference(OrderPreInsertCustomizer.class))
|
||||
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD.getRole(), new QCodeReference(OrderPreUpdateCustomizer.class))
|
||||
.withBackendName(MEMORY_BACKEND_NAME)
|
||||
.withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)))
|
||||
.withPrimaryKeyField("id")
|
||||
@ -427,11 +432,63 @@ public class TestUtils
|
||||
|
||||
if("throw".equals(record.getValueString("orderNo")))
|
||||
{
|
||||
throw (new QException("Throwing error, as requested..."));
|
||||
record.addError(new SystemErrorStatusMessage("Throwing error, as requested..."));
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class OrderPreUpdateCustomizer extends AbstractPreUpdateCustomizer
|
||||
{
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// use same logic as pre-insert customizer //
|
||||
/////////////////////////////////////////////
|
||||
return new OrderPreInsertCustomizer().apply(records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PersonPreDeleteCustomizer extends AbstractPreDeleteCustomizer
|
||||
{
|
||||
public static final Integer DELETE_ERROR_ID = 9999;
|
||||
public static final Integer DELETE_WARN_ID = 9998;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(DELETE_ERROR_ID.equals(record.getValue("id")))
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("You may not delete this person"));
|
||||
}
|
||||
else if(DELETE_WARN_ID.equals(record.getValue("id")))
|
||||
{
|
||||
record.addWarning(new QWarningMessage("It was bad that you deleted this person"));
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -128,7 +128,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
void testSpec()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/openapi.yaml").asString();
|
||||
System.out.println(response.getBody());
|
||||
assertThat(response.getBody())
|
||||
.contains("""
|
||||
title: "Test API"
|
||||
@ -217,8 +216,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/order/1").asString();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
System.out.println(jsonObject.toString(3));
|
||||
JSONArray orderLines = jsonObject.getJSONArray("orderLines");
|
||||
JSONArray orderLines = jsonObject.getJSONArray("orderLines");
|
||||
assertEquals(3, orderLines.length());
|
||||
JSONObject orderLine0 = orderLines.getJSONObject(0);
|
||||
JSONArray lineExtrinsics = orderLine0.getJSONArray("extrinsics");
|
||||
@ -522,7 +520,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/order/query?id=1").asString();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
System.out.println(jsonObject.toString(3));
|
||||
JSONObject order0 = jsonObject.getJSONArray("records").getJSONObject(0);
|
||||
JSONArray orderLines = order0.getJSONArray("orderLines");
|
||||
assertEquals(3, orderLines.length());
|
||||
@ -595,7 +592,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
}
|
||||
""")
|
||||
.asString();
|
||||
System.out.println(response.getBody());
|
||||
assertEquals(HttpStatus.CREATED_201, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.getInt("id"));
|
||||
@ -699,7 +695,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
}
|
||||
""")
|
||||
.asString();
|
||||
System.out.println(response.getBody());
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Quantity may not be less than 0", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/order/")
|
||||
@ -707,8 +702,30 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
{"orderNo": "throw", "storeId": 47}
|
||||
""")
|
||||
.asString();
|
||||
System.out.println(response.getBody());
|
||||
assertErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, "Throwing error, as requested", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/order/bulk")
|
||||
.body("""
|
||||
[
|
||||
{"orderNo": "ORD123", "storeId": 47, "orderLines":
|
||||
[
|
||||
{"lineNumber": 1, "sku": "BASIC1", "quantity": 0},
|
||||
]
|
||||
},
|
||||
{"orderNo": "throw", "storeId": 47}
|
||||
]
|
||||
""")
|
||||
.asString();
|
||||
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||
assertEquals(2, jsonArray.length());
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||
assertThat(jsonArray.getJSONObject(0).getString("error")).contains("Quantity may not be less than 0");
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, jsonArray.getJSONObject(1).getInt("statusCode"));
|
||||
assertThat(jsonArray.getJSONObject(1).getString("error")).contains("Throwing error, as requested");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -921,6 +938,57 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUpdateErrorsFromCustomizer() throws QException
|
||||
{
|
||||
insert1Order3Lines4LineExtrinsicsAnd1OrderExtrinsic();
|
||||
|
||||
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/order/1")
|
||||
.body("""
|
||||
{"orderNo": "ORD123", "storeId": 47, "orderLines":
|
||||
[
|
||||
{"lineNumber": 1, "sku": "BASIC1", "quantity": 0},
|
||||
]
|
||||
}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Quantity may not be less than 0", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/order/1")
|
||||
.body("""
|
||||
{"orderNo": "throw", "storeId": 47}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, "Throwing error, as requested", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/order/bulk")
|
||||
.body("""
|
||||
[
|
||||
{"id": 1, "orderNo": "ORD123", "storeId": 47, "orderLines":
|
||||
[
|
||||
{"lineNumber": 1, "sku": "BASIC1", "quantity": 0},
|
||||
]
|
||||
},
|
||||
{"id": 1, "orderNo": "throw", "storeId": 47}
|
||||
]
|
||||
""")
|
||||
.asString();
|
||||
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||
assertEquals(2, jsonArray.length());
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||
assertThat(jsonArray.getJSONObject(0).getString("error")).contains("Quantity may not be less than 0");
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, jsonArray.getJSONObject(1).getInt("statusCode"));
|
||||
assertThat(jsonArray.getJSONObject(1).getString("error")).contains("Throwing error, as requested");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -941,7 +1009,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
.asString();
|
||||
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||
System.out.println(jsonArray.toString(3));
|
||||
assertEquals(4, jsonArray.length());
|
||||
|
||||
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||
@ -1155,6 +1222,57 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDeleteErrorsFromCustomizer() throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", TestUtils.PersonPreDeleteCustomizer.DELETE_ERROR_ID),
|
||||
new QRecord().withValue("id", TestUtils.PersonPreDeleteCustomizer.DELETE_WARN_ID)));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/" + TestUtils.PersonPreDeleteCustomizer.DELETE_ERROR_ID).asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "You may not delete this person", response);
|
||||
|
||||
response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/" + TestUtils.PersonPreDeleteCustomizer.DELETE_WARN_ID).asString();
|
||||
assertErrorResponse(HttpStatus.NO_CONTENT_204, "It was bad that you deleted this person", response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDeleteBulkErrorsFromCustomizer() throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", TestUtils.PersonPreDeleteCustomizer.DELETE_ERROR_ID),
|
||||
new QRecord().withValue("id", TestUtils.PersonPreDeleteCustomizer.DELETE_WARN_ID)));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/bulk")
|
||||
.body("[" + TestUtils.PersonPreDeleteCustomizer.DELETE_ERROR_ID + "," + TestUtils.PersonPreDeleteCustomizer.DELETE_WARN_ID + "]")
|
||||
.asString();
|
||||
assertEquals(HttpStatus.MULTI_STATUS_207, response.getStatus());
|
||||
JSONArray jsonArray = new JSONArray(response.getBody());
|
||||
assertEquals(2, jsonArray.length());
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(0).getInt("statusCode"));
|
||||
assertThat(jsonArray.getJSONObject(0).getString("error")).contains("Error deleting Person: You may not delete this person");
|
||||
|
||||
assertEquals(HttpStatus.NO_CONTENT_204, jsonArray.getJSONObject(1).getInt("statusCode"));
|
||||
assertFalse(jsonArray.getJSONObject(1).has("error"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user