From bf13227cb0829500541748b7b54e59ba81f41eba Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 9 Oct 2024 09:39:11 -0700 Subject: [PATCH] Add "inet" codec (#1517) --- .../filters/table/codecs/JSONCodec.java | 10 ++++ .../table/codecs/JSONCodecRegistries.java | 3 +- .../filters/table/codecs/JSONCodecs.java | 9 +++ .../tables/InsertOneTableIntegrationTest.java | 60 ++++++++++++++++++- .../table/codecs/JSONCodecRegistryTest.java | 37 ++++++++++-- .../codecs/JSONCodecRegistryTestData.java | 3 + 6 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodec.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodec.java index 1bed856fd..16e9e427e 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodec.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodec.java @@ -12,6 +12,8 @@ import io.stargate.sgv2.jsonapi.exception.catchable.ToJSONCodecException; import io.stargate.sgv2.jsonapi.service.shredding.tables.RowShredder; import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.time.DateTimeException; import java.util.function.Function; @@ -363,6 +365,14 @@ static ByteBuffer byteBufferFromEJSON(DataType targetCQLType, EJSONWrapper wrapp } return ByteBuffer.wrap(binaryPayload); } + + static InetAddress inetAddressFromString(String value) throws IllegalArgumentException { + try { + return InetAddress.getByName(value); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid IP address value '%s'".formatted(value)); + } + } } /** diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistries.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistries.java index a2b0330a0..0154a9008 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistries.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistries.java @@ -54,5 +54,6 @@ public abstract class JSONCodecRegistries { // Other codecs JSONCodecs.BINARY, - JSONCodecs.BOOLEAN)); + JSONCodecs.BOOLEAN, + JSONCodecs.INET_FROM_STRING)); } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecs.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecs.java index 9854d1531..fe4bf6352 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecs.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecs.java @@ -294,4 +294,13 @@ public abstract class JSONCodecs { DataTypes.TIMEUUID, JSONCodec.ToCQL.safeFromString(UUID::fromString), JSONCodec.ToJSON.toJSONUsingToString()); + + // Misc other Codecs + public static final JSONCodec INET_FROM_STRING = + new JSONCodec<>( + GenericType.STRING, + DataTypes.INET, + JSONCodec.ToCQL.safeFromString(JSONCodec.ToCQL::inetAddressFromString), + (objectMapper, fromCQLType, value) -> + objectMapper.getNodeFactory().textNode(value.getHostAddress())); } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java index 553d89450..693b487b2 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java @@ -24,6 +24,7 @@ public class InsertOneTableIntegrationTest extends AbstractTableIntegrationTestB static final String TABLE_WITH_BINARY_COLUMN = "insertOneBinaryColumnsTable"; static final String TABLE_WITH_DATETIME_COLUMNS = "insertOneDateTimeColumnsTable"; static final String TABLE_WITH_UUID_COLUMN = "insertOneUuidColumnTable"; + static final String TABLE_WITH_INET_COLUMN = "insertOneInetColumnTable"; static final String TABLE_WITH_LIST_COLUMNS = "insertOneListColumnsTable"; static final String TABLE_WITH_SET_COLUMNS = "insertOneSetColumnsTable"; @@ -76,6 +77,13 @@ public final void createDefaultTables() { "uuidValue", "uuid"), "id"); + createTableWithColumns( + TABLE_WITH_INET_COLUMN, + Map.of( + "id", "text", + "inetValue", "inet"), + "id"); + createTableWithColumns( TABLE_WITH_LIST_COLUMNS, Map.of( @@ -526,6 +534,56 @@ private String uuidDoc(String id, String uuidValueStr) { @Nested @Order(7) + class InsertInetColumn { + @Test + void insertValidInetValue() { + final String docJSON = inetDoc("inetValid", "\"192.168.5.99\""); + insertOneInTable(TABLE_WITH_INET_COLUMN, docJSON); + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INET_COLUMN) + .postFindOne("{ \"filter\": { \"id\": \"inetValid\" } }") + .hasNoErrors() + .hasJSONField("data.document", docJSON); + } + + @Test + void failOnInvalidInetString() { + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INET_COLUMN) + .postInsertOne(inetDoc("inetInvalid", "\"xxx\"")) + .hasSingleApiError( + DocumentException.Code.INVALID_COLUMN_VALUES, + DocumentException.class, + "Only values that are supported by", + "Error trying to convert to targetCQLType `INET` from", + "problem: Invalid IP address value 'xxx'"); + } + + // Test for non-String input + @Test + void failOnInvalidInetArray() { + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INET_COLUMN) + .postInsertOne(inetDoc("inetInvalid", "[1, 2, 3, 4]")) + .hasSingleApiError( + DocumentException.Code.INVALID_COLUMN_VALUES, + DocumentException.class, + "Only values that are supported by", + "Error trying to convert to targetCQLType `INET` from", + "Root cause: no codec matching value type"); + } + + private String inetDoc(String id, String inetValueStr) { + return + """ + { + "id": "%s", + "inetValue": %s + } + """ + .formatted(id, inetValueStr); + } + } + + @Nested + @Order(8) class InsertListColumns { @Test void insertValidListValues() { @@ -624,7 +682,7 @@ void failOnWrongListElementValue() { } @Nested - @Order(8) + @Order(9) class InsertSetColumns { @Test void insertValidSetValues() { diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTest.java index 59254f6c4..547a5d8bb 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.InetAddress; import java.nio.ByteBuffer; import java.time.Instant; import java.time.LocalDate; @@ -272,7 +273,7 @@ private static Stream validCodecToCQLTestCasesUuid() { java.util.UUID.fromString(TEST_DATA.UUID_VALID_STR_UC))); } - private static Stream validCodecToCQLTestCasesOther() { + private static Stream validCodecToCQLTestCasesOther() throws Exception { // Arguments: (CQL-type, from-caller, bound-by-driver-for-cql) return Stream.of( // Short regular base64-encoded string @@ -281,7 +282,11 @@ private static Stream validCodecToCQLTestCasesOther() { binaryWrapper(TEST_DATA.BASE64_PADDED_ENCODED_STR), ByteBuffer.wrap(TEST_DATA.BASE64_PADDED_DECODED_BYTES)), // edge case: empty String -> byte[0] - Arguments.of(DataTypes.BLOB, binaryWrapper(""), ByteBuffer.wrap(new byte[0]))); + Arguments.of(DataTypes.BLOB, binaryWrapper(""), ByteBuffer.wrap(new byte[0])), + Arguments.of( + DataTypes.INET, + TEST_DATA.INET_ADDRESS_VALID_STRING, + InetAddress.getByName(TEST_DATA.INET_ADDRESS_VALID_STRING))); } private static Stream validCodecToCQLTestCasesCollections() { @@ -516,7 +521,7 @@ private static Stream validCodecToJSONTestCasesUuid() { JSONS.textNode(TEST_DATA.UUID_VALID_STR_UC.toLowerCase()))); } - private static Stream validCodecToJSONTestCasesOther() { + private static Stream validCodecToJSONTestCasesOther() throws Exception { // Arguments: (CQL-type, from-CQL-result-set, JsonNode-to-serialize) return Stream.of( // Short regular base64-encoded string @@ -526,7 +531,11 @@ private static Stream validCodecToJSONTestCasesOther() { binaryWrapper(TEST_DATA.BASE64_PADDED_ENCODED_STR).asJsonNode()), // edge case: empty String -> byte[0] - Arguments.of(DataTypes.BLOB, ByteBuffer.wrap(new byte[0]), binaryWrapper("").asJsonNode())); + Arguments.of(DataTypes.BLOB, ByteBuffer.wrap(new byte[0]), binaryWrapper("").asJsonNode()), + Arguments.of( + DataTypes.INET, + InetAddress.getByName(TEST_DATA.INET_ADDRESS_VALID_STRING), + JSONS.textNode(TEST_DATA.INET_ADDRESS_VALID_STRING))); } private static Stream validCodecToJSONTestCasesCollections() throws IOException { @@ -854,6 +863,26 @@ public void invalidBinaryInputs() { }); } + @Test + public void invalidInetAddress() { + final String valueToTest = TEST_DATA.INET_ADDRESS_INVALID_STRING; + final var codec = assertGetCodecToCQL(DataTypes.INET, valueToTest); + var error = + assertThrowsExactly( + ToCQLCodecException.class, + () -> codec.toCQL(valueToTest), + "Throw ToCQLCodecException when attempting to convert DataTypes.INET from invalid Base64 value"); + assertThat(error) + .satisfies( + e -> { + assertThat(e.targetCQLType).isEqualTo(DataTypes.INET); + assertThat(e.value).isEqualTo(valueToTest); + assertThat(e.getMessage()) + .contains("Root cause: Invalid String value for type `INET`") + .contains("Invalid IP address value"); + }); + } + @Test public void invalidListValueFail() { DataType cqlTypeToTest = DataTypes.listOf(DataTypes.INT); diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTestData.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTestData.java index 7ee75be61..59e7d0e9d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTestData.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/filters/table/codecs/JSONCodecRegistryTestData.java @@ -49,6 +49,9 @@ public class JSONCodecRegistryTestData { public final String BASE64_PADDED_ENCODED_STR = "bGlnaHQgd29yaw=="; public final String BASE64_UNPADDED_ENCODED_STR = "bGlnaHQgd29yaw"; + public final String INET_ADDRESS_VALID_STRING = "192.168.1.3"; + public final String INET_ADDRESS_INVALID_STRING = "not-an-ip-address"; + public final String STRING_ASCII_SAFE = "ascii-safe-string"; public final String STRING_WITH_2BYTE_UTF8_CHAR = "text-with-2-byte-utf8-\u00a2"; // cent symbol public final String STRING_WITH_3BYTE_UTF8_CHAR = "text-with-3-byte-utf8-\u20ac"; // euro symbol