Skip to content

Commit

Permalink
Add "inet" codec (#1517)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatu-at-datastax authored Oct 9, 2024
1 parent b479461 commit bf13227
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ public abstract class JSONCodecRegistries {

// Other codecs
JSONCodecs.BINARY,
JSONCodecs.BOOLEAN));
JSONCodecs.BOOLEAN,
JSONCodecs.INET_FROM_STRING));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, java.net.InetAddress> INET_FROM_STRING =
new JSONCodec<>(
GenericType.STRING,
DataTypes.INET,
JSONCodec.ToCQL.safeFromString(JSONCodec.ToCQL::inetAddressFromString),
(objectMapper, fromCQLType, value) ->
objectMapper.getNodeFactory().textNode(value.getHostAddress()));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -624,7 +682,7 @@ void failOnWrongListElementValue() {
}

@Nested
@Order(8)
@Order(9)
class InsertSetColumns {
@Test
void insertValidSetValues() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -272,7 +273,7 @@ private static Stream<Arguments> validCodecToCQLTestCasesUuid() {
java.util.UUID.fromString(TEST_DATA.UUID_VALID_STR_UC)));
}

private static Stream<Arguments> validCodecToCQLTestCasesOther() {
private static Stream<Arguments> validCodecToCQLTestCasesOther() throws Exception {
// Arguments: (CQL-type, from-caller, bound-by-driver-for-cql)
return Stream.of(
// Short regular base64-encoded string
Expand All @@ -281,7 +282,11 @@ private static Stream<Arguments> 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<Arguments> validCodecToCQLTestCasesCollections() {
Expand Down Expand Up @@ -516,7 +521,7 @@ private static Stream<Arguments> validCodecToJSONTestCasesUuid() {
JSONS.textNode(TEST_DATA.UUID_VALID_STR_UC.toLowerCase())));
}

private static Stream<Arguments> validCodecToJSONTestCasesOther() {
private static Stream<Arguments> validCodecToJSONTestCasesOther() throws Exception {
// Arguments: (CQL-type, from-CQL-result-set, JsonNode-to-serialize)
return Stream.of(
// Short regular base64-encoded string
Expand All @@ -526,7 +531,11 @@ private static Stream<Arguments> 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<Arguments> validCodecToJSONTestCasesCollections() throws IOException {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit bf13227

Please sign in to comment.