Skip to content

Commit

Permalink
change: enforce serializability at runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanbarrossilva committed Aug 19, 2023
1 parent 3bfd12c commit d2da1a3
Show file tree
Hide file tree
Showing 26 changed files with 110 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package com.jeanbarrossilva.loadable.list

import com.jeanbarrossilva.loadable.Loadable
import com.jeanbarrossilva.loadable.map
import java.io.Serializable

/**
* Returns the first element of the [SerializableList] to match the given [predicate], wrapped by a
* [Loadable] matching the state of this [ListLoadable].
*
* @param predicate Condition to which the element to be found should conform.
**/
fun <T : Serializable?> ListLoadable<T>.find(predicate: (T) -> Boolean): Loadable<T?> {
fun <T> ListLoadable<T>.find(predicate: (T) -> Boolean): Loadable<T?> {
return toLoadable().map { content ->
content.find(predicate)
}
Expand All @@ -22,7 +21,7 @@ fun <T : Serializable?> ListLoadable<T>.find(predicate: (T) -> Boolean): Loadabl
*
* @param transform Transformation to be made to the [SerializableList].
**/
fun <I : Serializable?, O> ListLoadable<I>.ifPopulated(transform: SerializableList<I>.() -> O): O? {
fun <I, O> ListLoadable<I>.ifPopulated(transform: SerializableList<I>.() -> O): O? {
return if (this is ListLoadable.Populated) content.transform() else null
}

Expand All @@ -34,9 +33,7 @@ fun <I : Serializable?, O> ListLoadable<I>.ifPopulated(transform: SerializableLi
* @param transform Transformation to be made to the elements of the
* [populated][ListLoadable.Populated] [SerializableList].
**/
inline fun <I : Serializable?, reified O : Serializable?> ListLoadable<I>.mapNotNull(
transform: (I) -> O?
): ListLoadable<O> {
inline fun <I, reified O> ListLoadable<I>.mapNotNull(transform: (I) -> O?): ListLoadable<O> {
return when (this) {
is ListLoadable.Loading ->
ListLoadable.Loading()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.jeanbarrossilva.loadable.list

import com.jeanbarrossilva.loadable.Loadable
import java.io.Serializable

/**
* [Loadable]-like structure for representing different stages of an asynchronously-loaded
* [SerializableList], as well as its "population" state.
**/
sealed interface ListLoadable<T : Serializable?> {
sealed interface ListLoadable<T> {
/** Whether this [ListLoadable] has been successfully loaded. **/
val isLoaded: Boolean

/** Stage in which the [SerializableList] is loading. **/
class Loading<T : Serializable?> : ListLoadable<T> {
class Loading<T> : ListLoadable<T> {
override val isLoaded = false

override fun toLoadable(): Loadable<SerializableList<T>> {
Expand All @@ -21,7 +20,7 @@ sealed interface ListLoadable<T : Serializable?> {
}

/** Stage in which the [SerializableList] has been loaded but it's empty. **/
class Empty<T : Serializable?> : ListLoadable<T> {
class Empty<T> : ListLoadable<T> {
override val isLoaded = true

override fun toLoadable(): Loadable<SerializableList<T>> {
Expand All @@ -37,7 +36,7 @@ sealed interface ListLoadable<T : Serializable?> {
* @throws IllegalArgumentException If [content] is empty.
*/
@JvmInline
value class Populated<T : Serializable?>(val content: SerializableList<T>) : ListLoadable<T> {
value class Populated<T>(val content: SerializableList<T>) : ListLoadable<T> {
override val isLoaded
get() = true

Expand All @@ -58,7 +57,7 @@ sealed interface ListLoadable<T : Serializable?> {
* @param error [Throwable] that's been thrown while trying to load the [SerializableList].
**/
@JvmInline
value class Failed<T : Serializable?>(val error: Throwable) : ListLoadable<T> {
value class Failed<T>(val error: Throwable) : ListLoadable<T> {
override val isLoaded
get() = false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.jeanbarrossilva.loadable.list

import java.io.Serializable

/** Scope through which [ListLoadable]s are sent. **/
abstract class ListLoadableScope<T : Serializable?> internal constructor() {
abstract class ListLoadableScope<T> internal constructor() {
/** Sends a [ListLoadable.Loading]. **/
suspend fun load() {
send(ListLoadable.Loading())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.jeanbarrossilva.loadable.list

import com.jeanbarrossilva.loadable.Loadable
import java.io.Serializable

/** Converts this [Loadable] into a [ListLoadable]. **/
fun <T : Serializable?> Loadable<SerializableList<T>>.toListLoadable(): ListLoadable<T> {
fun <T> Loadable<SerializableList<T>>.toListLoadable(): ListLoadable<T> {
return when (this) {
is Loadable.Loading -> ListLoadable.Loading()
is Loadable.Loaded -> content.toListLoadable()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.jeanbarrossilva.loadable.list

import java.io.Serializable

/** Converts this [SerializableList] into a [ListLoadable]. **/
fun <T : Serializable?> SerializableList<T>.toListLoadable(): ListLoadable<T> {
fun <T> SerializableList<T>.toListLoadable(): ListLoadable<T> {
return if (isEmpty()) ListLoadable.Empty() else ListLoadable.Populated(this)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package com.jeanbarrossilva.loadable.list

import com.jeanbarrossilva.loadable.requireSerializable
import java.io.NotSerializableException
import java.io.Serializable

/**
* [List] that conforms to [Serializable].
*
* @param elements Instances of [T] contained in this [SerializableList].
* @throws NotSerializableException If any of the [elements] cannot be serialized.
**/
@JvmInline
value class SerializableList<T> internal constructor(private val elements: List<T>) :
List<T> by elements, Serializable
value class SerializableList<T>
@Throws(NotSerializableException::class)
internal constructor(private val elements: List<T>) : List<T> by elements, Serializable {
init {
forEach(::requireSerializable)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package com.jeanbarrossilva.loadable.list.flow

import com.jeanbarrossilva.loadable.list.ListLoadable
import com.jeanbarrossilva.loadable.list.ListLoadableScope
import java.io.Serializable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow

/** Returns a [Flow] containing only non-[loading][ListLoadable.Loading] values. **/
fun <T : Serializable?> Flow<ListLoadable<T>>.filterNotLoading(): Flow<ListLoadable<T>> {
fun <T> Flow<ListLoadable<T>>.filterNotLoading(): Flow<ListLoadable<T>> {
return filterNot {
it is ListLoadable.Loading
}
Expand All @@ -22,8 +21,7 @@ fun <T : Serializable?> Flow<ListLoadable<T>>.filterNotLoading(): Flow<ListLoada
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting
* [ListLoadable]s sent to it to the created [Flow].
**/
fun <T : Serializable?> listLoadableFlow(load: suspend ListLoadableScope<T>.() -> Unit):
Flow<ListLoadable<T>> {
fun <T> listLoadableFlow(load: suspend ListLoadableScope<T>.() -> Unit): Flow<ListLoadable<T>> {
return emptyListLoadableFlow {
load()
load.invoke(this)
Expand All @@ -37,9 +35,8 @@ fun <T : Serializable?> listLoadableFlow(load: suspend ListLoadableScope<T>.() -
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting
* [ListLoadable]s sent to it to the created [Flow].
**/
internal fun <T : Serializable?> emptyListLoadableFlow(
load: suspend ListLoadableScope<T>.() -> Unit
): Flow<ListLoadable<T>> {
internal fun <T> emptyListLoadableFlow(load: suspend ListLoadableScope<T>.() -> Unit):
Flow<ListLoadable<T>> {
return flow<ListLoadable<T>> {
FlowCollectorListLoadableScope(this).apply {
load.invoke(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package com.jeanbarrossilva.loadable.list.flow

import com.jeanbarrossilva.loadable.list.ListLoadable
import com.jeanbarrossilva.loadable.list.ListLoadableScope
import java.io.Serializable
import kotlinx.coroutines.flow.FlowCollector

/**
* [ListLoadableScope] that emits sent [ListLoadable]s to the given [collector].
*
* @param collector [FlowCollector] to which sent [ListLoadable]s will be emitted.
**/
internal class FlowCollectorListLoadableScope<T : Serializable?>(
internal class FlowCollectorListLoadableScope<T>(
private val collector: FlowCollector<ListLoadable<T>>
) : ListLoadableScope<T>() {
override suspend fun send(listLoadable: ListLoadable<T>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import com.jeanbarrossilva.loadable.list.ListLoadable
import com.jeanbarrossilva.loadable.list.SerializableList
import com.jeanbarrossilva.loadable.list.serializableListOf
import com.jeanbarrossilva.loadable.list.toListLoadable
import java.io.Serializable
import kotlinx.coroutines.flow.MutableStateFlow

/** Creates a [MutableStateFlow] with a [ListLoadable.Loading] as its initial value. **/
fun <T : Serializable?> listLoadableFlow(): MutableStateFlow<ListLoadable<T>> {
fun <T> listLoadableFlow(): MutableStateFlow<ListLoadable<T>> {
return MutableStateFlow(ListLoadable.Loading())
}

Expand All @@ -18,7 +17,7 @@ fun <T : Serializable?> listLoadableFlow(): MutableStateFlow<ListLoadable<T>> {
*
* @param content [Array] from which the [ListLoadable] will be created.
**/
fun <T : Serializable?> listLoadableFlowOf(vararg content: T): MutableStateFlow<ListLoadable<T>> {
fun <T> listLoadableFlowOf(vararg content: T): MutableStateFlow<ListLoadable<T>> {
return listLoadableFlowOf(serializableListOf(*content))
}

Expand All @@ -28,7 +27,6 @@ fun <T : Serializable?> listLoadableFlowOf(vararg content: T): MutableStateFlow<
*
* @param content [SerializableList] from which the [ListLoadable] will be created.
**/
fun <T : Serializable?> listLoadableFlowOf(content: SerializableList<T>):
MutableStateFlow<ListLoadable<T>> {
fun <T> listLoadableFlowOf(content: SerializableList<T>): MutableStateFlow<ListLoadable<T>> {
return MutableStateFlow(content.toListLoadable())
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.jeanbarrossilva.loadable.list.ListLoadable
import com.jeanbarrossilva.loadable.list.ListLoadableScope
import com.jeanbarrossilva.loadable.list.SerializableList
import com.jeanbarrossilva.loadable.list.toListLoadable
import java.io.Serializable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
Expand All @@ -25,7 +24,7 @@ import kotlinx.coroutines.launch
* [value][StateFlow.value] will be shared.
* @param sharingStarted Strategy for controlling when sharing starts and ends.
**/
fun <T : Serializable?> Flow<SerializableList<T>>.listLoadable(
fun <T> Flow<SerializableList<T>>.listLoadable(
coroutineScope: CoroutineScope,
sharingStarted: SharingStarted
): StateFlow<ListLoadable<T>> {
Expand All @@ -46,7 +45,7 @@ fun <T : Serializable?> Flow<SerializableList<T>>.listLoadable(
* @param load Operations to be made on the [ListLoadableScope] responsible for emitting
* [ListLoadable]s sent to it to the created [StateFlow].
**/
fun <T : Serializable?> listLoadableFlow(
fun <T> listLoadableFlow(
coroutineScope: CoroutineScope,
load: suspend ListLoadableScope<T>.() -> Unit
): StateFlow<ListLoadable<T>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.jeanbarrossilva.loadable.list

import java.io.Serializable
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
Expand All @@ -22,7 +21,7 @@ internal class SerializableListExtensions {

@Test
fun `GIVEN a SerializableList created through emptySerializableList WHEN checking if it's empty THEN it is`() { // ktlint-disable max-line-length
assertContentEquals(emptyList(), emptySerializableList<Serializable>())
assertContentEquals(emptyList(), emptySerializableList<Any?>())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.google.accompanist.placeholder.placeholder
import com.jeanbarrossilva.loadable.Loadable
import com.jeanbarrossilva.loadable.ifLoaded
import com.jeanbarrossilva.loadable.placeholder.test.Loading
import java.io.Serializable

/** Default values of a [Placeholder]. **/
object PlaceholderDefaults {
Expand All @@ -41,7 +40,7 @@ object PlaceholderDefaults {
* @param content Content that's shown if the [loadable] is [loaded][Loadable.Loaded].
**/
@Composable
fun <T : Serializable?> Placeholder(
fun <T> Placeholder(
loadable: Loadable<T>,
modifier: Modifier = Modifier,
shape: Shape = PlaceholderDefaults.shape,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal class PlaceholderTests {
@Test
fun isNotLoadingWhenLoadableIsFailed() {
composeRule.setContent {
Placeholder(Loadable.Failed(Exception()), Modifier.tagAsPlaceholder()) {
Placeholder(Loadable.Failed<Any?>(Exception()), Modifier.tagAsPlaceholder()) {
}
}
composeRule.onPlaceholder().assertIsNotLoading()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jeanbarrossilva.loadable

import java.io.Serializable
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream

/**
* Converts this into a [Loadable].
Expand All @@ -13,11 +14,26 @@ import java.io.Serializable
* - `null` (with [T] being a non-`null` type), [Loadable.Loading];
* - of a type other than [T]`?`, `null`.
**/
inline fun <reified T : Serializable?> Any?.loadable(): Loadable<T>? {
inline fun <reified T> Any?.loadable(): Loadable<T>? {
return when (this) {
is Throwable -> Loadable.Failed(this)
is T -> Loadable.Loaded(this)
null -> Loadable.Loading()
else -> null
}
}

/**
* Requires the [value] to be serializable.
*
* @param value Object whose serialization capability will be required.
* @return The [value] itself.
**/
fun <T> requireSerializable(value: T): T {
ByteArrayOutputStream().use { byteArrayOutputStream ->
ObjectOutputStream(byteArrayOutputStream).use { objectOutputStream ->
objectOutputStream.writeObject(value)
}
}
return value
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.jeanbarrossilva.loadable

import java.io.Serializable

/**
* [Content][Loadable.Loaded.content] of the given [Loadable] if it's [loaded][Loadable.Loaded];
* otherwise, `null`.
**/
inline val <T : Serializable?> Loadable<T>.contentOrNull
inline val <T> Loadable<T>.contentOrNull
get() = ifLoaded { this }

/**
Expand All @@ -16,7 +14,7 @@ inline val <T : Serializable?> Loadable<T>.contentOrNull
* @param operation Callback to be run on the [loaded][Loadable.Loaded]
* [content][Loadable.Loaded.content].
**/
inline fun <I : Serializable?, O> Loadable<I>.ifLoaded(operation: I.() -> O): O? {
inline fun <I, O> Loadable<I>.ifLoaded(operation: I.() -> O): O? {
return if (this is Loadable.Loaded) content.operation() else null
}

Expand All @@ -30,8 +28,7 @@ inline fun <I : Serializable?, O> Loadable<I>.ifLoaded(operation: I.() -> O): O?
* @param transform Transformation to be done to the [loaded][Loadable.Loaded]
* [content][Loadable.Loaded.content].
**/
inline fun <I : Serializable?, O : Serializable?> Loadable<I>.map(transform: (I) -> O):
Loadable<O> {
inline fun <I, O> Loadable<I>.map(transform: (I) -> O): Loadable<O> {
return when (this) {
is Loadable.Loading -> Loadable.Loading()
is Loadable.Loaded -> Loadable.Loaded(transform(content))
Expand Down
Loading

0 comments on commit d2da1a3

Please sign in to comment.