Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ val ElementDeclarationContext.elementAnnotations: List<Annotation>
inline fun <reified T : Annotation> ElementDeclarationContext.findElementAnnotation(): T? {
return elementAnnotations.findAnnotation()
}

inline fun <reified T : Annotation> ElementDeclarationContext.hasAnnotation(): Boolean {
return findElementAnnotation<T>() != null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@file:OptIn(ExperimentalSerializationApi::class)

package io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.annotations

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialInfo

/**
* Disables the SCALE `Option<bool>` single-byte optimization for the annotated `Boolean?` property.
* When present, the property is encoded using the standard 2-byte optional encoding:
* `0x00` for null, `0x01 0x00` for false, `0x01 0x01` for true.
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
annotation class DisableOptionalBooleanOptimization
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.dec

import io.emeraldpay.polkaj.scale.ScaleCodecReader
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.ElementDeclarationContext
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.annotations.DisableOptionalBooleanOptimization
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.common.ScaleOptional.NULL_MARK
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.findElementAnnotation
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
Expand Down Expand Up @@ -70,7 +72,9 @@ abstract class BaseCompositeBinaryDecoder(
if (nullabilityByte == NULL_MARK) return null

val elementContext = ElementDeclarationContext(index, descriptor)
val delegate = PrimitiveBinaryScaleDecoder(serializersModule, reader, elementContext, nullabilityByte)
val disableOptBool = elementContext.findElementAnnotation<DisableOptionalBooleanOptimization>() != null
val passedNullabilityByte = if (disableOptBool) null else nullabilityByte
val delegate = PrimitiveBinaryScaleDecoder(serializersModule, reader, elementContext, passedNullabilityByte)
return delegate.decodeSerializableValue(deserializer)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.enc
import io.emeraldpay.polkaj.scale.ScaleCodecWriter
import io.novasama.substrate_sdk_android.extensions.toSignedBytes
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.ElementDeclarationContext
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.annotations.DisableOptionalBooleanOptimization
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.common.ScaleOptional.NOT_NULL_MARK
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.common.ScaleOptional.NULL_MARK
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.common.encodeOptionalBoolean
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.findElementAnnotation
import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.hasAnnotation
import io.novasama.substrate_sdk_android.scale.utils.directWrite
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
Expand Down Expand Up @@ -130,7 +133,7 @@ internal class CompositeBinaryEncoder(
) {
when {
serializer.descriptor.kind == PrimitiveKind.BOOLEAN -> {
writer.encodeOptionalBoolean(value as Boolean?)
encodeOptionalBoolean(descriptor, index, value)
}

value == null -> {
Expand All @@ -144,6 +147,26 @@ internal class CompositeBinaryEncoder(
}
}

private fun <T> encodeOptionalBoolean(
descriptor: SerialDescriptor,
index: Int,
value: T?
) {
val elementContext = ElementDeclarationContext(index, descriptor)
val optimizeBool = !elementContext.hasAnnotation<DisableOptionalBooleanOptimization>()

when {
optimizeBool -> writer.encodeOptionalBoolean(value as Boolean?)

value == null -> writer.writeByte(NULL_MARK)

else -> {
writer.writeByte(NOT_NULL_MARK)
ScaleCodecWriter.BOOL.write(writer, value as Boolean)
}
}
}

private fun unsupported(label: String): Nothing {
throw SerializationException("Encoding of $label is not supported")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.novasama.substrate_sdk_android.koltinx_serialization_scale.decode.binary

import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.annotations.DisableOptionalBooleanOptimization
import kotlinx.serialization.Serializable
import org.junit.Test

Expand Down Expand Up @@ -36,4 +37,14 @@ class OptionalBinaryDecodeTest : BinaryDecodeTest() {
runDecodeTest<TestData>(raw = byteArrayOf(0x01), expected = TestData(false))
runDecodeTest<TestData>(raw = byteArrayOf(0x02), expected = TestData(true))
}

@Test
fun `should decode optional boolean without optimization when annotated`() {
@Serializable
data class TestData(@DisableOptionalBooleanOptimization val a: Boolean?)

runDecodeTest<TestData>(raw = byteArrayOf(0x00), expected = TestData(null))
runDecodeTest<TestData>(raw = byteArrayOf(0x01, 0x00), expected = TestData(false))
runDecodeTest<TestData>(raw = byteArrayOf(0x01, 0x01), expected = TestData(true))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.novasama.substrate_sdk_android.koltinx_serialization_scale.encode.binary

import io.novasama.substrate_sdk_android.koltinx_serialization_scale.binary.annotations.DisableOptionalBooleanOptimization
import kotlinx.serialization.Serializable
import org.junit.Test

Expand Down Expand Up @@ -32,8 +33,18 @@ class OptionalBinaryEncodeTest : BinaryEncodeTest() {
@Serializable
data class TestData(val a: Boolean?)

// runEncodeTest<TestData>(value = TestData(null), expected = byteArrayOf(0x00))
runEncodeTest<TestData>(value = TestData(null), expected = byteArrayOf(0x00))
runEncodeTest<TestData>(value = TestData(false), expected = byteArrayOf(0x01))
// runEncodeTest<TestData>(value = TestData(true), expected = byteArrayOf(0x02))
runEncodeTest<TestData>(value = TestData(true), expected = byteArrayOf(0x02))
}

@Test
fun `should encode optional boolean without optimization when annotated`() {
@Serializable
data class TestData(@DisableOptionalBooleanOptimization val a: Boolean?)

runEncodeTest<TestData>(value = TestData(null), expected = byteArrayOf(0x00))
runEncodeTest<TestData>(value = TestData(false), expected = byteArrayOf(0x01, 0x00))
runEncodeTest<TestData>(value = TestData(true), expected = byteArrayOf(0x01, 0x01))
}
}
Loading