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

support Kotlin/Wasm for apollo-adapters #5803

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion libraries/apollo-adapters/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
apolloLibrary(
namespace = "com.apollographql.apollo3.adapter",
withLinux = false,
withWasm = false
withWasm = true
)

kotlin {
Expand All @@ -22,5 +22,10 @@ kotlin {
implementation(npm("big.js", "5.2.2"))
}
}
findByName("wasmJsMain")?.apply {
dependencies {
implementation(npm("big.js", "5.2.2"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.apollographql.apollo3.adapter

@JsModule("big.js")
internal external fun Big(raw: JsAny): Big

@JsName("Number")
internal external fun jsNumber(raw: Big): JsNumber

internal external class Big {
fun plus(other: Big): Big
fun minus(other: Big): Big
fun times(other: Big): Big
fun div(other: Big): Big
fun cmp(other: Big): Int
fun eq(other: Big): Boolean
fun round(dp: Int, rm: Int): Big
}

actual class BigDecimal {
internal val raw: Big

internal constructor(raw: Big) {
this.raw = raw
}

constructor() : this(raw = Big())

actual constructor(strVal: String) : this(raw = Big(strVal.toJsString()))

actual constructor(doubleVal: Double) {
check(!doubleVal.isNaN() && !doubleVal.isInfinite())
raw = Big(doubleVal.toJsNumber())
}

actual constructor(intVal: Int) : this(raw = Big(intVal.toJsNumber()))

// JS does not support 64-bit integer natively.
actual constructor(longVal: Long) : this(raw = Big(longVal.toString().toJsString()))

actual fun add(augend: BigDecimal): BigDecimal = BigDecimal(raw = raw.plus(augend.raw))

actual fun subtract(subtrahend: BigDecimal): BigDecimal = BigDecimal(raw = raw.minus(subtrahend.raw))

actual fun multiply(multiplicand: BigDecimal): BigDecimal = BigDecimal(raw = raw.times(multiplicand.raw))

actual fun divide(divisor: BigDecimal): BigDecimal = BigDecimal(raw = raw.div(divisor.raw))

actual fun negate(): BigDecimal = BigDecimal().subtract(this)

actual fun signum(): Int {
return raw.cmp(Big())
}

fun toInt(): Int {
return jsNumber(raw).toInt()
}

fun toLong(): Long {
// JSNumber is double precision, so it cannot exactly represent 64-bit `Long`.
return toString().toLong()
}

fun toShort(): Short {
return jsNumber(raw).toInt().toShort()
}

fun toByte(): Byte {
return jsNumber(raw).toInt().toByte()
}

fun toChar(): Char {
return jsNumber(raw).toInt().toChar()
}

fun toDouble(): Double {
return jsNumber(raw).toDouble()
}

fun toFloat(): Float {
return jsNumber(raw).toDouble().toFloat()
}

override fun equals(other: Any?): Boolean {
if (other is BigDecimal) {
return raw.eq(other.raw)
}
return false
}

override fun hashCode(): Int = raw.toString().hashCode()

override fun toString(): String = raw.toString()
}

actual fun BigDecimal.toNumber(): Number {
val rounded = raw.round(0, 0)

return if (raw.minus(rounded).eq(Big())) {
toLong()
} else {
toDouble()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.apollographql.apollo3.adapter.test

import com.apollographql.apollo3.adapter.BigDecimal
import kotlin.test.*

class BigDecimalTests {
Copy link
Contributor

@martinbonnin martinbonnin Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those the exact same tests as for JS? If yes, I'll try to factor them in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, was wondering about that too (they are identical)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try. Also, I wonder why they are jsTest and not commonTest, I'll dig

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a commit to share the tests between JS and Wasm.

I tried moving to commonTest. Unfortunately, we still have behavioural discrepencies there :/...We should probably use a proper BigDecimal lib. Anyone with recommendations?

@Test
fun equality() {
assertEquals(BigDecimal("12345.12345678901234567890123"), BigDecimal("12345.12345678901234567890123"))
assertEquals(BigDecimal("987654321098765432109876543210"), BigDecimal("987654321098765432109876543210"))
assertEquals(BigDecimal("1024768"), BigDecimal("1024768"))
assertEquals(BigDecimal(-Double.MAX_VALUE), BigDecimal(-Double.MAX_VALUE))
assertEquals(BigDecimal(Double.MAX_VALUE), BigDecimal(Double.MAX_VALUE))
assertEquals(BigDecimal(-Long.MAX_VALUE - 1), BigDecimal(-Long.MAX_VALUE - 1))
assertEquals(BigDecimal(Long.MAX_VALUE), BigDecimal(Long.MAX_VALUE))
}

@Test
fun overflow() {
assertFails {
BigDecimal("987654321098765432109876543210").toLong()
}
}

@Test
fun truncating() {

assertEquals(
BigDecimal("12345.12345678901234567890123").toDouble(),
12345.123456789011
)
}

@Test
fun roundTrip_Int() {
val bridged = BigDecimal(Int.MAX_VALUE)
assertEquals(bridged.toInt(), Int.MAX_VALUE)

val bridgedNeg = BigDecimal( -Int.MAX_VALUE - 1)
assertEquals(bridgedNeg.toInt(), -Int.MAX_VALUE - 1)
}

@Test
fun roundTrip_Long() {
val bridged = BigDecimal(Long.MAX_VALUE)
assertEquals(bridged.toLong(), Long.MAX_VALUE)

val bridgedNeg = BigDecimal(-Long.MAX_VALUE - 1)
assertEquals(bridgedNeg.toLong(), -Long.MAX_VALUE - 1)
}

@Test
fun roundTrip_Double() {
val bridged = BigDecimal(Double.MAX_VALUE)
assertEquals(bridged.toDouble(), Double.MAX_VALUE)

val bridgedNeg = BigDecimal(-Double.MAX_VALUE)
assertEquals(bridgedNeg.toDouble(), -Double.MAX_VALUE)

assertFails {
BigDecimal(Double.POSITIVE_INFINITY)
}

assertFails {
BigDecimal(Double.NEGATIVE_INFINITY)
}

assertFails {
BigDecimal(Double.NaN)
}
}
}
Loading