Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into arrow-2
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev committed Jul 10, 2023
2 parents c3672f9 + 1d1d540 commit 094895b
Show file tree
Hide file tree
Showing 28 changed files with 902 additions and 161 deletions.
6 changes: 6 additions & 0 deletions arrow-libs/core/arrow-annotations/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ kotlin {
}

apply(from = property("ANIMALSNIFFER_MPP"))

tasks.jar {
manifest {
attributes["Automatic-Module-Name"] = "arrow.annotations"
}
}
6 changes: 6 additions & 0 deletions arrow-libs/core/arrow-atomic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ kotlin {
}
}
}

tasks.jar {
manifest {
attributes["Automatic-Module-Name"] = "arrow.atomic"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
public final class arrow/core/serialization/EitherSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Larrow/core/Either;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Larrow/core/Either;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public final class arrow/core/serialization/IorSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Larrow/core/Ior;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Larrow/core/Ior;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public final class arrow/core/serialization/NonEmptyListSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize-0-xjo5U (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/List;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize-EgRvm48 (Lkotlinx/serialization/encoding/Encoder;Ljava/util/List;)V
}

public final class arrow/core/serialization/NonEmptySetSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize-J9TPrxk (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Set;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize-EvCv4gE (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Set;)V
}

public final class arrow/core/serialization/OptionSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Larrow/core/Option;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Larrow/core/Option;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

50 changes: 50 additions & 0 deletions arrow-libs/core/arrow-core-serialization/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@file:Suppress("DSL_SCOPE_VIOLATION")

plugins {
id(libs.plugins.kotlin.multiplatform.get().pluginId)
alias(libs.plugins.arrowGradleConfig.kotlin)
alias(libs.plugins.arrowGradleConfig.publish)
alias(libs.plugins.arrowGradleConfig.versioning)
alias(libs.plugins.kotest.multiplatform)
id(libs.plugins.kotlinx.serialization.get().pluginId)
}

apply(from = property("ANIMALSNIFFER_MPP"))

val enableCompatibilityMetadataVariant =
providers.gradleProperty("kotlin.mpp.enableCompatibilityMetadataVariant")
.orNull?.toBoolean() == true

if (enableCompatibilityMetadataVariant) {
tasks.withType<Test>().configureEach {
exclude("**/*")
}
}

kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.arrowCore)
api(libs.kotlin.stdlibCommon)
api(libs.kotlinx.serializationCore)
}
}
if (!enableCompatibilityMetadataVariant) {
commonTest {
dependencies {
implementation(libs.kotlinx.serializationJson)
implementation(libs.kotest.frameworkEngine)
implementation(libs.kotest.assertionsCore)
implementation(libs.kotest.property)
}
}

jvmTest {
dependencies {
runtimeOnly(libs.kotest.runnerJUnit5)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package arrow.core.serialization

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.left
import arrow.core.none
import arrow.core.right
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.encoding.encodeStructure

public class EitherSerializer<A, B>(
private val errorSerializer: KSerializer<A>,
private val elementSerializer: KSerializer<B>,
) : KSerializer<Either<A, B>> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Either") {
element("left", errorSerializer.descriptor, isOptional = true)
element("right", elementSerializer.descriptor, isOptional = true)
}
override fun serialize(encoder: Encoder, value: Either<A, B>) {
encoder.encodeStructure(descriptor) {
when (value) {
is Either.Left -> encodeSerializableElement(descriptor, 0, errorSerializer, value.value)
is Either.Right -> encodeSerializableElement(descriptor, 1, elementSerializer, value.value)
}
}
}
override fun deserialize(decoder: Decoder): Either<A, B> {
var leftValue: Option<A> = none()
var rightValue: Option<B> = none()
decoder.decodeStructure(descriptor) {
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> {
leftValue = Some(decodeSerializableElement(descriptor, 0, errorSerializer))
}
1 -> {
rightValue = Some(decodeSerializableElement(descriptor, 1, elementSerializer))
}
CompositeDecoder.DECODE_DONE -> break
else -> error("unexpected index: $index")
}
}
}
return when {
leftValue is None && rightValue is None -> throw SerializationException("No information found for this Either")
leftValue is Some && rightValue is Some -> throw SerializationException("Both Left and Right specified for Either")
leftValue is Some -> (leftValue as Some<A>).value.left()
rightValue is Some -> (rightValue as Some<B>).value.right()
else -> error("this should never happen")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package arrow.core.serialization

import arrow.core.Ior
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.none
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.encoding.encodeStructure

public class IorSerializer<A, B>(
private val errorSerializer: KSerializer<A>,
private val elementSerializer: KSerializer<B>,
) : KSerializer<Ior<A, B>> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Ior") {
element("left", errorSerializer.descriptor, isOptional = true)
element("right", elementSerializer.descriptor, isOptional = true)
}
override fun serialize(encoder: Encoder, value: Ior<A, B>) {
encoder.encodeStructure(descriptor) {
when (value) {
is Ior.Left -> encodeSerializableElement(descriptor, 0, errorSerializer, value.value)
is Ior.Right -> encodeSerializableElement(descriptor, 1, elementSerializer, value.value)
is Ior.Both -> {
encodeSerializableElement(descriptor, 0, errorSerializer, value.leftValue)
encodeSerializableElement(descriptor, 1, elementSerializer, value.rightValue)
}
}
}
}
override fun deserialize(decoder: Decoder): Ior<A, B> {
var leftValue: Option<A> = none()
var rightValue: Option<B> = none()
decoder.decodeStructure(descriptor) {
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> {
leftValue = Some(decodeSerializableElement(descriptor, 0, errorSerializer))
}
1 -> {
rightValue = Some(decodeSerializableElement(descriptor, 1, elementSerializer))
}
CompositeDecoder.DECODE_DONE -> break
else -> error("unexpected index: $index")
}
}
}
return when {
leftValue is None && rightValue is None -> throw SerializationException("No information found for this Ior")
leftValue is Some && rightValue is Some -> Ior.Both((leftValue as Some<A>).value, (rightValue as Some<B>).value)
leftValue is Some -> Ior.Left((leftValue as Some<A>).value)
rightValue is Some -> Ior.Right((rightValue as Some<B>).value)
else -> error("this should never happen")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package arrow.core.serialization

import arrow.core.NonEmptyList
import arrow.core.NonEmptySet
import arrow.core.toNonEmptyListOrNull
import arrow.core.toNonEmptySetOrNull
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

public class NonEmptyListSerializer<T>(
elementSerializer: KSerializer<T>
) : KSerializer<NonEmptyList<T>> {
private val listSerializer: KSerializer<List<T>> = ListSerializer(elementSerializer)

@OptIn(ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor =
SerialDescriptor("NonEmptyList", listSerializer.descriptor)
override fun serialize(encoder: Encoder, value: NonEmptyList<T>) {
listSerializer.serialize(encoder, value.toList())
}
override fun deserialize(decoder: Decoder): NonEmptyList<T> =
listSerializer.deserialize(decoder).toNonEmptyListOrNull()
?: throw SerializationException("expected non-empty list")
}

public class NonEmptySetSerializer<T>(
elementSerializer: KSerializer<T>
) : KSerializer<NonEmptySet<T>> {
private val setSerializer: KSerializer<Set<T>> = SetSerializer(elementSerializer)

@OptIn(ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor =
SerialDescriptor("NonEmptySet", setSerializer.descriptor)
override fun serialize(encoder: Encoder, value: NonEmptySet<T>) {
setSerializer.serialize(encoder, value.toSet())
}
override fun deserialize(decoder: Decoder): NonEmptySet<T> =
setSerializer.deserialize(decoder).toNonEmptySetOrNull()
?: throw SerializationException("expected non-empty set")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package arrow.core.serialization

import arrow.core.Option
import arrow.core.toOption
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

public class OptionSerializer<T : Any>(
elementSerializer: KSerializer<T>
) : KSerializer<Option<T>> {
private val nullableSerializer: KSerializer<T?> = elementSerializer.nullable

override val descriptor: SerialDescriptor = nullableSerializer.descriptor
override fun serialize(encoder: Encoder, value: Option<T>) {
nullableSerializer.serialize(encoder, value.getOrNull())
}
override fun deserialize(decoder: Decoder): Option<T> =
nullableSerializer.deserialize(decoder).toOption()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@file:UseSerializers(
EitherSerializer::class,
IorSerializer::class,
OptionSerializer::class,
NonEmptyListSerializer::class,
NonEmptySetSerializer::class
)

package arrow.core.serialization

import arrow.core.Either
import arrow.core.Ior
import arrow.core.NonEmptyList
import arrow.core.NonEmptySet
import arrow.core.Option
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.Arb
import io.kotest.property.checkAll
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import io.kotest.matchers.shouldBe
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.map
import io.kotest.property.arbitrary.string
import kotlinx.serialization.Serializable

/*
These types are needed to "trick" the kotlinx.serialization plug-in
to use the corresponding (de)serializers for those types.
*/

@Serializable
data class EitherInside<A, B>(val thing: Either<A, B>)

@Serializable
data class IorInside<A, B>(val thing: Ior<A, B>)

@Serializable
data class OptionInside<A>(val thing: Option<A>)

@Serializable
data class NonEmptyListInside<A>(val thing: NonEmptyList<A>)

@Serializable
data class NonEmptySetInside<A>(val thing: NonEmptySet<A>)

inline fun <reified T> StringSpec.backAgain(generator: Arb<T>) {
"there and back again, ${T::class.simpleName}" {
checkAll(generator) { e ->
val result = Json.encodeToJsonElement<T>(e)
val back = Json.decodeFromJsonElement<T>(result)
back shouldBe e
}
}
}

/**
* Checks that the result of serializing a value into JSON,
* and then deserializing it, gives back the original.
*/
class BackAgainTest : StringSpec({
backAgain(Arb.either(Arb.string(), Arb.int()).map(::EitherInside))
backAgain(Arb.ior(Arb.string(), Arb.int()).map(::IorInside))
backAgain(Arb.option(Arb.string()).map(::OptionInside))
backAgain(Arb.nonEmptyList(Arb.int()).map(::NonEmptyListInside))
backAgain(Arb.nonEmptySet(Arb.int()).map(::NonEmptySetInside))
})
Loading

0 comments on commit 094895b

Please sign in to comment.