Skip to content

Commit c20e678

Browse files
oschwaldclaude
andcommitted
Add type coercion support for UINT64/UINT128
Implement coerceFromBigInteger() method to enable type coercion for UINT64 and UINT128 values, making them consistent with UINT16, UINT32, and INT32 types. This allows UINT64/UINT128 values to be decoded into smaller types (Long, Integer, Short, Byte) when they fit within the target type's range. Add comprehensive tests covering successful coercions and proper range checking for out-of-bounds values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent d881a49 commit c20e678

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

src/main/java/com/maxmind/db/Decoder.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ private <T> Object decodeByType(
244244
return coerceFromInt(this.decodeInt32(size), cls);
245245
case UINT64:
246246
case UINT128:
247-
return this.decodeBigInteger(size);
247+
return coerceFromBigInteger(this.decodeBigInteger(size), cls);
248248
default:
249249
throw new InvalidDatabaseException(
250250
"Unknown or unexpected type: " + type.name());
@@ -319,6 +319,47 @@ private static Object coerceFromLong(long value, Class<?> target) {
319319
return value;
320320
}
321321

322+
private static Object coerceFromBigInteger(BigInteger value, Class<?> target) {
323+
if (target.equals(Object.class) || target.equals(BigInteger.class)) {
324+
return value;
325+
}
326+
if (target.equals(Long.TYPE) || target.equals(Long.class)) {
327+
if (value.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0
328+
|| value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
329+
throw new DeserializationException("Value " + value + " out of range for long");
330+
}
331+
return value.longValue();
332+
}
333+
if (target.equals(Integer.TYPE) || target.equals(Integer.class)) {
334+
if (value.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0
335+
|| value.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
336+
throw new DeserializationException("Value " + value + " out of range for int");
337+
}
338+
return value.intValue();
339+
}
340+
if (target.equals(Short.TYPE) || target.equals(Short.class)) {
341+
if (value.compareTo(BigInteger.valueOf(Short.MIN_VALUE)) < 0
342+
|| value.compareTo(BigInteger.valueOf(Short.MAX_VALUE)) > 0) {
343+
throw new DeserializationException("Value " + value + " out of range for short");
344+
}
345+
return value.shortValue();
346+
}
347+
if (target.equals(Byte.TYPE) || target.equals(Byte.class)) {
348+
if (value.compareTo(BigInteger.valueOf(Byte.MIN_VALUE)) < 0
349+
|| value.compareTo(BigInteger.valueOf(Byte.MAX_VALUE)) > 0) {
350+
throw new DeserializationException("Value " + value + " out of range for byte");
351+
}
352+
return value.byteValue();
353+
}
354+
if (target.equals(Double.TYPE) || target.equals(Double.class)) {
355+
return value.doubleValue();
356+
}
357+
if (target.equals(Float.TYPE) || target.equals(Float.class)) {
358+
return value.floatValue();
359+
}
360+
return value;
361+
}
362+
322363
private String decodeString(long size) throws CharacterCodingException {
323364
var oldLimit = buffer.limit();
324365
buffer.limit(buffer.position() + size);

src/test/java/com/maxmind/db/DecoderTest.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,105 @@ private static <T> void testTypeDecoding(Type type, Map<T, byte[]> tests)
475475
}
476476
}
477477

478+
@Test
479+
public void testUint64Coercion() throws IOException {
480+
// Test data: small UINT64 values that fit in smaller types
481+
var testData = largeUint(64);
482+
483+
var cache = new CHMCache();
484+
485+
// Test UINT64(0) → Byte
486+
var zeroBytes = testData.get(BigInteger.ZERO);
487+
var buffer = SingleBuffer.wrap(zeroBytes);
488+
var decoder = new TestDecoder(cache, buffer, 0);
489+
assertEquals((byte) 0, decoder.decode(0, Byte.class), "UINT64(0) should coerce to byte");
490+
491+
// Test UINT64(500) → Long
492+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(500)));
493+
decoder = new TestDecoder(cache, buffer, 0);
494+
assertEquals(500L, decoder.decode(0, Long.class), "UINT64(500) should coerce to long");
495+
496+
// Test UINT64(500) → Integer
497+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(500)));
498+
decoder = new TestDecoder(cache, buffer, 0);
499+
assertEquals(500, decoder.decode(0, Integer.class), "UINT64(500) should coerce to int");
500+
501+
// Test UINT64(500) → Short
502+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(500)));
503+
decoder = new TestDecoder(cache, buffer, 0);
504+
assertEquals((short) 500, decoder.decode(0, Short.class), "UINT64(500) should coerce to short");
505+
506+
// Test UINT64(500) → Byte (should fail - out of range)
507+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(500)));
508+
decoder = new TestDecoder(cache, buffer, 0);
509+
var finalDecoder1 = decoder;
510+
var ex1 = assertThrows(DeserializationException.class,
511+
() -> finalDecoder1.decode(0, Byte.class),
512+
"UINT64(500) should not fit in byte");
513+
assertThat(ex1.getMessage(), containsString("out of range for byte"));
514+
515+
// Test UINT64(2^64-1) → Long (should fail - too large)
516+
var maxUint64 = BigInteger.valueOf(2).pow(64).subtract(BigInteger.ONE);
517+
buffer = SingleBuffer.wrap(testData.get(maxUint64));
518+
decoder = new TestDecoder(cache, buffer, 0);
519+
var finalDecoder2 = decoder;
520+
var ex2 = assertThrows(DeserializationException.class,
521+
() -> finalDecoder2.decode(0, Long.class),
522+
"UINT64(2^64-1) should not fit in long");
523+
assertThat(ex2.getMessage(), containsString("out of range for long"));
524+
525+
// Test UINT64(2^64-1) → BigInteger (should work)
526+
buffer = SingleBuffer.wrap(testData.get(maxUint64));
527+
decoder = new TestDecoder(cache, buffer, 0);
528+
assertEquals(maxUint64, decoder.decode(0, BigInteger.class),
529+
"UINT64(2^64-1) should decode to BigInteger");
530+
531+
// Test UINT64(10872) → Float
532+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(10872)));
533+
decoder = new TestDecoder(cache, buffer, 0);
534+
assertEquals(10872.0f, decoder.decode(0, Float.class), 0.001f,
535+
"UINT64(10872) should coerce to float");
536+
537+
// Test UINT64(10872) → Double
538+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(10872)));
539+
decoder = new TestDecoder(cache, buffer, 0);
540+
assertEquals(10872.0, decoder.decode(0, Double.class), 0.001,
541+
"UINT64(10872) should coerce to double");
542+
}
543+
544+
@Test
545+
public void testUint128Coercion() throws IOException {
546+
// Test data: UINT128 values
547+
var testData = largeUint(128);
548+
549+
var cache = new CHMCache();
550+
551+
// Test UINT128(0) → Long
552+
var zeroBytes = testData.get(BigInteger.ZERO);
553+
var buffer = SingleBuffer.wrap(zeroBytes);
554+
var decoder = new TestDecoder(cache, buffer, 0);
555+
assertEquals(0L, decoder.decode(0, Long.class), "UINT128(0) should coerce to long");
556+
557+
// Test UINT128(500) → Integer
558+
buffer = SingleBuffer.wrap(testData.get(BigInteger.valueOf(500)));
559+
decoder = new TestDecoder(cache, buffer, 0);
560+
assertEquals(500, decoder.decode(0, Integer.class), "UINT128(500) should coerce to int");
561+
562+
// Test UINT128(2^128-1) → Long (should fail - way too large)
563+
var maxUint128 = BigInteger.valueOf(2).pow(128).subtract(BigInteger.ONE);
564+
buffer = SingleBuffer.wrap(testData.get(maxUint128));
565+
decoder = new TestDecoder(cache, buffer, 0);
566+
var finalDecoder = decoder;
567+
var ex = assertThrows(DeserializationException.class,
568+
() -> finalDecoder.decode(0, Long.class),
569+
"UINT128(2^128-1) should not fit in long");
570+
assertThat(ex.getMessage(), containsString("out of range for long"));
571+
572+
// Test UINT128(2^128-1) → BigInteger (should work)
573+
buffer = SingleBuffer.wrap(testData.get(maxUint128));
574+
decoder = new TestDecoder(cache, buffer, 0);
575+
assertEquals(maxUint128, decoder.decode(0, BigInteger.class),
576+
"UINT128(2^128-1) should decode to BigInteger");
577+
}
578+
478579
}

0 commit comments

Comments
 (0)