Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance KotlinSerializer with value codecs for widening primitive conversion. #1301

Merged
merged 11 commits into from
Jul 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import org.bson.BsonType
import org.bson.BsonValue
import org.bson.codecs.BsonValueCodec
import org.bson.codecs.DecoderContext
import org.bson.internal.NumberCodecHelper
import org.bson.internal.StringCodecHelper
import org.bson.types.ObjectId

/**
Expand Down Expand Up @@ -154,14 +156,17 @@ internal open class DefaultBsonDecoder(
}
}

override fun decodeByte(): Byte = decodeInt().toByte()
override fun decodeChar(): Char = decodeString().single()
override fun decodeFloat(): Float = decodeDouble().toFloat()
override fun decodeShort(): Short = decodeInt().toShort()
override fun decodeBoolean(): Boolean = readOrThrow({ reader.readBoolean() }, BsonType.BOOLEAN)
override fun decodeDouble(): Double = readOrThrow({ reader.readDouble() }, BsonType.DOUBLE)
override fun decodeInt(): Int = readOrThrow({ reader.readInt32() }, BsonType.INT32)
override fun decodeLong(): Long = readOrThrow({ reader.readInt64() }, BsonType.INT64)
override fun decodeByte(): Byte = NumberCodecHelper.decodeByte(reader)

override fun decodeChar(): Char = StringCodecHelper.decodeChar(reader)
override fun decodeFloat(): Float = NumberCodecHelper.decodeFloat(reader)

override fun decodeShort(): Short = NumberCodecHelper.decodeShort(reader)
override fun decodeBoolean(): Boolean = reader.readBoolean()

override fun decodeDouble(): Double = NumberCodecHelper.decodeDouble(reader)
override fun decodeInt(): Int = NumberCodecHelper.decodeInt(reader)
override fun decodeLong(): Long = NumberCodecHelper.decodeLong(reader)
override fun decodeString(): String = readOrThrow({ reader.readString() }, BsonType.STRING)

override fun decodeNull(): Nothing? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.bson.codecs.kotlinx

import java.util.stream.Stream
import kotlin.test.assertEquals
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
Expand All @@ -23,12 +24,17 @@ import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.plus
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import org.bson.BsonBoolean
import org.bson.BsonDocument
import org.bson.BsonDocumentReader
import org.bson.BsonDocumentWriter
import org.bson.BsonDouble
import org.bson.BsonInt32
import org.bson.BsonInt64
import org.bson.BsonInvalidOperationException
import org.bson.BsonMaxKey
import org.bson.BsonMinKey
import org.bson.BsonString
import org.bson.BsonUndefined
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
Expand Down Expand Up @@ -90,11 +96,12 @@ import org.bson.codecs.kotlinx.samples.SealedInterface
import org.bson.codecs.kotlinx.samples.ValueClass
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource

@OptIn(ExperimentalSerializationApi::class)
@Suppress("LargeClass")
class KotlinSerializerCodecTest {
private val numberLong = "\$numberLong"
private val oid = "\$oid"
private val emptyDocument = "{}"
private val altConfiguration =
Expand Down Expand Up @@ -134,15 +141,59 @@ class KotlinSerializerCodecTest {

private val allBsonTypesDocument = BsonDocument.parse(allBsonTypesJson)

@Test
fun testDataClassWithSimpleValues() {
val expected =
"""{"char": "c", "byte": 0, "short": 1, "int": 22, "long": {"$numberLong": "42"}, "float": 4.0,
| "double": 4.2, "boolean": true, "string": "String"}"""
.trimMargin()
val dataClass = DataClassWithSimpleValues('c', 0, 1, 22, 42L, 4.0f, 4.2, true, "String")
companion object {
@JvmStatic
fun testTypesCastingDataClassWithSimpleValues(): Stream<BsonDocument> {
return Stream.of(
BsonDocument()
.append("char", BsonString("c"))
.append("byte", BsonInt32(1))
.append("short", BsonInt32(2))
.append("int", BsonInt32(10))
.append("long", BsonInt32(10))
.append("float", BsonInt32(2))
.append("double", BsonInt32(3))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),
BsonDocument()
.append("char", BsonString("c"))
.append("byte", BsonDouble(1.0))
.append("short", BsonDouble(2.0))
.append("int", BsonDouble(9.9999999999999992))
.append("long", BsonDouble(9.9999999999999992))
.append("float", BsonDouble(2.0))
.append("double", BsonDouble(3.0))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),
BsonDocument()
.append("char", BsonString("c"))
.append("byte", BsonDouble(1.0))
.append("short", BsonDouble(2.0))
.append("int", BsonDouble(10.0))
.append("long", BsonDouble(10.0))
.append("float", BsonDouble(2.0))
.append("double", BsonDouble(3.0))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),
BsonDocument()
.append("char", BsonString("c"))
.append("byte", BsonInt64(1))
.append("short", BsonInt64(2))
.append("int", BsonInt64(10))
.append("long", BsonInt64(10))
.append("float", BsonInt64(2))
.append("double", BsonInt64(3))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")))
}
}

assertRoundTrips(expected, dataClass)
@ParameterizedTest
@MethodSource("testTypesCastingDataClassWithSimpleValues")
fun testTypesCastingDataClassWithSimpleValues(data: BsonDocument) {
val expectedDataClass = DataClassWithSimpleValues('c', 1, 2, 10, 10L, 2.0f, 3.0, true, "String")

assertDecodesTo(data, expectedDataClass)
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion bson/src/main/org/bson/codecs/AtomicIntegerCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import java.util.concurrent.atomic.AtomicInteger;

import static org.bson.codecs.NumberCodecHelper.decodeInt;
import static org.bson.internal.NumberCodecHelper.decodeInt;

/**
* Encodes and decodes {@code AtomicInteger} objects.
Expand Down
2 changes: 1 addition & 1 deletion bson/src/main/org/bson/codecs/AtomicLongCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import java.util.concurrent.atomic.AtomicLong;

import static org.bson.codecs.NumberCodecHelper.decodeLong;
import static org.bson.internal.NumberCodecHelper.decodeLong;

/**
* Encodes and decodes {@code AtomicLong} objects.
Expand Down
10 changes: 2 additions & 8 deletions bson/src/main/org/bson/codecs/ByteCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@

package org.bson.codecs;

import org.bson.BsonInvalidOperationException;
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static java.lang.String.format;
import static org.bson.codecs.NumberCodecHelper.decodeInt;
import static org.bson.internal.NumberCodecHelper.decodeByte;

/**
* Encodes and decodes {@code Byte} objects.
Expand All @@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Byte value, final EncoderConte

@Override
public Byte decode(final BsonReader reader, final DecoderContext decoderContext) {
int value = decodeInt(reader);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value));
}
return (byte) value;
return decodeByte(reader);
}

@Override
Expand Down
11 changes: 2 additions & 9 deletions bson/src/main/org/bson/codecs/CharacterCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

package org.bson.codecs;

import org.bson.BsonInvalidOperationException;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.internal.StringCodecHelper;

import static java.lang.String.format;
import static org.bson.assertions.Assertions.notNull;

/**
Expand All @@ -38,13 +37,7 @@ public void encode(final BsonWriter writer, final Character value, final Encoder

@Override
public Character decode(final BsonReader reader, final DecoderContext decoderContext) {
String string = reader.readString();
if (string.length() != 1) {
throw new BsonInvalidOperationException(format("Attempting to decode the string '%s' to a character, but its length is not "
+ "equal to one", string));
}

return string.charAt(0);
return StringCodecHelper.decodeChar(reader);
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion bson/src/main/org/bson/codecs/DoubleCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static org.bson.codecs.NumberCodecHelper.decodeDouble;
import static org.bson.internal.NumberCodecHelper.decodeDouble;

/**
* Encodes and decodes {@code Double} objects.
Expand Down
10 changes: 2 additions & 8 deletions bson/src/main/org/bson/codecs/FloatCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@

package org.bson.codecs;

import org.bson.BsonInvalidOperationException;
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static java.lang.String.format;
import static org.bson.codecs.NumberCodecHelper.decodeDouble;
import static org.bson.internal.NumberCodecHelper.decodeFloat;

/**
* Encodes and decodes {@code Float} objects.
Expand All @@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Float value, final EncoderCont

@Override
public Float decode(final BsonReader reader, final DecoderContext decoderContext) {
double value = decodeDouble(reader);
if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value));
}
return (float) value;
return decodeFloat(reader);
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion bson/src/main/org/bson/codecs/IntegerCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static org.bson.codecs.NumberCodecHelper.decodeInt;
import static org.bson.internal.NumberCodecHelper.decodeInt;

/**
* Encodes and decodes {@code Integer} objects.
Expand Down
2 changes: 1 addition & 1 deletion bson/src/main/org/bson/codecs/LongCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static org.bson.codecs.NumberCodecHelper.decodeLong;
import static org.bson.internal.NumberCodecHelper.decodeLong;

/**
* Encodes and decodes {@code Long} objects.
Expand Down
10 changes: 2 additions & 8 deletions bson/src/main/org/bson/codecs/ShortCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@

package org.bson.codecs;

import org.bson.BsonInvalidOperationException;
import org.bson.BsonReader;
import org.bson.BsonWriter;

import static java.lang.String.format;
import static org.bson.codecs.NumberCodecHelper.decodeInt;
import static org.bson.internal.NumberCodecHelper.decodeShort;

/**
* Encodes and decodes {@code Short} objects.
Expand All @@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Short value, final EncoderCont

@Override
public Short decode(final BsonReader reader, final DecoderContext decoderContext) {
int value = decodeInt(reader);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value));
}
return (short) value;
return decodeShort(reader);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.bson.codecs;
package org.bson.internal;

import org.bson.BsonInvalidOperationException;
import org.bson.BsonReader;
Expand All @@ -25,9 +25,28 @@

import static java.lang.String.format;

final class NumberCodecHelper {
/**
* This class is not part of the public API. It may be removed or changed at any time.
*/
public final class NumberCodecHelper {

public static byte decodeByte(final BsonReader reader) {
int value = decodeInt(reader);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value));
}
return (byte) value;
}

public static short decodeShort(final BsonReader reader) {
int value = decodeInt(reader);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value));
}
return (short) value;
}

static int decodeInt(final BsonReader reader) {
public static int decodeInt(final BsonReader reader) {
int intValue;
BsonType bsonType = reader.getCurrentBsonType();
switch (bsonType) {
Expand Down Expand Up @@ -61,7 +80,7 @@ static int decodeInt(final BsonReader reader) {
return intValue;
}

static long decodeLong(final BsonReader reader) {
public static long decodeLong(final BsonReader reader) {
long longValue;
BsonType bsonType = reader.getCurrentBsonType();
switch (bsonType) {
Expand Down Expand Up @@ -91,7 +110,15 @@ static long decodeLong(final BsonReader reader) {
return longValue;
}

static double decodeDouble(final BsonReader reader) {
public static float decodeFloat(final BsonReader reader) {
double value = decodeDouble(reader);
if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) {
throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value));
}
return (float) value;
}

public static double decodeDouble(final BsonReader reader) {
double doubleValue;
BsonType bsonType = reader.getCurrentBsonType();
switch (bsonType) {
Expand Down
Loading