mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 12:30:00 +03:00
Add BaseSurrogateSerializer (#120)
This commit is contained in:
parent
69a4eef68f
commit
3fddc3d285
3 changed files with 150 additions and 36 deletions
|
@ -1,10 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseSurrogateSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
private const val KDF_TYPE_ARGON2_ID = 1
|
private const val KDF_TYPE_ARGON2_ID = 1
|
||||||
private const val KDF_TYPE_PBKDF2_SHA256 = 0
|
private const val KDF_TYPE_PBKDF2_SHA256 = 0
|
||||||
|
@ -40,38 +37,37 @@ data class PreLoginResponseJson(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PreLoginResponseSerializer : KSerializer<PreLoginResponseJson> {
|
private class PreLoginResponseSerializer :
|
||||||
|
BaseSurrogateSerializer<PreLoginResponseJson, InternalPreLoginResponseJson>() {
|
||||||
|
|
||||||
private val surrogateSerializer = InternalPreLoginResponseJson.serializer()
|
override val surrogateSerializer = InternalPreLoginResponseJson.serializer()
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor = surrogateSerializer.descriptor
|
override fun InternalPreLoginResponseJson.toExternalType(): PreLoginResponseJson =
|
||||||
|
PreLoginResponseJson(
|
||||||
|
kdfParams = when (this.kdfType) {
|
||||||
|
KDF_TYPE_PBKDF2_SHA256 -> {
|
||||||
|
PreLoginResponseJson.KdfParams.Pbkdf2(
|
||||||
|
iterations = this.kdfIterations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): PreLoginResponseJson {
|
KDF_TYPE_ARGON2_ID -> {
|
||||||
val surrogate = decoder.decodeSerializableValue(surrogateSerializer)
|
@Suppress("UnsafeCallOnNullableType")
|
||||||
val kdfParams = when (surrogate.kdfType) {
|
PreLoginResponseJson.KdfParams.Argon2ID(
|
||||||
KDF_TYPE_PBKDF2_SHA256 -> {
|
iterations = this.kdfIterations,
|
||||||
PreLoginResponseJson.KdfParams.Pbkdf2(
|
memory = this.kdfMemory!!,
|
||||||
iterations = surrogate.kdfIterations,
|
parallelism = this.kdfParallelism!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalStateException(
|
||||||
|
"Unable to parse KDF params for unknown kdfType: ${this.kdfType}",
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
|
||||||
KDF_TYPE_ARGON2_ID -> {
|
override fun PreLoginResponseJson.toSurrogateType(): InternalPreLoginResponseJson =
|
||||||
PreLoginResponseJson.KdfParams.Argon2ID(
|
when (val params = this.kdfParams) {
|
||||||
iterations = surrogate.kdfIterations,
|
|
||||||
memory = surrogate.kdfMemory!!,
|
|
||||||
parallelism = surrogate.kdfParallelism!!,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw IllegalStateException(
|
|
||||||
"Unable to parse KDF params for unknown kdfType: ${surrogate.kdfType}",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return PreLoginResponseJson(kdfParams = kdfParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: PreLoginResponseJson) {
|
|
||||||
val surrogate = when (val params = value.kdfParams) {
|
|
||||||
is PreLoginResponseJson.KdfParams.Argon2ID -> {
|
is PreLoginResponseJson.KdfParams.Argon2ID -> {
|
||||||
InternalPreLoginResponseJson(
|
InternalPreLoginResponseJson(
|
||||||
kdfType = KDF_TYPE_ARGON2_ID,
|
kdfType = KDF_TYPE_ARGON2_ID,
|
||||||
|
@ -88,9 +84,4 @@ private class PreLoginResponseSerializer : KSerializer<PreLoginResponseJson> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
encoder.encodeSerializableValue(
|
|
||||||
surrogateSerializer,
|
|
||||||
surrogate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class that simplifies the process of providing a "surrogate" [KSerializer]. These are
|
||||||
|
* used to provide mappings between an "internal" type [R] (the "surrogate") to an external type
|
||||||
|
* [T].
|
||||||
|
*
|
||||||
|
* See the [official surrogate documentation](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#composite-serializer-via-surrogate)
|
||||||
|
* for details.
|
||||||
|
*/
|
||||||
|
abstract class BaseSurrogateSerializer<T, R> : KSerializer<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [KSerializer] naturally associated with the type [R], which is a typical class annotated
|
||||||
|
* with [Serializable].
|
||||||
|
*/
|
||||||
|
abstract val surrogateSerializer: KSerializer<R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A conversion from the internal/surrogate type [R] to external type [T].
|
||||||
|
*/
|
||||||
|
abstract fun R.toExternalType(): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A conversion from the external type [T] to the internal/surrogate type [R].
|
||||||
|
*/
|
||||||
|
abstract fun T.toSurrogateType(): R
|
||||||
|
|
||||||
|
//region KSerializer overrides
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = surrogateSerializer.descriptor
|
||||||
|
|
||||||
|
final override fun deserialize(decoder: Decoder): T =
|
||||||
|
decoder
|
||||||
|
.decodeSerializableValue(surrogateSerializer)
|
||||||
|
.toExternalType()
|
||||||
|
|
||||||
|
final override fun serialize(encoder: Encoder, value: T) {
|
||||||
|
encoder.encodeSerializableValue(
|
||||||
|
surrogateSerializer,
|
||||||
|
value.toSurrogateType(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion KSerializer overrides
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class BaseSurrogateSerializerTest {
|
||||||
|
private val json = Json
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `properly deserializes raw JSON to the external model`() {
|
||||||
|
assertEquals(
|
||||||
|
ExternalData(
|
||||||
|
dataAsInt = 100,
|
||||||
|
),
|
||||||
|
json.decodeFromString<ExternalData>(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"dataAsString": "100"
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `properly serializes external model back to raw JSON`() {
|
||||||
|
assertEquals(
|
||||||
|
json.parseToJsonElement(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"dataAsString": "100"
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
json.encodeToJsonElement(
|
||||||
|
ExternalData(
|
||||||
|
dataAsInt = 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data class InternalData(
|
||||||
|
val dataAsString: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable(TestSurrogateSerializer::class)
|
||||||
|
private data class ExternalData(
|
||||||
|
val dataAsInt: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
private class TestSurrogateSerializer : BaseSurrogateSerializer<ExternalData, InternalData>() {
|
||||||
|
override val surrogateSerializer: KSerializer<InternalData>
|
||||||
|
get() = InternalData.serializer()
|
||||||
|
|
||||||
|
override fun InternalData.toExternalType(): ExternalData =
|
||||||
|
ExternalData(
|
||||||
|
dataAsInt = this.dataAsString.toInt(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun ExternalData.toSurrogateType(): InternalData =
|
||||||
|
InternalData(
|
||||||
|
dataAsString = this.dataAsInt.toString(),
|
||||||
|
)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue