diff --git a/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt b/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt index f7d381e7..b7d33a8b 100644 --- a/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt +++ b/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt @@ -25,8 +25,9 @@ import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.hex import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.json import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.serialize import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.verify -import opensavvy.ktmongo.bson.types.Vector +import opensavvy.ktmongo.bson.types.ByteVector import opensavvy.ktmongo.bson.types.UuidAsBsonBinarySerializer +import opensavvy.ktmongo.bson.types.Vector import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.prepared.suite.Prepared import opensavvy.prepared.suite.SuiteDsl @@ -292,6 +293,9 @@ fun SuiteDsl.binary(context: Prepared) = suite("Binary") { document { writeVector("x", Vector.fromBinaryData(Base64.decode("AwB/Bw=="))) }, + document { + writeVector("x", ByteVector(127, 7)) + }, hex("11000000057800040000000903007F0700"), json($$"""{"x": {"$binary": {"base64": "AwB/Bw==", "subType": "09"}}}"""), verify("Read type") { @@ -309,6 +313,9 @@ fun SuiteDsl.binary(context: Prepared) = suite("Binary") { verify("Read vector content") { check(read("x")?.readVector()?.raw.contentEquals(byteArrayOf(127, 7))) }, + verify("Read vector content as list") { + check(read("x")?.readVector() == listOf(127, 7)) + }, ) testBson( @@ -376,6 +383,9 @@ fun SuiteDsl.binary(context: Prepared) = suite("Binary") { document { writeVector("x", Vector.fromBinaryData(Base64.decode("AwA="))) }, + document { + writeVector("x", ByteVector()) + }, hex("0F0000000578000200000009030000"), json($$"""{"x": {"$binary": {"base64": "AwA=", "subType": "09"}}}"""), verify("Read type") { @@ -393,6 +403,9 @@ fun SuiteDsl.binary(context: Prepared) = suite("Binary") { verify("Read vector content") { check(read("x")?.readVector()?.raw?.size == 0) }, + verify("Read vector content as list") { + check(read("x")?.readVector() == emptyList()) + }, ) testBson( diff --git a/bson/src/commonMain/kotlin/types/Vector.kt b/bson/src/commonMain/kotlin/types/Vector.kt index 4f9cd484..7e209d81 100644 --- a/bson/src/commonMain/kotlin/types/Vector.kt +++ b/bson/src/commonMain/kotlin/types/Vector.kt @@ -97,8 +97,10 @@ interface Vector { companion object { @LowLevelApi - fun fromBinaryData(content: ByteArray): Vector = - UnknownVector(content) + fun fromBinaryData(content: ByteArray): Vector = when (content[0]) { + 0x03.toByte() -> ByteVector(content.sliceArray(2 until content.size), Unit) + else -> UnknownVector(content) + } } } @@ -152,3 +154,106 @@ private class UnknownVector( append("])") } } + +/** + * A [Vector] of [Byte] elements (BSON's `Int8Vector`). + * + * The different bytes can be extracted with [toArray]. + * + * Alternatively, this class implements [List]. + */ +@OptIn(LowLevelApi::class) +class ByteVector internal constructor( + /** + * The underlying byte storage. **Do not mutate this array!** + * + * Note that this storage does NOT include the type nor the padding. + */ + private val rawUnsafe: ByteArray, + @Suppress("unused") unused: Unit, // avoid platform declaration clash with the vararg overload +) : Vector, Iterable, Collection, List { + + /** + * Constructs a [ByteVector] from a collection of bytes. + */ + constructor(bytes: Collection) : this(bytes.toByteArray(), Unit) + + /** + * Constructs a [ByteVector] from multiple bytes. + */ + constructor(vararg bytes: Byte) : this(bytes.asList()) + + override val type: Byte + get() = 0x03 + + @LowLevelApi + override val raw: ByteArray + get() = rawUnsafe.copyOf() + + @LowLevelApi + override val padding: Byte + get() = 0 + + override val size: Int + get() = rawUnsafe.size + + override fun isEmpty(): Boolean = + rawUnsafe.size == 0 + + override fun contains(element: Byte): Boolean = + rawUnsafe.contains(element) + + override fun containsAll(elements: Collection): Boolean = + elements.all { rawUnsafe.contains(it) } + + override fun get(index: Int): Byte = + rawUnsafe[index] + + override fun indexOf(element: Byte): Int = + rawUnsafe.indexOf(element) + + override fun lastIndexOf(element: Byte): Int = + rawUnsafe.lastIndexOf(element) + + override fun listIterator(): ListIterator = + rawUnsafe.asList().listIterator() + + override fun listIterator(index: Int): ListIterator = + rawUnsafe.asList().listIterator(index) + + override fun subList(fromIndex: Int, toIndex: Int): List = + rawUnsafe.slice(fromIndex until toIndex) + + override fun iterator(): ByteIterator = + rawUnsafe.iterator() + + fun toArray(): ByteArray = + raw // 'raw' is cloned on access + + override fun equals(other: Any?): Boolean { + return when { + this === other -> true + other === null -> false + other is ByteVector -> rawUnsafe.contentEquals(other.rawUnsafe) + other is List<*> -> { + if (size != other.size) return false + for (i in indices) { + if (rawUnsafe[i] != other[i]) return false + } + true + } + + else -> false + } + } + + override fun hashCode(): Int { + var hashCode = 1 + for (e in this) + hashCode = 31 * hashCode + e.hashCode() + return hashCode + } + + override fun toString(): String = + joinToString(separator = ", ", prefix = "ByteVector[", postfix = "]") +}