From c6b473ec3984c66089598f70bce90f1380e0f583 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 09:46:48 -0800 Subject: [PATCH 01/20] Batch 12 of #2285, converting `ErrorCodeV1` entries --- .../io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java | 3 --- .../stargate/sgv2/jsonapi/exception/JsonApiException.java | 1 - .../embedding/configuration/EmbeddingProvidersConfig.java | 6 ++++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 50f6e61964..50e55b8bf0 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,9 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - // Really Bad, generic name: but only used for EmbeddingProvidersConfig validation issues - INVALID_PARAMETER_VALIDATION_TYPE("Invalid Parameter Validation Type"), - SHRED_BAD_BINARY_VECTOR_VALUE("Bad binary vector value to shred"), SHRED_BAD_DOCID_TYPE("Bad type for '_id' property"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/JsonApiException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/JsonApiException.java index f029895801..6442e99dc6 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/JsonApiException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/JsonApiException.java @@ -62,7 +62,6 @@ public class JsonApiException extends RuntimeException implements Supplier() { { - add(INVALID_PARAMETER_VALIDATION_TYPE); add(INVALID_REQUEST); } }, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/embedding/configuration/EmbeddingProvidersConfig.java b/src/main/java/io/stargate/sgv2/jsonapi/service/embedding/configuration/EmbeddingProvidersConfig.java index cde7ad3819..1241911809 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/embedding/configuration/EmbeddingProvidersConfig.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/embedding/configuration/EmbeddingProvidersConfig.java @@ -4,7 +4,6 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.service.provider.ApiModelSupport; import jakarta.annotation.Nullable; import jakarta.inject.Inject; @@ -207,7 +206,10 @@ public static ValidationType fromString(String type) { } else if (type.equals("options")) { return OPTIONS; } - throw ErrorCodeV1.INVALID_PARAMETER_VALIDATION_TYPE.toApiException(type); + throw new IllegalArgumentException( + "Invalid `ValidationType` value ('" + + type + + "') for `EmbeddingProvidersConfig`: expected either 'numericRange' or 'options'"); } @Override From 348226de0943f6ec7b35a4ba98b899b46f0ee618 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 13:50:43 -0800 Subject: [PATCH 02/20] Convert 2nd code --- .../jsonapi/exception/DocumentException.java | 3 +++ .../collections/DocumentShredder.java | 17 +++++++++++------ .../collections/WritableShreddedDocument.java | 4 +++- .../stargate/sgv2/jsonapi/util/JsonUtil.java | 9 ++++++++- src/main/resources/errors.yaml | 6 ++++++ .../api/v1/VectorSearchIntegrationTest.java | 18 +++++++++--------- 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 41fc279e7e..7a6a40a746 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -22,6 +22,9 @@ public enum Code implements ErrorCode { INVALID_COLUMN_VALUES, MISSING_PRIMARY_KEY_COLUMNS, + + SHRED_BAD_BINARY_VECTOR_VALUE, + UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, UNSUPPORTED_VECTORIZE_CONFIGURATIONS, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index 40a4a53196..7ddfedbcfe 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -14,6 +14,7 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.SchemaException; import io.stargate.sgv2.jsonapi.exception.ServerException; @@ -537,16 +538,20 @@ private void traverseVector(JsonPath path, JsonNode value) { } JsonNode binaryValue = entry.getValue(); if (!binaryValue.isTextual()) { - throw ErrorCodeV1.SHRED_BAD_BINARY_VECTOR_VALUE.toApiException( - "Unsupported JSON value type in EJSON $binary wrapper (%s): only STRING allowed", - binaryValue.getNodeType()); + throw DocumentException.Code.SHRED_BAD_BINARY_VECTOR_VALUE.get( + Map.of( + "errorMessage", + "Unsupported JSON value type in EJSON $binary wrapper (%s): only String allowed" + .formatted(JsonUtil.nodeTypeAsString(binaryValue.getNodeType())))); } try { shredder.shredVector(path, binaryValue.binaryValue()); } catch (IOException e) { - throw ErrorCodeV1.SHRED_BAD_BINARY_VECTOR_VALUE.toApiException( - "Invalid content in EJSON $binary wrapper: not valid Base64-encoded String, problem: %s" - .formatted(e.getMessage())); + throw DocumentException.Code.SHRED_BAD_BINARY_VECTOR_VALUE.get( + Map.of( + "errorMessage", + "Invalid content in EJSON $binary wrapper; not valid Base64-encoded String: %s" + .formatted(e.getMessage()))); } } else { throw ErrorCodeV1.SHRED_BAD_DOCUMENT_VECTOR_TYPE.toApiException( diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java index 75633877e0..e9655846f7 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.util.CqlVectorUtil; import io.stargate.sgv2.jsonapi.util.JsonUtil; @@ -315,7 +316,8 @@ public void shredVector(JsonPath path, byte[] binaryVector) { try { queryVectorValues = CqlVectorUtil.bytesToFloats(binaryVector); } catch (IllegalArgumentException e) { - throw ErrorCodeV1.SHRED_BAD_BINARY_VECTOR_VALUE.toApiException(e.getMessage()); + throw DocumentException.Code.SHRED_BAD_BINARY_VECTOR_VALUE.get( + Map.of("errorMessage", e.getMessage())); } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java index dd9ef4f7c5..cad9abd358 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java @@ -33,7 +33,14 @@ public static String nodeTypeAsString(JsonNode node) { if (node == null) { return ""; } - String typeDesc = node.getNodeType().toString(); + return nodeTypeAsString(node.getNodeType()); + } + + public static String nodeTypeAsString(JsonNodeType nodeType) { + if (nodeType == null) { + return ""; + } + String typeDesc = nodeType.toString(); // We know all are longer than 1 character, upper case, so: return typeDesc.substring(0, 1) + typeDesc.substring(1).toLowerCase(); } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 1541efc7b7..4c5301da7b 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -307,6 +307,12 @@ request-errors: Resend the command including the missing primary key columns. + - scope: DOCUMENT + code: SHRED_BAD_BINARY_VECTOR_VALUE + title: Bad binary vector value to shred + body: |- + Bad binary vector value to shred, problem: ${errorMessage}. + # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT code: UNKNOWN_TABLE_COLUMNS diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java index 207651e39e..3bc49b0db4 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java @@ -464,12 +464,12 @@ public void failToInsertBinaryVectorWithInvalidBinaryString() { .then() .statusCode(200) .body("$", responseIsWritePartialSuccess()) - .body("errors[0].exceptionClass", is("JsonApiException")) .body("errors[0].errorCode", is("SHRED_BAD_BINARY_VECTOR_VALUE")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - is( - "Bad binary vector value to shred: Invalid content in EJSON $binary wrapper: not valid Base64-encoded String, problem: Cannot access contents of TextNode as binary due to broken Base64 encoding: Illegal character '@' (code 0x40) in base64 content")); + containsString( + "Bad binary vector value to shred, problem: Invalid content in EJSON $binary wrapper; not valid Base64-encoded String: Cannot access contents of TextNode as binary due to broken Base64 encoding: Illegal character '@'")); } @Test @@ -487,12 +487,12 @@ public void failToInsertBinaryVectorWithInvalidBinaryValue() { .then() .statusCode(200) .body("$", responseIsWritePartialSuccess()) - .body("errors[0].exceptionClass", is("JsonApiException")) .body("errors[0].errorCode", is("SHRED_BAD_BINARY_VECTOR_VALUE")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - is( - "Bad binary vector value to shred: Unsupported JSON value type in EJSON $binary wrapper (NUMBER): only STRING allowed")); + containsString( + "Bad binary vector value to shred, problem: Unsupported JSON value type in EJSON $binary wrapper (Number): only String allowed")); } @Test @@ -533,12 +533,12 @@ public void failToInsertBinaryVectorWithInvalidDecodedValue() { .then() .statusCode(200) .body("$", responseIsWritePartialSuccess()) - .body("errors[0].exceptionClass", is("JsonApiException")) .body("errors[0].errorCode", is("SHRED_BAD_BINARY_VECTOR_VALUE")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - is( - "Bad binary vector value to shred: binary value to decode is not a multiple of 4 bytes long (3 bytes)")); + containsString( + "Bad binary vector value to shred, problem: binary value to decode is not a multiple of 4 bytes long (3 bytes)")); } @Test From b484ee47b5e7b39cbffff24ec3fe11dd026bdc1c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 13:59:30 -0800 Subject: [PATCH 03/20] Remove unused codes --- .../java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 50e55b8bf0..8b2d417629 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_BINARY_VECTOR_VALUE("Bad binary vector value to shred"), - SHRED_BAD_DOCID_TYPE("Bad type for '_id' property"), SHRED_BAD_DOCID_EMPTY_STRING("Bad value for '_id' property: empty String not allowed"), @@ -27,8 +25,6 @@ public enum ErrorCodeV1 { SHRED_BAD_DOCUMENT_VECTOR_TYPE("Bad $vector document type to shred "), - SHRED_BAD_DOCUMENT_VECTORIZE_TYPE("Bad $vectorize document type to shred "), - SHRED_BAD_DOCUMENT_LEXICAL_TYPE("Bad type for $lexical content to shred"), SHRED_BAD_EJSON_VALUE("Bad JSON Extension value"), From 68bb2d0619afc15b989cf09d833bd90eec3eb427 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 14:12:56 -0800 Subject: [PATCH 04/20] ... --- .../sgv2/jsonapi/exception/DocumentException.java | 1 + src/main/resources/errors.yaml | 8 +++++++- .../sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 7a6a40a746..2d5db8a5fd 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -24,6 +24,7 @@ public enum Code implements ErrorCode { MISSING_PRIMARY_KEY_COLUMNS, SHRED_BAD_BINARY_VECTOR_VALUE, + SHRED_BAD_DOCID_TYPE, UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 4c5301da7b..5f8a898399 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -311,7 +311,13 @@ request-errors: code: SHRED_BAD_BINARY_VECTOR_VALUE title: Bad binary vector value to shred body: |- - Bad binary vector value to shred, problem: ${errorMessage}. + Bad binary vector value to shred: ${errorMessage}. + + - scope: DOCUMENT + code: SHRED_BAD_DOCID_TYPE + title: Bad binary vector value to shred + body: |- + Bad type for '_id' property: ${errorMessage}. # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java index 3bc49b0db4..9a723f708d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java @@ -469,7 +469,7 @@ public void failToInsertBinaryVectorWithInvalidBinaryString() { .body( "errors[0].message", containsString( - "Bad binary vector value to shred, problem: Invalid content in EJSON $binary wrapper; not valid Base64-encoded String: Cannot access contents of TextNode as binary due to broken Base64 encoding: Illegal character '@'")); + "Bad binary vector value to shred: Invalid content in EJSON $binary wrapper; not valid Base64-encoded String: Cannot access contents of TextNode as binary due to broken Base64 encoding: Illegal character '@'")); } @Test @@ -492,7 +492,7 @@ public void failToInsertBinaryVectorWithInvalidBinaryValue() { .body( "errors[0].message", containsString( - "Bad binary vector value to shred, problem: Unsupported JSON value type in EJSON $binary wrapper (Number): only String allowed")); + "Bad binary vector value to shred: Unsupported JSON value type in EJSON $binary wrapper (Number): only String allowed")); } @Test @@ -538,7 +538,7 @@ public void failToInsertBinaryVectorWithInvalidDecodedValue() { .body( "errors[0].message", containsString( - "Bad binary vector value to shred, problem: binary value to decode is not a multiple of 4 bytes long (3 bytes)")); + "Bad binary vector value to shred: binary value to decode is not a multiple of 4 bytes long (3 bytes)")); } @Test From 4f568de24a9c192cc33252e58a37e1deb2081bcf Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 14:30:48 -0800 Subject: [PATCH 05/20] Convert one more code --- .../sgv2/jsonapi/exception/ErrorCodeV1.java | 2 - .../shredding/collections/DocumentId.java | 55 ++++++++++++------- .../builders/FilterClauseBuilderTest.java | 5 +- .../shredding/DocumentShredderTest.java | 9 +-- ...DocumentShredderWithExtendedTypesTest.java | 17 +++--- .../shredding/collections/DocumentIdTest.java | 27 +++++---- 6 files changed, 66 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 8b2d417629..15c7909f17 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_DOCID_TYPE("Bad type for '_id' property"), - SHRED_BAD_DOCID_EMPTY_STRING("Bad value for '_id' property: empty String not allowed"), SHRED_BAD_DOCUMENT_TYPE("Bad document type to shred"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java index 769db2aa89..89c63fd346 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java @@ -6,12 +6,14 @@ import io.quarkus.runtime.annotations.RegisterForReflection; import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.JsonType; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; import io.stargate.sgv2.jsonapi.util.JsonUtil; import java.math.BigDecimal; import java.util.Date; +import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -78,20 +80,26 @@ static DocumentId fromJson(JsonNode node) { return fromExtensionType(extType, valueNode); } } - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "unrecognized JSON extension type '%s'", node.fieldNames().next()); - } - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or NULL instead got %s: %s", - node.getNodeType(), node.toString()); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "unrecognized JSON extension type '%s'".formatted(node.fieldNames().next()))); + } + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got %s: %s" + .formatted(JsonUtil.nodeTypeAsString(node), node.toString()))); } static DocumentId fromDatabase(int typeId, String documentIdAsText) { JsonType type = DocumentConstants.KeyTypeId.getJsonType(typeId); if (type == null) { - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "Document Id must be a JSON String(1), Number(2), Boolean(3), NULL(4) or Date(5) instead got %d", - typeId); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got %d" + .formatted(typeId))); } switch (type) { case BOOLEAN -> { @@ -101,9 +109,11 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { case "false": return fromBoolean(false); } - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "Document Id type Boolean stored as invalid String '%s' (must be 'true' or 'false')", - documentIdAsText); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "Document Id type Boolean stored as invalid String '%s' (must be 'true' or 'false')" + .formatted(documentIdAsText))); } case NULL -> { return fromNull(); @@ -112,9 +122,11 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { try { return fromNumber(new BigDecimal(documentIdAsText)); } catch (NumberFormatException e) { - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "Document Id type Number stored as invalid String '%s' (not a valid Number)", - documentIdAsText); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "Document Id type Number stored as invalid String '%s' (not a valid Number)" + .formatted(documentIdAsText))); } } case STRING -> { @@ -125,13 +137,16 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { long ts = Long.parseLong(documentIdAsText); return fromTimestamp(ts); } catch (NumberFormatException e) { - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException( - "Document Id type Date stored as invalid String '%s' (needs to be Number)", - documentIdAsText); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of( + "errorMessage", + "Document Id type Date stored as invalid String '%s' (needs to be Number)" + .formatted(documentIdAsText))); } } } - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException(); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + Map.of("errorMessage", "unknown `JsonType`: '%s'".formatted(type))); } static DocumentId fromBoolean(boolean key) { @@ -172,7 +187,7 @@ static DocumentId fromExtensionType(JsonExtensionType extType, JsonNode valueNod Object rawId = JsonUtil.extractExtendedValueUnwrapped(extType, valueNode); return new ExtensionTypeId(extType, String.valueOf(rawId)); } catch (JsonApiException e) { - throw ErrorCodeV1.SHRED_BAD_DOCID_TYPE.toApiException(e.getMessage()); + throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get(Map.of("errorMessage", e.getMessage())); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java index 0803656595..ab3ed9efee 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java @@ -9,6 +9,7 @@ import io.stargate.sgv2.jsonapi.TestConstants; import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.*; import io.stargate.sgv2.jsonapi.config.OperationsConfig; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.FilterException; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.shredding.collections.DocumentId; @@ -1421,7 +1422,7 @@ public void mustFailOnBadUUIDAsId() throws Exception { Throwable throwable = catchThrowable(() -> readCollectionFilterClause(json)); assertThat(throwable) - .isInstanceOf(JsonApiException.class) + .isInstanceOf(DocumentException.class) .satisfies( t -> { assertThat(t.getMessage()) @@ -1439,7 +1440,7 @@ public void mustFailOnBadObjectIdAsId() throws Exception { Throwable throwable = catchThrowable(() -> readCollectionFilterClause(json)); assertThat(throwable) - .isInstanceOf(JsonApiException.class) + .isInstanceOf(DocumentException.class) .satisfies( t -> { assertThat(t.getMessage()) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index 59f4b04aa4..47637bb0bb 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -14,6 +14,7 @@ import io.stargate.sgv2.jsonapi.TestConstants; import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.request.RequestContext; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; @@ -483,9 +484,9 @@ public void docBadDocIdTypeArray() { commandContext(), objectMapper.readTree("{ \"_id\" : [ ] }"), null)); assertThat(t) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessage( - "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or NULL instead got ARRAY: []"); + "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Array: []."); } @Test @@ -499,9 +500,9 @@ public void docBadDocIdTypeObjectNotEJSON() { null)); assertThat(t) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessage( - "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or NULL instead got OBJECT: {\"foo\":\"bar\"}"); + "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Object: {\"foo\":\"bar\"}."); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java index d4f962223d..67dda723d5 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java @@ -13,6 +13,7 @@ import io.stargate.sgv2.jsonapi.TestConstants; import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.request.RequestContext; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionIdType; @@ -375,7 +376,8 @@ public void docInvalidObjectAsDocId() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageContaining( "'$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"not-an-oid\")"); } @@ -396,7 +398,8 @@ public void docInvalidUUIDAsDocId() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageContaining( "'$uuid' value has to be 36-character UUID String, instead got (\"not-a-uuid\")"); @@ -411,7 +414,8 @@ public void docInvalidUUIDAsDocId() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageContaining( "'$uuid' value has to be 36-character UUID String, instead got ({})"); } @@ -429,10 +433,9 @@ public void docUnknownEJSonAsId() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) - .hasMessage( - ErrorCodeV1.SHRED_BAD_DOCID_TYPE.getMessage() - + ": unrecognized JSON extension type '$unknown'"); + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) + .hasMessageContaining(": unrecognized JSON extension type '$unknown'"); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java index f27c848799..381b8ab0e0 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java @@ -7,8 +7,7 @@ import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; -import io.stargate.sgv2.jsonapi.exception.JsonApiException; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; import java.math.BigDecimal; import java.util.Date; @@ -78,10 +77,10 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id must be a JSON String(1), Number(2), Boolean(3), NULL(4) or Date(5) instead got 99"); + "Bad type for '_id' property: Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got 99."); e = catchException( @@ -90,10 +89,10 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')"); + "Bad type for '_id' property: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')."); e = catchException( @@ -102,10 +101,10 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Number stored as invalid String 'abc' (not a valid Number)"); + "Bad type for '_id' property: Document Id type Number stored as invalid String 'abc' (not a valid Number)."); e = catchException( @@ -114,9 +113,9 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_TYPE) + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Date stored as invalid String 'abc' (needs to be Number)"); + "Bad type for '_id' property: Document Id type Date stored as invalid String 'abc' (needs to be Number)."); } } From 276a2b015ffaac3b1b9d0053bc5349146a58d2ed Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 15:53:05 -0800 Subject: [PATCH 06/20] Fix 5 IT regressions --- .../sgv2/jsonapi/api/v1/FindOneIntegrationTest.java | 4 ++-- .../jsonapi/api/v1/InsertInCollectionIntegrationTest.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java index 5d8afe90ec..15a041c93e 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java @@ -811,7 +811,7 @@ public void failForInvalidUUIDAsId() { .body("$", responseIsError()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) - .body("errors[0].exceptionClass", is("JsonApiException")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( @@ -825,7 +825,7 @@ public void failForInvalidObjectIdAsId() { .body("$", responseIsError()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) - .body("errors[0].exceptionClass", is("JsonApiException")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index 37d67e4407..d131fe6a74 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -606,7 +606,7 @@ public void failInsertDocWithInvalidUUIDAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) + .body("errors[0].exceptionClass", is("DocumentException")) .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) .body( "errors[0].message", @@ -627,8 +627,8 @@ public void failInsertDocWithInvalidObjectIdAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( @@ -648,8 +648,8 @@ public void failInsertDocWithUnknownExtensionAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", startsWith("Bad type for '_id' property: unrecognized JSON extension type")); From 51b5c5b14a9478efd6a0db6c28a8d5e7ab96b549 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 15:54:47 -0800 Subject: [PATCH 07/20] Fix error title --- src/main/resources/errors.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 5f8a898399..af815e4c90 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -315,7 +315,7 @@ request-errors: - scope: DOCUMENT code: SHRED_BAD_DOCID_TYPE - title: Bad binary vector value to shred + title: Bad type for '_id' property body: |- Bad type for '_id' property: ${errorMessage}. From f57f9b15a753f5627da9e12abfba1033a1afdd92 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 16:05:07 -0800 Subject: [PATCH 08/20] One more conversion --- .../jsonapi/exception/DocumentException.java | 1 + .../sgv2/jsonapi/exception/ErrorCodeV1.java | 2 -- .../shredding/collections/DocumentId.java | 4 ++-- src/main/resources/errors.yaml | 10 ++++++++-- .../shredding/DocumentShredderTest.java | 18 +++++++++--------- .../shredding/collections/DocumentIdTest.java | 8 ++++---- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 2d5db8a5fd..7981b840dd 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -25,6 +25,7 @@ public enum Code implements ErrorCode { SHRED_BAD_BINARY_VECTOR_VALUE, SHRED_BAD_DOCID_TYPE, + SHRED_BAD_DOCID_VALUE, UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 15c7909f17..c25360f2ba 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_DOCID_EMPTY_STRING("Bad value for '_id' property: empty String not allowed"), - SHRED_BAD_DOCUMENT_TYPE("Bad document type to shred"), SHRED_BAD_DOCUMENT_VECTOR_TYPE("Bad $vector document type to shred "), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java index 89c63fd346..b406a64265 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java @@ -7,7 +7,6 @@ import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.JsonType; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; import io.stargate.sgv2.jsonapi.util.JsonUtil; @@ -165,7 +164,8 @@ static DocumentId fromNumber(BigDecimal key) { static DocumentId fromString(String key) { key = Objects.requireNonNull(key); if (key.isEmpty()) { - throw ErrorCodeV1.SHRED_BAD_DOCID_EMPTY_STRING.toApiException(); + throw DocumentException.Code.SHRED_BAD_DOCID_VALUE.get( + Map.of("errorMessage", "empty String not allowed")); } return new StringId(key); } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index af815e4c90..6c5ca5e669 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -315,9 +315,15 @@ request-errors: - scope: DOCUMENT code: SHRED_BAD_DOCID_TYPE - title: Bad type for '_id' property + title: Bad type for '_id' field body: |- - Bad type for '_id' property: ${errorMessage}. + Bad type for '_id' field: ${errorMessage}. + + - scope: DOCUMENT + code: SHRED_BAD_DOCID_VALUE + title: Bad value for '_id' field + body: |- + Bad value for '_id' field: ${errorMessage}. # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index 47637bb0bb..9c9b0e4f7d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -470,9 +470,9 @@ public void docBadJSONType() { assertThat(t) .isNotNull() + .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCUMENT_TYPE) .hasMessage( - "Bad document type to shred: document to shred must be a JSON Object, instead got ARRAY") - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCUMENT_TYPE); + "Bad document type to shred: document to shred must be a JSON Object, instead got ARRAY"); } @Test @@ -485,8 +485,8 @@ public void docBadDocIdTypeArray() { assertThat(t) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessage( - "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Array: []."); + .hasMessageContaining( + "Bad type for '_id' field: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Array: []."); } @Test @@ -501,8 +501,8 @@ public void docBadDocIdTypeObjectNotEJSON() { assertThat(t) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessage( - "Bad type for '_id' property: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Object: {\"foo\":\"bar\"}."); + .hasMessageContaining( + "Bad type for '_id' field: Document Id must be a JSON String, Number, Boolean, EJSON-Encoded Date Object or null instead got Object: {\"foo\":\"bar\"}."); } @Test @@ -515,8 +515,8 @@ public void docBadDocIdEmptyString() { assertThat(t) .isNotNull() - .hasMessage("Bad value for '_id' property: empty String not allowed") - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCID_EMPTY_STRING); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_VALUE.name()) + .hasMessageContaining("Bad value for '_id' field: empty String not allowed"); } @Test @@ -530,7 +530,7 @@ public void docBadFieldNameRootLeadingDollar() { assertThat(t) .isNotNull() .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessage("Document field name invalid: field name '$id' starts with '$'"); + .hasMessageContaining("Document field name invalid: field name '$id' starts with '$'"); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java index 381b8ab0e0..a22970e727 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java @@ -80,7 +80,7 @@ public void testFromDatabaseInvalid() throws Exception { .isInstanceOf(DocumentException.class) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got 99."); + "Bad type for '_id' field: Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got 99."); e = catchException( @@ -92,7 +92,7 @@ public void testFromDatabaseInvalid() throws Exception { .isInstanceOf(DocumentException.class) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')."); + "Bad type for '_id' field: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')."); e = catchException( @@ -104,7 +104,7 @@ public void testFromDatabaseInvalid() throws Exception { .isInstanceOf(DocumentException.class) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Number stored as invalid String 'abc' (not a valid Number)."); + "Bad type for '_id' field: Document Id type Number stored as invalid String 'abc' (not a valid Number)."); e = catchException( @@ -116,6 +116,6 @@ public void testFromDatabaseInvalid() throws Exception { .isInstanceOf(DocumentException.class) .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) .hasMessageStartingWith( - "Bad type for '_id' property: Document Id type Date stored as invalid String 'abc' (needs to be Number)."); + "Bad type for '_id' field: Document Id type Date stored as invalid String 'abc' (needs to be Number)."); } } From 7746144b51dc3d590ee488e2b36855789b5e3a18 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 16:50:50 -0800 Subject: [PATCH 09/20] More conversions, IT fixes --- .../jsonapi/exception/DocumentException.java | 2 ++ .../sgv2/jsonapi/exception/ErrorCodeV1.java | 4 ---- .../collections/DocumentShredder.java | 18 ++++++++++++------ src/main/resources/errors.yaml | 12 ++++++++++++ .../v1/InsertInCollectionIntegrationTest.java | 13 +++++++------ .../api/v1/VectorSearchIntegrationTest.java | 11 +++++++---- .../shredding/DocumentShredderTest.java | 7 ++++--- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 7981b840dd..1e4b140043 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -26,6 +26,8 @@ public enum Code implements ErrorCode { SHRED_BAD_BINARY_VECTOR_VALUE, SHRED_BAD_DOCID_TYPE, SHRED_BAD_DOCID_VALUE, + SHRED_BAD_DOCUMENT_TYPE, + SHRED_BAD_DOCUMENT_VECTOR_TYPE, UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index c25360f2ba..f85cc9f0d4 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,10 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_DOCUMENT_TYPE("Bad document type to shred"), - - SHRED_BAD_DOCUMENT_VECTOR_TYPE("Bad $vector document type to shred "), - SHRED_BAD_DOCUMENT_LEXICAL_TYPE("Bad type for $lexical content to shred"), SHRED_BAD_EJSON_VALUE("Bad JSON Extension value"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index 7ddfedbcfe..e8be8845e8 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -111,8 +111,8 @@ public WritableShreddedDocument shred( // Although we could otherwise allow non-Object documents, requirement // to have the _id (or at least place for it) means we cannot allow that. if (!doc.isObject()) { - throw ErrorCodeV1.SHRED_BAD_DOCUMENT_TYPE.toApiException( - "document to shred must be a JSON Object, instead got %s", doc.getNodeType()); + throw DocumentException.Code.SHRED_BAD_DOCUMENT_TYPE.get( + Map.of("documentType", JsonUtil.nodeTypeAsString(doc))); } final ObjectNode docWithId = normalizeDocumentId(collectionSettings, (ObjectNode) doc); @@ -533,8 +533,11 @@ private void traverseVector(JsonPath path, JsonNode value) { final Map.Entry entry = obj.properties().iterator().next(); JsonExtensionType keyType = JsonExtensionType.fromEncodedName(entry.getKey()); if (keyType != BINARY) { - throw ErrorCodeV1.SHRED_BAD_DOCUMENT_VECTOR_TYPE.toApiException( - "The key for the %s object must be '%s'", path, BINARY.encodedName()); + throw DocumentException.Code.SHRED_BAD_DOCUMENT_VECTOR_TYPE.get( + Map.of( + "errorMessage", + "the key for the %s Object must be '%s', not '%s'" + .formatted(path, BINARY.encodedName(), entry.getKey()))); } JsonNode binaryValue = entry.getValue(); if (!binaryValue.isTextual()) { @@ -554,8 +557,11 @@ private void traverseVector(JsonPath path, JsonNode value) { .formatted(e.getMessage()))); } } else { - throw ErrorCodeV1.SHRED_BAD_DOCUMENT_VECTOR_TYPE.toApiException( - value.getNodeType().toString()); + throw DocumentException.Code.SHRED_BAD_DOCUMENT_VECTOR_TYPE.get( + Map.of( + "errorMessage", + "JSON '%s's not supported: only JSON Arrays and Objects are" + .formatted(JsonUtil.nodeTypeAsString(value.getNodeType())))); } } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 6c5ca5e669..916406ee7c 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -325,6 +325,18 @@ request-errors: body: |- Bad value for '_id' field: ${errorMessage}. + - scope: DOCUMENT + code: SHRED_BAD_DOCUMENT_TYPE + title: Bad document type to shred + body: |- + Bad document type to shred: document must be a JSON Object, instead got a JSON ${documentType}. + + - scope: DOCUMENT + code: SHRED_BAD_DOCUMENT_VECTOR_TYPE + title: Bad $vector value to shred + body: |- + Bad $vector value to shred: ${errorMessage}. + # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT code: UNKNOWN_TABLE_COLUMNS diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index d131fe6a74..8bc8398e41 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -16,6 +16,7 @@ import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; import io.stargate.sgv2.jsonapi.config.OperationsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import java.io.IOException; import java.math.BigDecimal; @@ -69,12 +70,12 @@ public void shredFailureOnNullDoc() { .body("$", responseIsWritePartialSuccess()) .body("status.insertedIds", jsonEquals("[]")) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("SHRED_BAD_DOCUMENT_TYPE")) - .body("errors[0].exceptionClass", is("JsonApiException")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_DOCUMENT_TYPE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith( - "Bad document type to shred: document to shred must be a JSON Object, instead got NULL")); + containsString( + "Bad document type to shred: document must be a JSON Object, instead got a JSON Null")); } @Test @@ -611,7 +612,7 @@ public void failInsertDocWithInvalidUUIDAsDocId() { .body( "errors[0].message", containsString( - "Bad type for '_id' property: Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (42)")); + "Bad type for '_id' field: Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (42)")); } @Test @@ -652,7 +653,7 @@ public void failInsertDocWithUnknownExtensionAsDocId() { .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith("Bad type for '_id' property: unrecognized JSON extension type")); + startsWith("Bad type for '_id' field: unrecognized JSON extension type")); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java index 9a723f708d..78f461fa4a 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java @@ -9,6 +9,7 @@ import io.restassured.response.Response; import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonApiMetricsConfig; import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import java.util.UUID; import org.junit.jupiter.api.*; @@ -510,12 +511,14 @@ public void failToInsertBinaryVectorWithInvalidVectorObject() { .then() .statusCode(200) .body("$", responseIsWritePartialSuccess()) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_BAD_DOCUMENT_VECTOR_TYPE")) + .body( + "errors[0].errorCode", + is(DocumentException.Code.SHRED_BAD_DOCUMENT_VECTOR_TYPE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - is( - "Bad $vector document type to shred : The key for the $vector object must be '$binary'")); + containsString( + "Bad $vector value to shred: the key for the $vector Object must be '$binary', not 'binary'")); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index 9c9b0e4f7d..6e45a6e026 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -470,9 +470,10 @@ public void docBadJSONType() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCUMENT_TYPE) - .hasMessage( - "Bad document type to shred: document to shred must be a JSON Object, instead got ARRAY"); + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_BAD_DOCUMENT_TYPE.name()) + .hasMessageContaining( + "Bad document type to shred: document must be a JSON Object, instead got a JSON Array"); } @Test From d59e4c3e770538ff8bec39ba01295f1083d6b4d9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 16:59:45 -0800 Subject: [PATCH 10/20] One more conversion --- .../sgv2/jsonapi/exception/DocumentException.java | 1 + .../io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java | 2 -- .../service/shredding/collections/DocumentShredder.java | 8 +++++--- src/main/resources/errors.yaml | 6 ++++++ .../jsonapi/service/shredding/DocumentShredderTest.java | 6 ++++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 1e4b140043..677e021af1 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -28,6 +28,7 @@ public enum Code implements ErrorCode { SHRED_BAD_DOCID_VALUE, SHRED_BAD_DOCUMENT_TYPE, SHRED_BAD_DOCUMENT_VECTOR_TYPE, + SHRED_BAD_DOCUMENT_LEXICAL_TYPE, UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index f85cc9f0d4..ee84d200bb 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_DOCUMENT_LEXICAL_TYPE("Bad type for $lexical content to shred"), - SHRED_BAD_EJSON_VALUE("Bad JSON Extension value"), SHRED_BAD_VECTOR_SIZE("$vector value can't be empty"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index e8be8845e8..83ccb8c76a 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -576,9 +576,11 @@ private void traverseLexical(JsonPath path, JsonNode value) { return; } if (!value.isTextual()) { - throw ErrorCodeV1.SHRED_BAD_DOCUMENT_LEXICAL_TYPE.toApiException( - "the value for field '%s' must be a STRING, was: %s", - path.toString(), value.getNodeType()); + throw DocumentException.Code.SHRED_BAD_DOCUMENT_LEXICAL_TYPE.get( + Map.of( + "errorMessage", + "the value for field '%s' must be a JSON String, not a JSON %s" + .formatted(path.toString(), JsonUtil.nodeTypeAsString(value)))); } shredder.shredLexical(path, value.asText()); } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 916406ee7c..f40fe15f0b 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -337,6 +337,12 @@ request-errors: body: |- Bad $vector value to shred: ${errorMessage}. + - scope: DOCUMENT + code: SHRED_BAD_DOCUMENT_LEXICAL_TYPE + title: Bad $lexical value to shred + body: |- + Bad $lexical value to shred: ${errorMessage}. + # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT code: UNKNOWN_TABLE_COLUMNS diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index 6e45a6e026..af9022f348 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -350,8 +350,10 @@ void badLexicalObject() throws Exception { Throwable t = catchThrowable(() -> documentShredder.shred(commandContext(), inputDoc, null)); assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_DOCUMENT_LEXICAL_TYPE) - .hasMessageContaining("the value for field '$lexical' must be a STRING, was: OBJECT"); + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_BAD_DOCUMENT_LEXICAL_TYPE.name()) + .hasMessageContaining( + "the value for field '$lexical' must be a JSON String, not a JSON Object"); } } From 72d474ee8a71139c173b1a2bc8b4f7de0906f269 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 17:22:24 -0800 Subject: [PATCH 11/20] 2 more conversions --- .../sgv2/jsonapi/exception/DocumentException.java | 3 +++ .../stargate/sgv2/jsonapi/exception/ErrorCodeV1.java | 4 ---- .../shredding/collections/DocumentShredder.java | 2 +- .../collections/WritableShreddedDocument.java | 5 ++++- .../java/io/stargate/sgv2/jsonapi/util/JsonUtil.java | 8 ++++++-- src/main/resources/errors.yaml | 12 ++++++++++++ .../command/builders/SortClauseBuilderTest.java | 12 +++++++----- .../service/shredding/DocumentShredderTest.java | 11 ++++++----- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 677e021af1..ec42b3e62c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -30,6 +30,9 @@ public enum Code implements ErrorCode { SHRED_BAD_DOCUMENT_VECTOR_TYPE, SHRED_BAD_DOCUMENT_LEXICAL_TYPE, + SHRED_BAD_VECTOR_SIZE, + SHRED_BAD_VECTOR_VALUE, + UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, UNSUPPORTED_VECTORIZE_CONFIGURATIONS, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index ee84d200bb..756daa9bb6 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -19,10 +19,6 @@ public enum ErrorCodeV1 { SHRED_BAD_EJSON_VALUE("Bad JSON Extension value"), - SHRED_BAD_VECTOR_SIZE("$vector value can't be empty"), - - SHRED_BAD_VECTOR_VALUE("$vector value needs to be array of numbers"), - SHRED_DOC_KEY_NAME_VIOLATION("Document field name invalid"), SHRED_DOC_LIMIT_VIOLATION("Document size limitation violated"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index 83ccb8c76a..c2ce7b5b8c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -524,7 +524,7 @@ private void traverseVector(JsonPath path, JsonNode value) { // e.g. "$vector": [0.25, 0.25] ArrayNode arr = (ArrayNode) value; if (arr.size() == 0) { - throw ErrorCodeV1.SHRED_BAD_VECTOR_SIZE.toApiException(); + throw DocumentException.Code.SHRED_BAD_VECTOR_SIZE.get(); } shredder.shredVector(path, arr); } else if (value.isObject()) { diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java index e9655846f7..e2c0e124c3 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java @@ -301,7 +301,10 @@ public void shredVector(JsonPath path, ArrayNode vector) { for (int i = 0; i < vector.size(); i++) { JsonNode element = vector.get(i); if (!element.isNumber()) { - throw ErrorCodeV1.SHRED_BAD_VECTOR_VALUE.toApiException(); + throw DocumentException.Code.SHRED_BAD_VECTOR_VALUE.get( + Map.of( + "nodeType", JsonUtil.nodeTypeAsString(element), + "nodeValue", element.toString())); } arrayVals[i] = element.floatValue(); } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java index cad9abd358..8b74f7732f 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Utf8; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.exception.ServerException; @@ -314,13 +315,16 @@ public static float[] arrayNodeToVector(ArrayNode arrayNode) { float[] arrayVals = new float[arrayNode.size()]; if (arrayNode.isEmpty()) { - throw ErrorCodeV1.SHRED_BAD_VECTOR_SIZE.toApiException(); + throw DocumentException.Code.SHRED_BAD_VECTOR_SIZE.get(); } for (int i = 0; i < arrayNode.size(); i++) { JsonNode element = arrayNode.get(i); if (!element.isNumber()) { - throw ErrorCodeV1.SHRED_BAD_VECTOR_VALUE.toApiException(); + throw DocumentException.Code.SHRED_BAD_VECTOR_VALUE.get( + Map.of( + "nodeType", JsonUtil.nodeTypeAsString(element), + "nodeValue", element.toString())); } arrayVals[i] = element.floatValue(); } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index f40fe15f0b..e997d62da5 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -343,6 +343,18 @@ request-errors: body: |- Bad $lexical value to shred: ${errorMessage}. + - scope: DOCUMENT + code: SHRED_BAD_VECTOR_SIZE + title: Bad $vector value + body: |- + Bad $vector value: cannot be empty Array. + + - scope: DOCUMENT + code: SHRED_BAD_VECTOR_VALUE + title: Bad $vector value + body: |- + Bad $vector value: needs to be an array containing only Numbers but has a ${nodeType} value (${nodeValue}). + # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT code: UNKNOWN_TABLE_COLUMNS diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/SortClauseBuilderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/SortClauseBuilderTest.java index 322707924a..9197fa1514 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/SortClauseBuilderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/SortClauseBuilderTest.java @@ -10,7 +10,7 @@ import io.stargate.sgv2.jsonapi.TestConstants; import io.stargate.sgv2.jsonapi.api.model.command.clause.sort.SortClause; import io.stargate.sgv2.jsonapi.api.model.command.clause.sort.SortExpression; -import io.stargate.sgv2.jsonapi.exception.JsonApiException; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.SortException; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; @@ -168,8 +168,8 @@ public void vectorSearchEmpty() { Throwable throwable = catchThrowable(() -> deserializeSortClause(json)); - assertThat(throwable).isInstanceOf(JsonApiException.class); - assertThat(throwable.getMessage()).contains("$vector value can't be empty"); + assertThat(throwable).isInstanceOf(DocumentException.class); + assertThat(throwable.getMessage()).contains("Bad $vector value: cannot be empty Array"); } @Test @@ -217,8 +217,10 @@ public void vectorSearchInvalidData() { Throwable throwable = catchThrowable(() -> deserializeSortClause(json)); - assertThat(throwable).isInstanceOf(JsonApiException.class); - assertThat(throwable.getMessage()).contains("$vector value needs to be array of numbers"); + assertThat(throwable).isInstanceOf(DocumentException.class); + assertThat(throwable.getMessage()) + .contains( + "Bad $vector value: needs to be an array containing only Numbers but has a String value (\"abc\")"); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index af9022f348..f73893b990 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -407,7 +407,7 @@ public void badEJSONDate() { assertThat(t) .isNotNull() .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_EJSON_VALUE) - .hasMessage( + .hasMessageContaining( ErrorCodeV1.SHRED_BAD_EJSON_VALUE.getMessage() + ": type '$date' has invalid JSON value of type BOOLEAN"); } @@ -422,8 +422,8 @@ public void badEmptyVectorData() { assertThat(t) .isNotNull() - .hasMessage("$vector value can't be empty") - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_VECTOR_SIZE); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_VECTOR_SIZE.name()) + .hasMessageContaining("Bad $vector value: cannot be empty Array"); } @Test @@ -438,8 +438,9 @@ public void badInvalidVectorData() { assertThat(t) .isNotNull() - .hasMessage("$vector value needs to be array of numbers") - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_VECTOR_VALUE); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_VECTOR_VALUE.name()) + .hasMessageContaining( + "Bad $vector value: needs to be an array containing only Numbers but has a String value (\"abc\")"); } @Test From f9d2645512949479ab91dad10405a388c0b117f4 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 2 Jan 2026 17:31:56 -0800 Subject: [PATCH 12/20] IT fixes --- .../api/v1/VectorSearchIntegrationTest.java | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java index 78f461fa4a..9a778bddbe 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/VectorSearchIntegrationTest.java @@ -304,9 +304,9 @@ public void insertEmptyVectorData() { .body("$", responseIsWritePartialSuccess()) .body("status", jsonEquals("{'insertedIds':[]}")) .body("errors", hasSize(1)) - .body("errors[0].message", startsWith("$vector value can't be empty")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_SIZE")) - .body("errors[0].exceptionClass", is("JsonApiException")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_SIZE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].message", containsString("Bad $vector value: cannot be empty Array")); } @Test @@ -331,9 +331,12 @@ public void insertInvalidVectorData() { .body("$", responseIsWritePartialSuccess()) .body("status", jsonEquals("{'insertedIds':[]}")) .body("errors", hasSize(1)) - .body("errors[0].message", startsWith("$vector value needs to be array of numbers")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_VALUE")) - .body("errors[0].exceptionClass", is("JsonApiException")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_VALUE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body( + "errors[0].message", + containsString( + "Bad $vector value: needs to be an array containing only Numbers but has a String value")); } @Test @@ -937,9 +940,9 @@ public void happyPathWithEmptyVector() { .statusCode(200) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_SIZE")) - .body("errors[0].message", is("$vector value can't be empty")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_SIZE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].message", containsString("Bad $vector value: cannot be empty Array")); } @Test @@ -963,9 +966,12 @@ public void happyPathWithInvalidData() { .statusCode(200) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_VALUE")) - .body("errors[0].message", is("$vector value needs to be array of numbers")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_VALUE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body( + "errors[0].message", + containsString( + "Bad $vector value: needs to be an array containing only Numbers but has a String value")); } @Test @@ -1090,9 +1096,9 @@ public void failWithEmptyVector() { .statusCode(200) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_SIZE")) - .body("errors[0].message", is("$vector value can't be empty")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_SIZE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].message", containsString("Bad $vector value: cannot be empty Array")); } @Test @@ -1139,9 +1145,12 @@ public void failWithInvalidVectorElements() { .statusCode(200) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_BAD_VECTOR_VALUE")) - .body("errors[0].message", is("$vector value needs to be array of numbers")); + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_VECTOR_VALUE.name())) + .body("errors[0].exceptionClass", is("DocumentException")) + .body( + "errors[0].message", + containsString( + "Bad $vector value: needs to be an array containing only Numbers but has a String value")); } // Vector columns can only use ANN, not regular filtering From ae4e795d261f4d816d2a763667ceb86b0c2f050d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 Jan 2026 09:52:54 -0800 Subject: [PATCH 13/20] One more code converted --- .../jsonapi/exception/DocumentException.java | 2 +- .../sgv2/jsonapi/exception/ErrorCodeV1.java | 2 -- .../collections/DocumentShredder.java | 8 +++-- .../collections/WritableShreddedDocument.java | 16 +++++---- .../stargate/sgv2/jsonapi/util/JsonUtil.java | 33 +++++++++++-------- src/main/resources/errors.yaml | 6 ++++ .../builders/FilterClauseBuilderTest.java | 23 +++++++------ .../v1/FindOneAndUpdateIntegrationTest.java | 7 ++-- .../shredding/DocumentShredderTest.java | 5 ++- ...DocumentShredderWithExtendedTypesTest.java | 20 +++++------ .../sgv2/jsonapi/util/JsonUtilTest.java | 8 ++--- 11 files changed, 72 insertions(+), 58 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index ec42b3e62c..f267a956ef 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -29,7 +29,7 @@ public enum Code implements ErrorCode { SHRED_BAD_DOCUMENT_TYPE, SHRED_BAD_DOCUMENT_VECTOR_TYPE, SHRED_BAD_DOCUMENT_LEXICAL_TYPE, - + SHRED_BAD_EJSON_VALUE, SHRED_BAD_VECTOR_SIZE, SHRED_BAD_VECTOR_VALUE, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 756daa9bb6..27feab21fe 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_BAD_EJSON_VALUE("Bad JSON Extension value"), - SHRED_DOC_KEY_NAME_VIOLATION("Document field name invalid"), SHRED_DOC_LIMIT_VIOLATION("Document size limitation violated"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index c2ce7b5b8c..e4e5c19e23 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -295,9 +295,11 @@ private void validateObjectValue( if (value.isTextual() || value.isIntegralNumber()) { return; } - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "type '%s' has invalid JSON value of type %s", - extType.encodedName(), value.getNodeType()); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "type '%s' has invalid JSON value of type %s" + .formatted(extType.encodedName(), JsonUtil.nodeTypeAsString(value)))); } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java index e2c0e124c3..b2c7aa1b6f 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/WritableShreddedDocument.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.util.CqlVectorUtil; import io.stargate.sgv2.jsonapi.util.JsonUtil; import java.math.BigDecimal; @@ -184,13 +183,18 @@ public boolean shredObject(JsonPath path, ObjectNode obj) { } break; } - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "invalid value (%s) for extended JSON type '%s' (path '%s')", - obj.iterator().next(), obj.fieldNames().next(), path); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "invalid value (%s) for extended JSON type '%s' (path '%s')" + .formatted(obj.iterator().next(), obj.fieldNames().next(), path))); } // Otherwise it's either unsupported of malformed EJSON-encoded value; fail - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "unrecognized extended JSON type '%s' (path '%s')", obj.fieldNames().next(), path); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "unrecognized extended JSON type '%s' (path '%s')" + .formatted(obj.fieldNames().next(), path))); } addKey(path); diff --git a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java index 8b74f7732f..547b1fe4a1 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Utf8; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.exception.ServerException; import io.stargate.sgv2.jsonapi.service.shredding.collections.DocumentId; @@ -130,9 +129,11 @@ public static Date extractEJsonDate(JsonNode json, Object path) { return new Date(value.longValue()); } // Otherwise we have an error case - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "Date (%s) needs to have NUMBER value, has %s (path '%s')", - EJSON_VALUE_KEY_DATE, value.getNodeType(), path); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "Date (%s) needs to have Number value, had %s (path '%s')" + .formatted(EJSON_VALUE_KEY_DATE, JsonUtil.nodeTypeAsString(value), path))); } } return null; @@ -268,17 +269,23 @@ private static Object tryExtractExtendedFromUnwrapped(JsonExtensionType etype, J private static void failOnInvalidExtendedValue(JsonExtensionType etype, JsonNode value) { switch (etype) { case EJSON_DATE: - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "'%s' value has to be an epoch timestamp, instead got (%s)", - etype.encodedName(), value); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "'%s' value has to be an epoch timestamp, instead got (%s)" + .formatted(etype.encodedName(), value))); case OBJECT_ID: - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "'%s' value has to be 24-digit hexadecimal ObjectId, instead got (%s)", - etype.encodedName(), value); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "'%s' value has to be 24-digit hexadecimal ObjectId, instead got (%s)" + .formatted(etype.encodedName(), value))); case UUID: - throw ErrorCodeV1.SHRED_BAD_EJSON_VALUE.toApiException( - "'%s' value has to be 36-character UUID String, instead got (%s)", - etype.encodedName(), value); + throw DocumentException.Code.SHRED_BAD_EJSON_VALUE.get( + Map.of( + "errorMessage", + "'%s' value has to be 36-character UUID String, instead got (%s)" + .formatted(etype.encodedName(), value))); } // should never happen throw ServerException.internalServerError("Unrecognized JsonExtensionType: " + etype); diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index e997d62da5..d2bf2105a2 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -343,6 +343,12 @@ request-errors: body: |- Bad $lexical value to shred: ${errorMessage}. + - scope: DOCUMENT + code: SHRED_BAD_EJSON_VALUE + title: Bad JSON Extension value to shred + body: |- + Bad JSON Extension value to shred: ${errorMessage}. + - scope: DOCUMENT code: SHRED_BAD_VECTOR_SIZE title: Bad $vector value diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java index ab3ed9efee..d18faa0b92 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/builders/FilterClauseBuilderTest.java @@ -11,7 +11,6 @@ import io.stargate.sgv2.jsonapi.config.OperationsConfig; import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.FilterException; -import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.shredding.collections.DocumentId; import io.stargate.sgv2.jsonapi.service.shredding.collections.JsonExtensionType; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; @@ -256,12 +255,12 @@ public void mustHandleDateAsEpoch() throws Exception { Throwable throwable = catchThrowable(() -> readCollectionFilterClause(json)); assertThat(throwable) - .isInstanceOf(JsonApiException.class) + .isInstanceOf(DocumentException.class) .satisfies( t -> { assertThat(t.getMessage()) - .isEqualTo( - "Bad JSON Extension value: '$date' value has to be an epoch timestamp, instead got (\"2023-01-01\")"); + .startsWith( + "Bad JSON Extension value to shred: '$date' value has to be an epoch timestamp, instead got (\"2023-01-01\")"); }); } @@ -274,12 +273,12 @@ public void mustHandleDateAsEpochAndOr() throws Exception { Throwable throwable = catchThrowable(() -> readCollectionFilterClause(json)); assertThat(throwable) - .isInstanceOf(JsonApiException.class) + .isInstanceOf(DocumentException.class) .satisfies( t -> { assertThat(t.getMessage()) - .isEqualTo( - "Bad JSON Extension value: '$date' value has to be an epoch timestamp, instead got (\"2023-01-01\")"); + .startsWith( + "Bad JSON Extension value to shred: '$date' value has to be an epoch timestamp, instead got (\"2023-01-01\")"); }); } @@ -1427,7 +1426,7 @@ public void mustFailOnBadUUIDAsId() throws Exception { t -> { assertThat(t.getMessage()) .contains( - "Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (\"abc\")"); + "Bad JSON Extension value to shred: '$uuid' value has to be 36-character UUID String, instead got (\"abc\")"); }); } @@ -1445,7 +1444,7 @@ public void mustFailOnBadObjectIdAsId() throws Exception { t -> { assertThat(t.getMessage()) .contains( - "Bad JSON Extension value: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"xyz\")"); + "Bad JSON Extension value to shred: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"xyz\")"); }); } @@ -1474,12 +1473,12 @@ public void mustFailOnBadUUIDAsField() throws Exception { Throwable throwable = catchThrowable(() -> readCollectionFilterClause(json)); assertThat(throwable) - .isInstanceOf(JsonApiException.class) + .isInstanceOf(DocumentException.class) .satisfies( t -> { assertThat(t.getMessage()) - .isEqualTo( - "Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (\"abc\")"); + .startsWith( + "Bad JSON Extension value to shred: '$uuid' value has to be 36-character UUID String, instead got (\"abc\")"); }); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java index e509a8ee24..e3596defb7 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.ClassOrderer; @@ -1369,11 +1370,11 @@ public void trySetWithInvalidDateField() { """; givenHeadersPostJsonThenOk(json) .body("$", responseIsError()) - .body("errors[0].errorCode", is("SHRED_BAD_EJSON_VALUE")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body( "errors[0].message", - is( - "Bad JSON Extension value: Date ($date) needs to have NUMBER value, has STRING (path 'createdAt')")); + containsString( + "Bad JSON Extension value to shred: Date ($date) needs to have Number value, had String (path 'createdAt')")); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index f73893b990..983e8b2a9d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -406,10 +406,9 @@ public void badEJSONDate() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_EJSON_VALUE) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) .hasMessageContaining( - ErrorCodeV1.SHRED_BAD_EJSON_VALUE.getMessage() - + ": type '$date' has invalid JSON value of type BOOLEAN"); + "Bad JSON Extension value to shred: type '$date' has invalid JSON value of type Boolean"); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java index 67dda723d5..614dc6d1fc 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java @@ -377,7 +377,7 @@ public void docInvalidObjectAsDocId() { assertThat(t) .isNotNull() .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) .hasMessageContaining( "'$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"not-an-oid\")"); } @@ -399,7 +399,7 @@ public void docInvalidUUIDAsDocId() { assertThat(t) .isNotNull() .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) .hasMessageContaining( "'$uuid' value has to be 36-character UUID String, instead got (\"not-a-uuid\")"); @@ -415,7 +415,7 @@ public void docInvalidUUIDAsDocId() { assertThat(t) .isNotNull() .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) .hasMessageContaining( "'$uuid' value has to be 36-character UUID String, instead got ({})"); } @@ -453,10 +453,9 @@ public void docBadObjectIdAsValue() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_EJSON_VALUE) - .hasMessageStartingWith( - ErrorCodeV1.SHRED_BAD_EJSON_VALUE.getMessage() - + ": invalid value (\"abc\") for extended JSON type '$objectId' (path 'value')"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) + .hasMessageContaining( + "Bad JSON Extension value to shred: invalid value (\"abc\") for extended JSON type '$objectId' (path 'value')"); } @Test @@ -471,10 +470,9 @@ public void docBadUUIDAsValue() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_EJSON_VALUE) - .hasMessageStartingWith( - ErrorCodeV1.SHRED_BAD_EJSON_VALUE.getMessage() - + ": invalid value (\"foobar\") for extended JSON type '$uuid' (path 'value')"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) + .hasMessageContaining( + "Bad JSON Extension value to shred: invalid value (\"foobar\") for extended JSON type '$uuid' (path 'value')"); } @Test diff --git a/src/test/java/io/stargate/sgv2/jsonapi/util/JsonUtilTest.java b/src/test/java/io/stargate/sgv2/jsonapi/util/JsonUtilTest.java index 9328a1ad0a..5d31018cd0 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/util/JsonUtilTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/util/JsonUtilTest.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; import jakarta.inject.Inject; import java.io.IOException; @@ -101,8 +101,8 @@ public void invalidEJSONDate() { Throwable t = catchThrowable(() -> JsonUtil.extractEJsonDate(ob, "/")); assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_BAD_EJSON_VALUE) - .hasMessage( - "Bad JSON Extension value: Date ($date) needs to have NUMBER value, has STRING (path '/')"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_EJSON_VALUE.name()) + .hasMessageContaining( + "Bad JSON Extension value to shred: Date ($date) needs to have Number value, had String (path '/')"); } } From fb4af3bdfb5a56d6ff3a91b11decd19bc4ef035d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 Jan 2026 10:30:00 -0800 Subject: [PATCH 14/20] Fix regressed ITs --- .../sgv2/jsonapi/api/v1/FindOneIntegrationTest.java | 9 +++++---- .../api/v1/InsertInCollectionIntegrationTest.java | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java index 15a041c93e..fa53912b38 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java @@ -6,6 +6,7 @@ import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.MethodOrderer; @@ -810,12 +811,12 @@ public void failForInvalidUUIDAsId() { "{ \"findOne\": { \"filter\" : {\"_id\": {\"$uuid\": \"not-an-uuid\"}}}}") .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( - "Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (\"not-an-uuid\")")); + "Bad JSON Extension value to shred: '$uuid' value has to be 36-character UUID String, instead got (\"not-an-uuid\")")); } @Test @@ -824,12 +825,12 @@ public void failForInvalidObjectIdAsId() { "{ \"findOne\": { \"filter\" : {\"_id\": {\"$objectId\": \"bogus\"}}}}") .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( - "Bad JSON Extension value: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"bogus\")")); + "Bad JSON Extension value to shred: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"bogus\")")); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index 8bc8398e41..4cb7aec76d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -608,11 +608,11 @@ public void failInsertDocWithInvalidUUIDAsDocId() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].exceptionClass", is("DocumentException")) - .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body( "errors[0].message", containsString( - "Bad type for '_id' field: Bad JSON Extension value: '$uuid' value has to be 36-character UUID String, instead got (42)")); + "Bad JSON Extension value to shred: '$uuid' value has to be 36-character UUID String, instead got (42)")); } @Test @@ -628,12 +628,12 @@ public void failInsertDocWithInvalidObjectIdAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", containsString( - "Bad JSON Extension value: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"not-quite-objectid\")")); + "Bad JSON Extension value to shred: '$objectId' value has to be 24-digit hexadecimal ObjectId, instead got (\"not-quite-objectid\")")); } @Test From 9c8a0c847a88dac1dc0a197a4622c634b53824e2 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 Jan 2026 12:00:10 -0800 Subject: [PATCH 15/20] One more conversion --- .../jsonapi/exception/DocumentException.java | 1 + .../sgv2/jsonapi/exception/ErrorCodeV1.java | 1 - .../collections/DocumentShredder.java | 7 ++++-- src/main/resources/errors.yaml | 6 +++++ .../v1/InsertInCollectionIntegrationTest.java | 14 +++++++----- .../DocumentShredderDocLimitsTest.java | 17 +++++++------- .../shredding/DocumentShredderTest.java | 22 +++++++++---------- ...DocumentShredderWithExtendedTypesTest.java | 6 ++--- 8 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index f267a956ef..3a7d2c6765 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -30,6 +30,7 @@ public enum Code implements ErrorCode { SHRED_BAD_DOCUMENT_VECTOR_TYPE, SHRED_BAD_DOCUMENT_LEXICAL_TYPE, SHRED_BAD_EJSON_VALUE, + SHRED_BAD_FIELD_NAME, // from ErrorV1.SHRED_DOC_KEY_NAME_VIOLATION SHRED_BAD_VECTOR_SIZE, SHRED_BAD_VECTOR_VALUE, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index 27feab21fe..e8787cf48e 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,7 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_DOC_KEY_NAME_VIOLATION("Document field name invalid"), SHRED_DOC_LIMIT_VIOLATION("Document size limitation violated"), // CreateCollection error codes: diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index e4e5c19e23..72a732bd5b 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -330,8 +330,11 @@ private void validateObjectKey(String key, JsonNode value, int depth, int parent || key.equals(DocumentConstants.Fields.LEXICAL_CONTENT_FIELD))) { ; } else { - throw ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION.toApiException( - "field name '%s' %s", key, key.isEmpty() ? "is empty" : "starts with '$'"); + throw DocumentException.Code.SHRED_BAD_FIELD_NAME.get( + Map.of( + "errorMessage", + "field name '%s' %s" + .formatted(key, key.isEmpty() ? "is empty" : "starts with '$'"))); } } int totalPathLength = parentPathLength + key.length(); diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index d2bf2105a2..e1ce93c249 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -349,6 +349,12 @@ request-errors: body: |- Bad JSON Extension value to shred: ${errorMessage}. + - scope: DOCUMENT + code: SHRED_BAD_FIELD_NAME + title: Document field name not valid + body: |- + Document field name not valid: ${errorMessage}. + - scope: DOCUMENT code: SHRED_BAD_VECTOR_SIZE title: Bad $vector value diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index 4cb7aec76d..be95ac96d0 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -1472,11 +1472,12 @@ public void orderedFailOnBadKeyReturnDocResponses() { """) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_KEY_NAME_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_FIELD_NAME.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith("Document field name invalid: field name '$username' starts with '$'")) + containsString( + "Document field name not valid: field name '$username' starts with '$'")) .body("insertedIds", is(nullValue())) .body("status.documentResponses", hasSize(3)) .body("status.documentResponses[0]", is(Map.of("_id", "doc1", "status", "OK"))) @@ -1562,11 +1563,12 @@ public void unorderedFailOnBadKeyReturnDocResponses() { """) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_KEY_NAME_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_FIELD_NAME.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith("Document field name invalid: field name '$username' starts with '$'")) + containsString( + "Document field name not valid: field name '$username' starts with '$'")) .body("insertedIds", is(nullValue())) .body("status.documentResponses", hasSize(3)) .body("status.documentResponses[0]", is(Map.of("_id", "doc1", "status", "OK"))) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java index 76e2a57038..5c85ae8ff7 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java @@ -15,6 +15,7 @@ import io.stargate.sgv2.jsonapi.api.request.RequestContext; import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; @@ -462,10 +463,10 @@ public void catchInvalidFieldNameDollar(String invalidName) { // First check at root level Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION.getMessage()) - .hasMessageEndingWith("field name '" + invalidName + "' starts with '$'"); + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageStartingWith("Document field name not valid: ") + .hasMessageContaining("field name '" + invalidName + "' starts with '$'"); // And then as a nested field final ObjectNode doc2 = objectMapper.createObjectNode(); @@ -474,10 +475,10 @@ public void catchInvalidFieldNameDollar(String invalidName) { e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION.getMessage()) - .hasMessageEndingWith("field name '" + invalidName + "' starts with '$'"); + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageStartingWith("Document field name not valid: ") + .hasMessageContaining("field name '" + invalidName + "' starts with '$'"); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java index 983e8b2a9d..5b95ff0092 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderTest.java @@ -15,7 +15,6 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.request.RequestContext; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; import io.stargate.sgv2.jsonapi.service.shredding.collections.*; @@ -454,8 +453,9 @@ public void badEJSONUnrecognized() { assertThat(t) .isNotNull() - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION.getMessage()) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageContaining( + "Document field name not valid: field name '$unknownType' starts with '$'"); } } @@ -532,8 +532,8 @@ public void docBadFieldNameRootLeadingDollar() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessageContaining("Document field name invalid: field name '$id' starts with '$'"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageContaining("Document field name not valid: field name '$id' starts with '$'"); } @Test @@ -548,8 +548,8 @@ public void docBadFieldNameNestedLeadingDollar() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessage("Document field name invalid: field name '$usd' starts with '$'"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageContaining("Document field name not valid: field name '$usd' starts with '$'"); } @Test @@ -562,8 +562,8 @@ public void docBadFieldNameEmptyRoot() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessage("Document field name invalid: field name '' is empty"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageContaining("Document field name not valid: field name '' is empty"); } @Test @@ -578,8 +578,8 @@ public void docBadFieldNameEmptyNested() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessage("Document field name invalid: field name '' is empty"); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageContaining("Document field name not valid: field name '' is empty"); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java index 614dc6d1fc..b541fef763 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderWithExtendedTypesTest.java @@ -14,7 +14,6 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.request.RequestContext; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionIdType; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; @@ -487,8 +486,9 @@ public void docUnknownEJsonAsValue() { assertThat(t) .isNotNull() - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_KEY_NAME_VIOLATION.getMessage()); + .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_FIELD_NAME.name()) + .hasMessageStartingWith( + "Document field name not valid: field name '$unknownType' starts with '$'"); } } From 6ec6cddaba1c62632b6f6b0e6d4b214271a9a1e1 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 Jan 2026 13:33:36 -0800 Subject: [PATCH 16/20] Convert the last shredding-specific ErrorCodeV1 --- .../jsonapi/exception/DocumentException.java | 1 + .../sgv2/jsonapi/exception/ErrorCodeV1.java | 2 - .../WebApplicationExceptionMapper.java | 10 +-- .../collections/DocumentShredder.java | 71 ++++++++++++------- src/main/resources/errors.yaml | 6 ++ .../v1/FindOneAndReplaceIntegrationTest.java | 12 ++-- .../v1/FindOneAndUpdateIntegrationTest.java | 4 +- ...indOneAndUpdateNoIndexIntegrationTest.java | 6 +- .../v1/InsertInCollectionIntegrationTest.java | 50 ++++++------- .../DocumentShredderDocLimitsTest.java | 66 +++++++++-------- 10 files changed, 127 insertions(+), 101 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 3a7d2c6765..85723a592b 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -33,6 +33,7 @@ public enum Code implements ErrorCode { SHRED_BAD_FIELD_NAME, // from ErrorV1.SHRED_DOC_KEY_NAME_VIOLATION SHRED_BAD_VECTOR_SIZE, SHRED_BAD_VECTOR_VALUE, + SHRED_DOC_LIMIT_VIOLATION, UNKNOWN_TABLE_COLUMNS, UNSUPPORTED_COLUMN_TYPES, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index e8787cf48e..a459eaabb6 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -17,8 +17,6 @@ public enum ErrorCodeV1 { EMBEDDING_PROVIDER_UNEXPECTED_RESPONSE("The Embedding Provider returned an unexpected response"), EMBEDDING_PROVIDER_API_KEY_MISSING("The Embedding Provider API key is missing"), - SHRED_DOC_LIMIT_VIOLATION("Document size limitation violated"), - // CreateCollection error codes: EXISTING_TABLE_NOT_DATA_API_COLLECTION("Existing table is not a valid Data API collection"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/WebApplicationExceptionMapper.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/WebApplicationExceptionMapper.java index 6ca472fcc8..eff9758feb 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/WebApplicationExceptionMapper.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/WebApplicationExceptionMapper.java @@ -4,15 +4,13 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; import io.stargate.sgv2.jsonapi.api.model.command.tracing.RequestTracing; import io.stargate.sgv2.jsonapi.config.OperationsConfig; -import io.stargate.sgv2.jsonapi.exception.APIException; -import io.stargate.sgv2.jsonapi.exception.APIExceptionCommandErrorBuilder; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; -import io.stargate.sgv2.jsonapi.exception.JsonApiException; +import io.stargate.sgv2.jsonapi.exception.*; import jakarta.inject.Inject; import jakarta.ws.rs.NotAllowedException; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotSupportedException; import jakarta.ws.rs.WebApplicationException; +import java.util.Map; import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.server.ServerExceptionMapper; @@ -32,7 +30,9 @@ public RestResponse webApplicationExceptionMapper(WebApplicationE // and if we have StreamConstraintsException, re-create as ApiException if (toReport instanceof StreamConstraintsException) { // but leave out the root cause, as it is not useful - toReport = ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException(toReport.getMessage()); + toReport = + DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of("errorMessage", toReport.getMessage())); } // V2 Error are returned as APIException, this is required to translate the exception to diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java index 72a732bd5b..cad4d960dd 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentShredder.java @@ -15,7 +15,6 @@ import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.exception.SchemaException; import io.stargate.sgv2.jsonapi.exception.ServerException; import io.stargate.sgv2.jsonapi.metrics.JsonProcessingMetricsReporter; @@ -235,9 +234,11 @@ private static JsonNode wrapExtensionType( private void validateDocumentSize(DocumentLimitsConfig limits, String docJson) { // First: is the resulting document size (as serialized) too big? if (docJson.length() > limits.maxSize()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "document size (%d chars) exceeds maximum allowed (%d)", - docJson.length(), limits.maxSize()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "document size (%d chars) exceeds maximum allowed (%d)" + .formatted(docJson.length(), limits.maxSize()))); } } @@ -339,16 +340,20 @@ private void validateObjectKey(String key, JsonNode value, int depth, int parent } int totalPathLength = parentPathLength + key.length(); if (totalPathLength > limits.maxPropertyPathLength()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "field path length (%d) exceeds maximum allowed (%d) (path ends with '%s')", - totalPathLength, limits.maxPropertyPathLength(), key); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "field path length (%d) exceeds maximum allowed (%d) (path ends with '%s')" + .formatted(totalPathLength, limits.maxPropertyPathLength(), key))); } } private void validateDocDepth(DocumentLimitsConfig limits, int depth) { if (depth > limits.maxDepth()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "document depth exceeds maximum allowed (%s)", limits.maxDepth()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "document depth exceeds maximum allowed (%d)".formatted(limits.maxDepth()))); } } } @@ -370,9 +375,11 @@ public IndexableValueValidator(DocumentLimitsConfig limits) { public void validate(ObjectNode doc) { validateObjectValue(null, doc); if (totalProperties.get() > limits.maxDocumentProperties()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "total number of indexed properties (%d) in document exceeds maximum allowed (%d)", - totalProperties.get(), limits.maxDocumentProperties()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "total number of indexed properties (%d) in document exceeds maximum allowed (%d)" + .formatted(totalProperties.get(), limits.maxDocumentProperties()))); } } @@ -391,14 +398,22 @@ private void validateArrayValue(String referringPropertyName, JsonNode arrayValu // One special case: vector embeddings allow larger size if (DocumentConstants.Fields.VECTOR_EMBEDDING_FIELD.equals(referringPropertyName)) { if (arrayValue.size() > limits.maxVectorEmbeddingLength()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "number of elements Vector embedding (field '%s') has (%d) exceeds maximum allowed (%d)", - referringPropertyName, arrayValue.size(), limits.maxVectorEmbeddingLength()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "number of elements Vector embedding (field '%s') has (%d) exceeds maximum allowed (%d)" + .formatted( + referringPropertyName, + arrayValue.size(), + limits.maxVectorEmbeddingLength()))); } } else { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "number of elements an indexable Array (field '%s') has (%d) exceeds maximum allowed (%d)", - referringPropertyName, arrayValue.size(), limits.maxArrayLength()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "number of elements an indexable Array (field '%s') has (%d) exceeds maximum allowed (%d)" + .formatted( + referringPropertyName, arrayValue.size(), limits.maxArrayLength()))); } } @@ -410,9 +425,12 @@ private void validateArrayValue(String referringPropertyName, JsonNode arrayValu private void validateObjectValue(String referringPropertyName, JsonNode objectValue) { final int propCount = objectValue.size(); if (propCount > limits.maxObjectProperties()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "number of properties an indexable Object (field '%s') has (%d) exceeds maximum allowed (%s)", - referringPropertyName, objectValue.size(), limits.maxObjectProperties()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "number of properties an indexable Object (field '%s') has (%d) exceeds maximum allowed (%s)" + .formatted( + referringPropertyName, objectValue.size(), limits.maxObjectProperties()))); } totalProperties.addAndGet(propCount); @@ -436,9 +454,14 @@ private void validateStringValue(String referringPropertyName, String value) { OptionalInt encodedLength = JsonUtil.lengthInBytesIfAbove(value, limits.maxStringLengthInBytes()); if (encodedLength.isPresent()) { - throw ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.toApiException( - "indexed String value (field '%s') length (%d bytes) exceeds maximum allowed (%d bytes)", - referringPropertyName, encodedLength.getAsInt(), limits.maxStringLengthInBytes()); + throw DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.get( + Map.of( + "errorMessage", + "indexed String value (field '%s') length (%d bytes) exceeds maximum allowed (%d bytes)" + .formatted( + referringPropertyName, + encodedLength.getAsInt(), + limits.maxStringLengthInBytes()))); } } } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index e1ce93c249..b56dd73531 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -367,6 +367,12 @@ request-errors: body: |- Bad $vector value: needs to be an array containing only Numbers but has a ${nodeType} value (${nodeValue}). + - scope: DOCUMENT + code: SHRED_DOC_LIMIT_VIOLATION + title: Document size limitation violated + body: |- + Document size limitation violated: ${errorMessage}. + # NOTE: UNKNOWN_TABLE_COLUMNS is also in the FILTER scope - scope: DOCUMENT code: UNKNOWN_TABLE_COLUMNS diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndReplaceIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndReplaceIntegrationTest.java index 7dd8d2fd4a..4badbe68f4 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndReplaceIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndReplaceIntegrationTest.java @@ -2,15 +2,11 @@ import static io.stargate.sgv2.jsonapi.api.v1.ResponseAssertions.*; import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; -import static org.hamcrest.Matchers.any; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.*; import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.ClassOrderer; @@ -613,10 +609,10 @@ public void tryReplaceWithTooLongNumber() { givenHeadersPostJsonThenOk(json) .body("errors", hasSize(1)) .body("$", responseIsError()) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) .body( "errors[0].message", - startsWith("Document size limitation violated: Number value length")); + containsString("Document size limitation violated: Number value length")); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java index e3596defb7..c32c4c238a 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java @@ -791,10 +791,10 @@ public void tryUpdateWithTooLongNumber() { .formatted(tooLongNumStr); givenHeadersPostJsonThenOk(json) .body("$", responseIsError()) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) .body( "errors[0].message", - startsWith("Document size limitation violated: Number value length")); + containsString("Document size limitation violated: Number value length")); } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateNoIndexIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateNoIndexIntegrationTest.java index 0bf798199e..c118a43c6b 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateNoIndexIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateNoIndexIntegrationTest.java @@ -12,6 +12,7 @@ import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Nested; @@ -232,7 +233,7 @@ public void failOnIndexedTooBigArray() { .then() .statusCode(200) .body("$", responseIsError()) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) .body( "errors[0].message", containsString("number of elements an indexable Array (field 'bigArray')")) @@ -297,7 +298,8 @@ public void failOnIndexedTooBigObject() { .then() .statusCode(200) .body("$", responseIsError()) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body( + "errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) .body( "errors[0].message", containsString("number of properties an indexable Object (field 'bigObject')")) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index be95ac96d0..4d04b4c7a5 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -693,11 +693,11 @@ public void tryInsertTooBigArray() { .formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - is( + containsString( "Document size limitation violated: number of elements an indexable Array (field 'arr') has (" + ARRAY_LEN + ") exceeds maximum allowed (" @@ -739,11 +739,11 @@ public void tryInsertTooLongPath() { .formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith( + containsString( "Document size limitation violated: field path length (1003) exceeds maximum allowed (1000)")); } @@ -786,11 +786,11 @@ public void tryInsertTooLongNumber() { .formatted(tooLongNumStr)) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith( + containsString( "Document size limitation violated: Number value length (110) exceeds the maximum allowed (100")); } @@ -828,11 +828,11 @@ public void tryInsertTooLongString() { .formatted(tooLongString)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith( + containsString( "Document size limitation violated: indexed String value (field 'bigString') length (8056 bytes) exceeds maximum allowed")); } @@ -880,11 +880,12 @@ public void tryInsertTooLongDoc() { .formatted(bigDoc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( - "errors[0].message", startsWith("Document size limitation violated: document size (")) - .body("errors[0].message", endsWith(") exceeds maximum allowed (4000000)")); + "errors[0].message", + containsString("Document size limitation violated: document size (")) + .body("errors[0].message", containsString(") exceeds maximum allowed (4000000)")); } @Test @@ -909,14 +910,14 @@ public void tryInsertDocWithTooBigObject() { .formatted(tooManyPropsDoc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith("Document size limitation violated: number of properties")) + containsString("Document size limitation violated: number of properties")) .body( "errors[0].message", - endsWith( + containsString( "indexable Object (field 'subdoc') has (1001) exceeds maximum allowed (1000)")); } @@ -944,12 +945,13 @@ public void tryInsertDocWithTooManyProps() { .formatted(tooManyPropsDoc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("JsonApiException")) - .body("errors[0].errorCode", is("SHRED_DOC_LIMIT_VIOLATION")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) + .body("errors[0].exceptionClass", is("DocumentException")) .body( "errors[0].message", - startsWith("Document size limitation violated: total number of indexed properties (")) - .body("errors[0].message", endsWith(" in document exceeds maximum allowed (2000)")); + containsString( + "Document size limitation violated: total number of indexed properties (")) + .body("errors[0].message", containsString(" in document exceeds maximum allowed (2000)")); } private void _verifyInsert(String docId, JsonNode doc) { diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java index 5c85ae8ff7..3927785f10 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/DocumentShredderDocLimitsTest.java @@ -16,8 +16,6 @@ import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; import io.stargate.sgv2.jsonapi.exception.DocumentException; -import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; -import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.projection.IndexingProjector; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; import io.stargate.sgv2.jsonapi.service.shredding.collections.DocumentShredder; @@ -70,10 +68,10 @@ public void catchTooBigDoc() { Exception e = catchException(() -> documentShredder.shred(commandContext(), bigDoc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith("exceeds maximum allowed (" + docLimits.maxSize() + ")"); + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining("exceeds maximum allowed (" + docLimits.maxSize() + ")"); } private ObjectNode createBigDoc(int mainProps, int subProps) { @@ -118,10 +116,10 @@ public void catchTooDeepDoc() { Exception e = catchException(() -> documentShredder.shred(commandContext(), deepDoc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( "document depth exceeds maximum allowed (" + docLimits.maxDepth() + ")"); } } @@ -158,10 +156,10 @@ public void catchTooManyObjectProps() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( " number of properties an indexable Object (field 'subdoc') has (" + tooManyProps + ") exceeds maximum allowed (" @@ -187,10 +185,10 @@ public void catchTooManyDocProps() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( " total number of indexed properties (2101) in document exceeds maximum allowed (" + docLimits.maxDocumentProperties() + ")"); @@ -236,10 +234,10 @@ public void catchTooManyArrayElements() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( " number of elements an indexable Array (field 'arr') has (" + arraySizeAboveMax + ") exceeds maximum allowed (" @@ -292,10 +290,10 @@ public void catchTooLongPaths() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( "field path length (1003) exceeds maximum allowed (" + docLimits.maxPropertyPathLength() + ") (path ends with '" @@ -327,10 +325,10 @@ public void catchTooLongStringValueAscii() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( " String value (field 'arr') length (" + tooLongLength + " bytes) exceeds maximum allowed (" @@ -359,10 +357,10 @@ public void catchTooLongStringValueUTF8() { Exception e = catchException(() -> documentShredder.shred(commandContext(), doc, null)); assertThat(e) .isNotNull() - .isInstanceOf(JsonApiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION) - .hasMessageStartingWith(ErrorCodeV1.SHRED_DOC_LIMIT_VIOLATION.getMessage()) - .hasMessageEndingWith( + .isInstanceOf(DocumentException.class) + .hasFieldOrPropertyWithValue( + "code", DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name()) + .hasMessageContaining( " String value (field 'text') length (" + (tooLongCharLength * 3) + " bytes) exceeds maximum allowed (" From 35bfa0345b954a2486b614464e01e604cf974588 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 Jan 2026 14:03:52 -0800 Subject: [PATCH 17/20] Convert one more shredding-adjacent ErrorCodeV1 --- .../stargate/sgv2/jsonapi/exception/DocumentException.java | 1 + .../io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java | 3 --- .../jsonapi/exception/mappers/ThrowableToErrorMapper.java | 4 ++-- src/main/resources/errors.yaml | 6 ++++++ .../api/v1/InsertLexicalInCollectionIntegrationTest.java | 5 ++++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index 85723a592b..a9a630f427 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -18,6 +18,7 @@ public enum Code implements ErrorCode { DOCUMENT_ALREADY_EXISTS, // Internal error: does it belong here? DOCUMENT_FROM_DB_UNPARSEABLE, + DOCUMENT_LEXICAL_CONTENT_TOO_BIG, DOCUMENT_REPLACE_DIFFERENT_DOCID, INVALID_COLUMN_VALUES, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java index a459eaabb6..e47bc8316c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/ErrorCodeV1.java @@ -74,9 +74,6 @@ public enum ErrorCodeV1 { VECTORIZE_CREDENTIAL_INVALID("Invalid credential name for vectorize"), VECTORIZECONFIG_CHECK_FAIL("Internal server error: VectorizeDefinition check fail"), - LEXICAL_CONTENT_TOO_BIG( - "Lexical content is too big, please use a smaller value for the $lexical field"), - HYBRID_FIELD_CONFLICT( "The '$hybrid' field cannot be used with '$lexical', '$vector', or '$vectorize'."), HYBRID_FIELD_UNSUPPORTED_VALUE_TYPE("Unsupported JSON value type for '$hybrid' field"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/ThrowableToErrorMapper.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/ThrowableToErrorMapper.java index e42aabaf15..8973e76dba 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/ThrowableToErrorMapper.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/mappers/ThrowableToErrorMapper.java @@ -190,8 +190,8 @@ private static CommandResult.Error handleQueryValidationException( // [data-api#2068]: Need to convert Lexical-value-too-big failure to something more meaningful if (message.contains( "analyzed size for column query_lexical_value exceeds the cumulative limit for index")) { - return ErrorCodeV1.LEXICAL_CONTENT_TOO_BIG - .toApiException() + return DocumentException.Code.DOCUMENT_LEXICAL_CONTENT_TOO_BIG + .get() .getCommandResultError(Response.Status.OK); } return ErrorCodeV1.INVALID_QUERY diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index b56dd73531..d1d14792ce 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -286,6 +286,12 @@ request-errors: Report the problem to database administrators. + - scope: DOCUMENT + code: DOCUMENT_LEXICAL_CONTENT_TOO_BIG + title: Document contains too big '$lexical' value + body: |- + Lexical content is too big, please use a smaller value for the '$lexical' field. + - scope: DOCUMENT code: DOCUMENT_REPLACE_DIFFERENT_DOCID title: Document the given _id already exists diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertLexicalInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertLexicalInCollectionIntegrationTest.java index 8543fb028e..ae496e6d18 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertLexicalInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertLexicalInCollectionIntegrationTest.java @@ -11,6 +11,7 @@ import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.fixtures.TestTextUtil; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; @@ -248,7 +249,9 @@ public void failInsertDocWithTooLongLexical() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is(ErrorCodeV1.LEXICAL_CONTENT_TOO_BIG.name())) + .body( + "errors[0].errorCode", + is(DocumentException.Code.DOCUMENT_LEXICAL_CONTENT_TOO_BIG.name())) .body("errors[0].message", containsString("Lexical content is too big")); } } From 6bd50294511e983434455350e66b6b9dbfef3b8c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 6 Jan 2026 15:36:34 -0800 Subject: [PATCH 18/20] Fix to issue from code review. --- .../jsonapi/exception/DatabaseException.java | 1 + .../shredding/collections/DocumentId.java | 11 +++-- src/main/resources/errors.yaml | 7 +++ .../v1/InsertInCollectionIntegrationTest.java | 48 +++++++++---------- .../shredding/collections/DocumentIdTest.java | 38 ++++++++------- 5 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java index 3ff2333e86..bc9991e28e 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java @@ -21,6 +21,7 @@ public enum Code implements ErrorCode { TIMEOUT_WRITING_DATA, UNAUTHORIZED_ACCESS, UNAVAILABLE_DATABASE, + UNEXPECTED_DOCUMENT_ID_TYPE, UNEXPECTED_DRIVER_ERROR, UNKNOWN_KEYSPACE, UNSUPPORTED_DATABASE_QUERY; diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java index b406a64265..97f68c52e3 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentId.java @@ -6,6 +6,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection; import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.JsonType; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; +import io.stargate.sgv2.jsonapi.exception.DatabaseException; import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; @@ -94,7 +95,7 @@ static DocumentId fromJson(JsonNode node) { static DocumentId fromDatabase(int typeId, String documentIdAsText) { JsonType type = DocumentConstants.KeyTypeId.getJsonType(typeId); if (type == null) { - throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + throw DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.get( Map.of( "errorMessage", "Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got %d" @@ -108,7 +109,7 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { case "false": return fromBoolean(false); } - throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + throw DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.get( Map.of( "errorMessage", "Document Id type Boolean stored as invalid String '%s' (must be 'true' or 'false')" @@ -121,7 +122,7 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { try { return fromNumber(new BigDecimal(documentIdAsText)); } catch (NumberFormatException e) { - throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + throw DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.get( Map.of( "errorMessage", "Document Id type Number stored as invalid String '%s' (not a valid Number)" @@ -136,7 +137,7 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { long ts = Long.parseLong(documentIdAsText); return fromTimestamp(ts); } catch (NumberFormatException e) { - throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + throw DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.get( Map.of( "errorMessage", "Document Id type Date stored as invalid String '%s' (needs to be Number)" @@ -144,7 +145,7 @@ static DocumentId fromDatabase(int typeId, String documentIdAsText) { } } } - throw DocumentException.Code.SHRED_BAD_DOCID_TYPE.get( + throw DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.get( Map.of("errorMessage", "unknown `JsonType`: '%s'".formatted(type))); } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index d1d14792ce..8f2355e7f5 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -2075,6 +2075,13 @@ server-errors: ${SNIPPET.RETRY} + - scope: DATABASE + code: UNEXPECTED_DOCUMENT_ID_TYPE + title: Unexpected stored document id type + body: |- + Type of document id stored in database unexpected: ${errorMessage}. + + ${SNIPPET.RETRY_UNKNOWN} - scope: DATABASE code: UNEXPECTED_DRIVER_ERROR diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index 4d04b4c7a5..91a0bc48be 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -649,11 +649,11 @@ public void failInsertDocWithUnknownExtensionAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("SHRED_BAD_DOCID_TYPE")) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_DOCID_TYPE.name())) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", - startsWith("Bad type for '_id' field: unrecognized JSON extension type")); + containsString("Bad type for '_id' field: unrecognized JSON extension type")); } } @@ -694,7 +694,7 @@ public void tryInsertTooBigArray() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -740,7 +740,7 @@ public void tryInsertTooLongPath() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -787,7 +787,7 @@ public void tryInsertTooLongNumber() { .body("$", responseIsError()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -829,7 +829,7 @@ public void tryInsertTooLongString() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -881,7 +881,7 @@ public void tryInsertTooLongDoc() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString("Document size limitation violated: document size (")) @@ -911,7 +911,7 @@ public void tryInsertDocWithTooBigObject() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString("Document size limitation violated: number of properties")) @@ -946,7 +946,7 @@ public void tryInsertDocWithTooManyProps() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_DOC_LIMIT_VIOLATION.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -1409,8 +1409,8 @@ public void orderedFailOnDuplicates() { .body("$", responseIsWritePartialSuccess()) .body("status.insertedIds", is(List.of("doc4"))) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", startsWith( @@ -1437,8 +1437,8 @@ public void orderedFailOnDupsReturnDocResponses() { """) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", startsWith( @@ -1475,7 +1475,7 @@ public void orderedFailOnBadKeyReturnDocResponses() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_FIELD_NAME.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -1520,23 +1520,23 @@ public void unorderedFailOnDups() { // Document to insert may fail as duplicate if it was executed after another // document in the list with that id .body("errors", hasSize(4)) - .body("errors[0].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", startsWith("Cannot insert the document: a document already exists with given '_id'")) - .body("errors[1].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[1].exceptionClass", is("DocumentException")) + .body("errors[1].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[1].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[1].message", startsWith("Cannot insert the document: a document already exists with given '_id'")) - .body("errors[2].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[2].exceptionClass", is("DocumentException")) + .body("errors[2].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[2].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[2].message", startsWith("Cannot insert the document: a document already exists with given '_id'")) - .body("errors[3].errorCode", is("DOCUMENT_ALREADY_EXISTS")) - .body("errors[3].exceptionClass", is("DocumentException")) + .body("errors[3].errorCode", is(DocumentException.Code.DOCUMENT_ALREADY_EXISTS.name())) + .body("errors[3].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[3].message", startsWith("Cannot insert the document: a document already exists with given '_id'")); @@ -1566,7 +1566,7 @@ public void unorderedFailOnBadKeyReturnDocResponses() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_FIELD_NAME.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java index a22970e727..a17b3f3d2f 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/shredding/collections/DocumentIdTest.java @@ -7,7 +7,7 @@ import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; -import io.stargate.sgv2.jsonapi.exception.DocumentException; +import io.stargate.sgv2.jsonapi.exception.DatabaseException; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; import java.math.BigDecimal; import java.util.Date; @@ -77,10 +77,11 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessageStartingWith( - "Bad type for '_id' field: Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got 99."); + .isInstanceOf(DatabaseException.class) + .hasFieldOrPropertyWithValue( + "code", DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.name()) + .hasMessageContaining( + "Type of document id stored in database unexpected: Document Id must be a JSON String(1), Number(2), Boolean(3), null(4) or Date(5) instead got 99"); e = catchException( @@ -89,10 +90,11 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessageStartingWith( - "Bad type for '_id' field: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')."); + .isInstanceOf(DatabaseException.class) + .hasFieldOrPropertyWithValue( + "code", DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.name()) + .hasMessageContaining( + "Type of document id stored in database unexpected: Document Id type Boolean stored as invalid String 'abc' (must be 'true' or 'false')"); e = catchException( @@ -101,10 +103,11 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessageStartingWith( - "Bad type for '_id' field: Document Id type Number stored as invalid String 'abc' (not a valid Number)."); + .isInstanceOf(DatabaseException.class) + .hasFieldOrPropertyWithValue( + "code", DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.name()) + .hasMessageContaining( + "Type of document id stored in database unexpected: Document Id type Number stored as invalid String 'abc' (not a valid Number)"); e = catchException( @@ -113,9 +116,10 @@ public void testFromDatabaseInvalid() throws Exception { }); assertThat(e) .isNotNull() - .isInstanceOf(DocumentException.class) - .hasFieldOrPropertyWithValue("code", DocumentException.Code.SHRED_BAD_DOCID_TYPE.name()) - .hasMessageStartingWith( - "Bad type for '_id' field: Document Id type Date stored as invalid String 'abc' (needs to be Number)."); + .isInstanceOf(DatabaseException.class) + .hasFieldOrPropertyWithValue( + "code", DatabaseException.Code.UNEXPECTED_DOCUMENT_ID_TYPE.name()) + .hasMessageContaining( + "Type of document id stored in database unexpected: Document Id type Date stored as invalid String 'abc' (needs to be Number)"); } } From 84668a8ffea5ebea4cdd0b40dc2633888288ae56 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 6 Jan 2026 15:45:14 -0800 Subject: [PATCH 19/20] Another minor fix --- src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java index 547b1fe4a1..8c4cb83949 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/util/JsonUtil.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Objects; import java.util.OptionalInt; +import org.apache.commons.text.WordUtils; import org.bson.types.ObjectId; public class JsonUtil { @@ -40,9 +41,7 @@ public static String nodeTypeAsString(JsonNodeType nodeType) { if (nodeType == null) { return ""; } - String typeDesc = nodeType.toString(); - // We know all are longer than 1 character, upper case, so: - return typeDesc.substring(0, 1) + typeDesc.substring(1).toLowerCase(); + return WordUtils.capitalizeFully(nodeType.toString()); } /** @@ -330,7 +329,7 @@ public static float[] arrayNodeToVector(ArrayNode arrayNode) { if (!element.isNumber()) { throw DocumentException.Code.SHRED_BAD_VECTOR_VALUE.get( Map.of( - "nodeType", JsonUtil.nodeTypeAsString(element), + "nodeType", nodeTypeAsString(element), "nodeValue", element.toString())); } arrayVals[i] = element.floatValue(); From bb026a2ac7557224cc76121250c2a96c4ea4c837 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 6 Jan 2026 17:13:56 -0800 Subject: [PATCH 20/20] Code review fixes --- .../jsonapi/exception/DatabaseException.java | 1 + .../jsonapi/exception/DocumentException.java | 2 -- .../collections/CollectionReadOperation.java | 4 +-- src/main/resources/errors.yaml | 28 +++++++++++-------- .../v1/InsertInCollectionIntegrationTest.java | 15 +++++----- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java index bc9991e28e..99fcb3fa72 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DatabaseException.java @@ -10,6 +10,7 @@ public DatabaseException(ErrorInstance errorInstance) { public enum Code implements ErrorCode { COUNT_READ_FAILED, // converted from ErrorV1 + DOCUMENT_FROM_DB_UNPARSEABLE, FAILED_CONCURRENT_OPERATIONS, FAILED_COMPARE_AND_SET, FAILED_READ_REQUEST, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java index a9a630f427..edf90cc247 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/DocumentException.java @@ -16,8 +16,6 @@ public DocumentException(ErrorInstance errorInstance) { public enum Code implements ErrorCode { DOCUMENT_ALREADY_EXISTS, - // Internal error: does it belong here? - DOCUMENT_FROM_DB_UNPARSEABLE, DOCUMENT_LEXICAL_CONTENT_TOO_BIG, DOCUMENT_REPLACE_DIFFERENT_DOCID, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/CollectionReadOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/CollectionReadOperation.java index 98973d2ea9..4d8b365c00 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/CollectionReadOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/CollectionReadOperation.java @@ -522,7 +522,7 @@ public JsonNode get() { * Helper method to handle details of exactly how much information to include in error message. */ static APIException parsingExceptionToApiException(JacksonException e) { - return DocumentException.Code.DOCUMENT_FROM_DB_UNPARSEABLE.get( - Map.of("message", e.getOriginalMessage())); + return DatabaseException.Code.DOCUMENT_FROM_DB_UNPARSEABLE.get( + Map.of("errorMessage", e.getOriginalMessage())); } } diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 8f2355e7f5..53690ba856 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -56,6 +56,10 @@ snippets: body: |- It is safe to retry this request. + - name: RETRY_AFTER_FIX + body: |- + Fix the issue before retrying this request. + - name: RETRY_UNKNOWN body: |- Review the Error Message before retrying this request. @@ -276,22 +280,14 @@ request-errors: Either resend the command with distinct '_id', or use an update command instead. - # Internal error: does it belong here? - - scope: DOCUMENT - code: DOCUMENT_FROM_DB_UNPARSEABLE - title: Unable to parse JSON content - body: |- - Unable to parse JSON content retrieved from the database; not valid JSON. - Underlying problem: ${message} - - Report the problem to database administrators. - - scope: DOCUMENT code: DOCUMENT_LEXICAL_CONTENT_TOO_BIG - title: Document contains too big '$lexical' value + title: Document contains too big lexical value body: |- Lexical content is too big, please use a smaller value for the '$lexical' field. + ${SNIPPET.RETRY_AFTER_FIX} + - scope: DOCUMENT code: DOCUMENT_REPLACE_DIFFERENT_DOCID title: Document the given _id already exists @@ -2075,6 +2071,16 @@ server-errors: ${SNIPPET.RETRY} + # Internal error: does it belong here? + - scope: DATABASE + code: DOCUMENT_FROM_DB_UNPARSEABLE + title: Unable to parse JSON content + body: |- + Unable to parse JSON content retrieved from the database; not valid JSON. + Underlying problem: ${errorMessage} + + Report the problem to database administrators. + - scope: DATABASE code: UNEXPECTED_DOCUMENT_ID_TYPE title: Unexpected stored document id type diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java index 91a0bc48be..b3f950c87e 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/InsertInCollectionIntegrationTest.java @@ -17,6 +17,7 @@ import io.stargate.sgv2.jsonapi.config.OperationsConfig; import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; import io.stargate.sgv2.jsonapi.exception.DocumentException; +import io.stargate.sgv2.jsonapi.exception.RequestException; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import java.io.IOException; import java.math.BigDecimal; @@ -71,7 +72,7 @@ public void shredFailureOnNullDoc() { .body("status.insertedIds", jsonEquals("[]")) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_DOCUMENT_TYPE.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString( @@ -319,8 +320,8 @@ public void noOptionsAllowed() { """) .body("$", responseIsError()) .body("errors", hasSize(1)) - .body("errors[0].errorCode", is("COMMAND_ACCEPTS_NO_OPTIONS")) - .body("errors[0].exceptionClass", is("RequestException")) + .body("errors[0].errorCode", is(RequestException.Code.COMMAND_ACCEPTS_NO_OPTIONS.name())) + .body("errors[0].exceptionClass", is(RequestException.class.getSimpleName())) .body( "errors[0].message", startsWith("Command 'insertOne' does not accept options but some were included.")); @@ -406,8 +407,8 @@ public void notValidDocumentMissing() { } """) .body("$", responseIsError()) - .body("errors[0].errorCode", is("COMMAND_FIELD_VALUE_INVALID")) - .body("errors[0].exceptionClass", is("RequestException")) + .body("errors[0].errorCode", is(RequestException.Code.COMMAND_FIELD_VALUE_INVALID.name())) + .body("errors[0].exceptionClass", is(RequestException.class.getSimpleName())) .body( "errors[0].message", startsWith( @@ -607,7 +608,7 @@ public void failInsertDocWithInvalidUUIDAsDocId() { givenHeadersPostJsonThenOk("{ \"insertOne\": { \"document\": %s }}".formatted(doc)) .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) .body( "errors[0].message", @@ -629,7 +630,7 @@ public void failInsertDocWithInvalidObjectIdAsDocId() { .body("$", responseIsWritePartialSuccess()) .body("errors", hasSize(1)) .body("errors[0].errorCode", is(DocumentException.Code.SHRED_BAD_EJSON_VALUE.name())) - .body("errors[0].exceptionClass", is("DocumentException")) + .body("errors[0].exceptionClass", is(DocumentException.class.getSimpleName())) .body( "errors[0].message", containsString(