Skip to content

Commit

Permalink
Merge pull request #46 from Liftric/feature/result_enhancements
Browse files Browse the repository at this point in the history
core/Result: functional programming addition
  • Loading branch information
benjohnde authored Feb 21, 2022
2 parents fe0e2a9 + 22ea971 commit 2ecadda
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Build testapp and test request (iOS)
run: |
cd iostests
xcodebuild clean test -project TestApp.xcodeproj -scheme TestApp -destination "platform=iOS Simulator,OS=15.0,name=iPhone 12" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO region=${{ secrets.region }} clientId=${{ secrets.clientid }}
xcodebuild clean test -project TestApp.xcodeproj -scheme TestApp -destination "platform=iOS Simulator,OS=15.2,name=iPhone 12" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO region=${{ secrets.region }} clientId=${{ secrets.clientid }}
- name: Upload test result
if: ${{ always() }}
uses: actions/upload-artifact@v2
Expand Down
25 changes: 24 additions & 1 deletion src/commonMain/kotlin/com/liftric/cognito/idp/core/Result.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ class Result<out T> constructor(val value: Any?) {
companion object {
fun <T> success(value: T): Result<T> = Result(value)
fun <T> failure(exception: Throwable): Result<T> = Result(Failure(exception))

/**
* [Result] builder function. Returns Success if [mightFail] doesn't, otherwise returns
* [mightFail] exception as [Failure]
*/
fun <T> doTry(mightFail: () -> T) : Result<T> = try {
success(mightFail())
} catch (e: Throwable){
failure(e)
}
}

class Failure(val exception: Throwable) {
Expand Down Expand Up @@ -54,6 +64,19 @@ class Result<out T> constructor(val value: Any?) {
}
}

/**
* Executes the [onSuccess] mapping if Success, or re-wraps the Failure doing nothing
*
* This can be used to pipe transform the result only if it is successful.
* A happy path of results can be modelled with this.
*
* Would be called foldRight if [Result] would be called Either<Throwable,T> ;)
*/
fun <R> andThen(onSuccess: (value: T) -> R): Result<R> = when(value) {
is Failure -> failure(value.exception)
else -> doTry { onSuccess(value as T) }
}

override fun toString(): String =
when (value) {
is Failure -> value.toString()
Expand Down Expand Up @@ -92,4 +115,4 @@ inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
} catch (e: Throwable) {
Result.failure(e)
}
}
}
48 changes: 47 additions & 1 deletion src/commonTest/kotlin/com/liftric/cognito/idp/ResultTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,52 @@ class ResultTests {
assertEquals(expected, result)
}

@Test
fun testDoTryAndFold() {
Result.doTry { "1" }
.andThen { "${it}2" }
.andThen { it.toInt() }
.andThen { it * 2 }
.fold(onFailure = {fail("shouldn't fail!")}, onSuccess = { assertEquals(24, it) })
}

@Test
fun testAndThenOnSuccess() {
var result: String? = null
Result.success("Hans")
.andThen { "$it Wurst" }
.fold(
onSuccess = {
result = it
},
onFailure = {
fail("onFailure() should not get called")
}
)
assertEquals("Hans Wurst", result)
}

@Test
fun testAndThenOnFailure() {
val exception = Exception("firstException")
val failedAndThen = Result.failure<String>(exception)
.andThen {
Result.success("won't happen")
}

assertEquals(true, failedAndThen.isFailure)
assertEquals(exception, failedAndThen.exceptionOrNull())

Result.success("Test")
.andThen {
error("sad")
}.fold(onSuccess = {
fail("onSuccess() should not get called")
}, onFailure = {
assertEquals("sad" ,it.message)
})
}

@Test
fun testFoldOnFailure() {
val expected = IOException("No connectivity")
Expand Down Expand Up @@ -115,4 +161,4 @@ class ResultTests {
assertEquals(expected, result.exceptionOrNull())
assertNull(result.getOrNull())
}
}
}

0 comments on commit 2ecadda

Please sign in to comment.