Skip to content

Commit

Permalink
feat(#9)!: remove inheritance between NotEmptySet and Set
Browse files Browse the repository at this point in the history
  • Loading branch information
LVMVRQUXL committed Dec 27, 2022
1 parent 89ab966 commit 5418edd
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 31 deletions.
2 changes: 2 additions & 0 deletions changelog.md
Expand Up @@ -41,3 +41,5 @@ The `AnyInt` hierarchy representing integers
([#37](https://github.com/kotools/libraries/issues/37)).
- Inheritance between the `NotEmptyList` and the `List` types
([#8](https://github.com/kotools/types/issues/8)).
- Inheritance between the `NotEmptySet` and the `Set` types
([#9](https://github.com/kotools/types/issues/9)).
64 changes: 39 additions & 25 deletions src/commonMain/kotlin/kotools/types/collection/NotEmptySet.kt
@@ -1,12 +1,17 @@
package kotools.types.collection

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotools.types.Package
import kotools.types.SinceKotoolsTypes
import kotools.types.text.NotBlankString
import kotools.types.text.toNotBlankString
import kotools.types.number.StrictlyPositiveInt
import kotools.types.number.toStrictlyPositiveInt
import kotools.types.toSuccessfulResult

/**
Expand All @@ -17,16 +22,21 @@ import kotools.types.toSuccessfulResult
*/
@Serializable(NotEmptySetSerializer::class)
@SinceKotoolsTypes("4.0")
public class NotEmptySet<out E>
private constructor(private val elements: Set<E>) : Set<E> by elements {
internal companion object {
infix fun <E> of(elements: Collection<E>): Result<NotEmptySet<E>> =
elements.takeIf(Collection<E>::isNotEmpty)
?.toSet()
?.toSuccessfulResult(::NotEmptySet)
?: Result.failure(EmptyCollectionException)
}
public class NotEmptySet<out E> internal constructor(
/** The first element of this set. */
public val head: E,
/** All elements of this set except the first one. */
public val tail: Set<E>
) {
/** All elements of this set. */
public val elements: Set<E> get() = setOf(head) + tail

/** The size of this set. */
public val size: StrictlyPositiveInt
get() = elements.size.toStrictlyPositiveInt()
.getOrThrow()

/** Returns the string representation of this set. */
override fun toString(): String = "$elements"
}

Expand All @@ -36,9 +46,8 @@ private constructor(private val elements: Set<E>) : Set<E> by elements {
*/
@SinceKotoolsTypes("4.0")
public fun <E> notEmptySetOf(head: E, vararg tail: E): NotEmptySet<E> {
val result: List<E> = listOf(head) + tail
return result.toNotEmptySet()
.getOrThrow()
val elements: Set<E> = setOf(head, *tail)
return NotEmptySet(head = elements.first(), tail = elements.drop(1).toSet())
}

/**
Expand All @@ -47,21 +56,26 @@ public fun <E> notEmptySetOf(head: E, vararg tail: E): NotEmptySet<E> {
*/
@SinceKotoolsTypes("4.0")
public fun <E> Collection<E>.toNotEmptySet(): Result<NotEmptySet<E>> =
NotEmptySet of this
takeIf(Collection<E>::isNotEmpty)
?.toSuccessfulResult { NotEmptySet(first(), drop(1).toSet()) }
?: Result.failure(EmptyCollectionException)

internal class NotEmptySetSerializer<E>(elementSerializer: KSerializer<E>) :
DelegatedSerializer<Set<E>, NotEmptySet<E>> {
override val delegate: KSerializer<Set<E>> by lazy {
SetSerializer(elementSerializer)
}
KSerializer<NotEmptySet<E>> {
private val delegate: KSerializer<Set<E>> = SetSerializer(elementSerializer)

override val serialName: Result<NotBlankString> by lazy(
"${Package.collection}.NotEmptySet"::toNotBlankString
@ExperimentalSerializationApi
override val descriptor: SerialDescriptor = SerialDescriptor(
"${Package.collection}.NotEmptySet",
delegate.descriptor
)

override val deserializationException: IllegalArgumentException by lazy {
EmptyCollectionException
}
override fun serialize(encoder: Encoder, value: NotEmptySet<E>): Unit =
encoder.encodeSerializableValue(delegate, value.elements)

override fun Set<E>.toResultOfB(): Result<NotEmptySet<E>> = toNotEmptySet()
override fun deserialize(decoder: Decoder): NotEmptySet<E> = decoder
.decodeSerializableValue(delegate)
.toNotEmptySet()
.getOrNull()
?: throw SerializationException(EmptyCollectionException)
}
45 changes: 39 additions & 6 deletions src/commonTest/kotlin/kotools/types/collection/NotEmptySetTest.kt
Expand Up @@ -8,13 +8,44 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotools.types.Package
import kotools.types.assertHasAMessage
import kotools.types.number.StrictlyPositiveInt
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class NotEmptySetTest {
@Test
fun head_should_return_the_first_element_of_this_set() {
val elements: Set<Int> = List(3) { Random.nextInt() }
.toSet()
val result: Int = elements.toNotEmptySet()
.getOrThrow()
.head
assertEquals(actual = result, expected = elements.first())
}

@Test
fun tail_should_return_all_elements_of_this_set_except_the_first_one() {
val elements: Set<Int> = List(3) { Random.nextInt() }
.toSet()
val result: Set<Int> = elements.toNotEmptySet()
.getOrThrow()
.tail
assertEquals(actual = result, expected = elements.drop(1).toSet())
}

@Test
fun size_should_return_the_size_of_this_set_as_a_StrictlyPositiveInt() {
val elements: Set<Int> = List(3) { Random.nextInt() }
.toSet()
val result: StrictlyPositiveInt = elements.toNotEmptySet()
.getOrThrow()
.size
assertEquals(actual = result.value, expected = elements.size)
}

@Test
fun toString_should_behave_like_a_Set() {
val elements: Set<Int> = List(8) { Random.nextInt() }
Expand All @@ -31,16 +62,18 @@ class NotEmptySetTest {
val tail: Array<Int> = List(7) { Random.nextInt() }
.toTypedArray()
val result: NotEmptySet<Int> = notEmptySetOf(head, *tail)
val expected: List<Int> = listOf(head) + tail
assertContentEquals(expected, result)
assertContentEquals(
actual = result.elements,
expected = listOf(head) + tail
)
}

@Test
fun collection_toNotEmptySet_should_pass_with_a_not_empty_Collection() {
val elements: List<Int> = List(8) { Random.nextInt() }
val result: NotEmptySet<Int> = elements.toNotEmptySet()
.getOrThrow()
assertContentEquals(elements, result)
assertContentEquals(actual = result.elements, expected = elements)
}

@Test
Expand Down Expand Up @@ -75,10 +108,10 @@ class NotEmptySetSerializerTest {

@Test
fun deserialization_should_pass_with_a_not_empty_Collection() {
val collection: Collection<Int> = List(8) { Random.nextInt() }
val encoded: String = Json.encodeToString(collection)
val elements: Collection<Int> = List(8) { Random.nextInt() }
val encoded: String = Json.encodeToString(elements)
val result: NotEmptySet<Int> = Json.decodeFromString(encoded)
assertContentEquals(collection, result)
assertContentEquals(actual = result.elements, expected = elements)
}

@Test
Expand Down

0 comments on commit 5418edd

Please sign in to comment.