Skip to content

Commit 5fc394d

Browse files
committed
improvements and (a lot of) fixes
1 parent 9e1104c commit 5fc394d

File tree

217 files changed

+3465
-496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+3465
-496
lines changed

arrow/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,18 @@ val kotlinCompileAttributes: KotlinCompile.() -> Unit = {
3737
sourceCompatibility = "11"
3838
targetCompatibility = "11"
3939

40-
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
40+
kotlinOptions {
41+
jvmTarget = "11"
42+
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
43+
}
4144
}
4245

4346
tasks.compileKotlin.configure(kotlinCompileAttributes)
4447
tasks.compileTestKotlin.configure(kotlinCompileAttributes)
4548

4649
dependencies {
4750
implementation(project(":core"))
48-
implementation(project(":kotlin"))
51+
api(project(":kotlin"))
4952
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
5053
api("io.arrow-kt:arrow-core-jvm:1.1.2")
5154
api("io.arrow-kt:arrow-fx-coroutines:1.1.2")

arrow/src/main/kotlin/com/github/ljtfreitas/julian/k/arrow/EitherResponseT.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import com.github.ljtfreitas.julian.Promise
3232
import com.github.ljtfreitas.julian.Response
3333
import com.github.ljtfreitas.julian.ResponseFn
3434
import com.github.ljtfreitas.julian.ResponseT
35+
import java.lang.reflect.Type
36+
import java.lang.reflect.WildcardType
3537

3638
object EitherResponseT : ResponseT<Any, Either<Throwable, Any>> {
3739

@@ -46,13 +48,19 @@ object EitherResponseT : ResponseT<Any, Either<Throwable, Any>> {
4648

4749
override fun adapted(endpoint: Endpoint): JavaType = endpoint.returnType().parameterized()
4850
.map { it.actualTypeArguments[1] }
51+
.map(::argument)
4952
.orElseGet { Any::class.java }
5053
.let(JavaType::valueOf)
5154

55+
private fun argument(type: Type) : Type =
56+
if (type is WildcardType && type.upperBounds.isNotEmpty())
57+
argument(type.upperBounds.first())
58+
else type
59+
5260
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Any>) = object : ResponseFn<A, Either<Throwable, Any>> {
5361

5462
@Suppress("UNCHECKED_CAST")
55-
override fun run(response: Promise<out Response<A>>, arguments: Arguments): Promise<Either<Throwable, Any>> {
63+
override fun run(response: Promise<out Response<A, out Throwable>>, arguments: Arguments): Promise<Either<Throwable, Any>> {
5664
val leftClassType: Class<out Any> = JavaType.valueOf(endpoint.returnType().parameterized()
5765
.map(JavaType.Parameterized::firstArg)
5866
.orElse(Throwable::class.java))

arrow/src/main/kotlin/com/github/ljtfreitas/julian/k/arrow/EvalResponseT.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,27 @@ import com.github.ljtfreitas.julian.Promise
3030
import com.github.ljtfreitas.julian.Response
3131
import com.github.ljtfreitas.julian.ResponseFn
3232
import com.github.ljtfreitas.julian.ResponseT
33+
import java.lang.reflect.Type
34+
import java.lang.reflect.WildcardType
3335

3436
object EvalResponseT : ResponseT<Any, Eval<Any>> {
3537

3638
override fun test(endpoint: Endpoint) = endpoint.returnType().`is`(Eval::class.java)
3739

3840
override fun adapted(endpoint: Endpoint): JavaType = endpoint.returnType().parameterized()
3941
.map(JavaType.Parameterized::firstArg)
42+
.map(::argument)
4043
.orElseGet { Any::class.java }
4144
.let(JavaType::valueOf)
4245

46+
private fun argument(type: Type) : Type =
47+
if (type is WildcardType && type.upperBounds.isNotEmpty())
48+
argument(type.upperBounds.first())
49+
else type
50+
4351
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Any>) = object : ResponseFn<A, Eval<Any>> {
4452

45-
override fun join(response: Promise<out Response<A>>, arguments: Arguments): Eval<Any> = Eval.later {
53+
override fun join(response: Promise<out Response<A, out Throwable>>, arguments: Arguments): Eval<Any> = Eval.later {
4654
next.join(response, arguments)
4755
}
4856

arrow/src/main/kotlin/com/github/ljtfreitas/julian/k/arrow/NonEmptyListResponseT.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
package com.github.ljtfreitas.julian.k.arrow
2424

2525
import arrow.core.NonEmptyList
26-
import arrow.core.Option
27-
import arrow.core.nel
28-
import arrow.core.toOption
2926
import com.github.ljtfreitas.julian.Arguments
3027
import com.github.ljtfreitas.julian.Endpoint
3128
import com.github.ljtfreitas.julian.JavaType
@@ -35,7 +32,6 @@ import com.github.ljtfreitas.julian.ResponseFn
3532
import com.github.ljtfreitas.julian.ResponseT
3633
import java.lang.reflect.Type
3734
import java.lang.reflect.WildcardType
38-
import java.util.stream.Stream
3935

4036
object NonEmptyListResponseT : ResponseT<Collection<Any>, NonEmptyList<Any>> {
4137

@@ -48,13 +44,13 @@ object NonEmptyListResponseT : ResponseT<Collection<Any>, NonEmptyList<Any>> {
4844
.let { JavaType.parameterized(Collection::class.java, it) }
4945

5046
private fun argument(type: Type) : Type =
51-
if (type is WildcardType && type.lowerBounds.isNotEmpty())
52-
argument(type.lowerBounds.first())
47+
if (type is WildcardType && type.upperBounds.isNotEmpty())
48+
argument(type.upperBounds.first())
5349
else type
5450

5551
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Collection<Any>>) = object : ResponseFn<A, NonEmptyList<Any>> {
5652

57-
override fun run(response: Promise<out Response<A?>>, arguments: Arguments): Promise<NonEmptyList<Any>> =
53+
override fun run(response: Promise<out Response<A?, out Throwable>>, arguments: Arguments): Promise<NonEmptyList<Any>> =
5854
next.run(response, arguments).then { NonEmptyList.fromListUnsafe(it.toList()) }
5955

6056
override fun returnType(): JavaType = next.returnType()

arrow/src/main/kotlin/com/github/ljtfreitas/julian/k/arrow/OptionResponseT.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,27 @@ import com.github.ljtfreitas.julian.Promise
3131
import com.github.ljtfreitas.julian.Response
3232
import com.github.ljtfreitas.julian.ResponseFn
3333
import com.github.ljtfreitas.julian.ResponseT
34+
import java.lang.reflect.Type
35+
import java.lang.reflect.WildcardType
3436

3537
object OptionResponseT : ResponseT<Any, Option<Any>> {
3638

3739
override fun test(endpoint: Endpoint) = endpoint.returnType().`is`(Option::class.java)
3840

3941
override fun adapted(endpoint: Endpoint): JavaType = endpoint.returnType().parameterized()
4042
.map(JavaType.Parameterized::firstArg)
43+
.map(::argument)
4144
.orElseGet { Any::class.java }
4245
.let(JavaType::valueOf)
4346

47+
private fun argument(type: Type) : Type =
48+
if (type is WildcardType && type.upperBounds.isNotEmpty())
49+
argument(type.upperBounds.first())
50+
else type
51+
4452
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Any>) = object : ResponseFn<A, Option<Any>> {
4553

46-
override fun run(response: Promise<out Response<A?>>, arguments: Arguments): Promise<Option<Any>> =
54+
override fun run(response: Promise<out Response<A?, out Throwable>>, arguments: Arguments): Promise<Option<Any>> =
4755
next.run(response, arguments).then { it.toOption() }
4856

4957
override fun returnType(): JavaType = next.returnType()
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (C) 2021 Tiago de Freitas Lima
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package com.github.ljtfreitas.julian.k.arrow
24+
25+
import arrow.core.Either
26+
import arrow.core.left
27+
import arrow.core.right
28+
import com.github.ljtfreitas.julian.Arguments
29+
import com.github.ljtfreitas.julian.Endpoint
30+
import com.github.ljtfreitas.julian.JavaType
31+
import com.github.ljtfreitas.julian.Kind
32+
import com.github.ljtfreitas.julian.Promise
33+
import com.github.ljtfreitas.julian.Response
34+
import com.github.ljtfreitas.julian.ResponseFn
35+
import com.github.ljtfreitas.julian.ResponseT
36+
import com.github.ljtfreitas.julian.http.RecoverableHTTPResponse
37+
import java.lang.reflect.Type
38+
import java.lang.reflect.WildcardType
39+
import java.util.Optional
40+
import java.util.function.Supplier
41+
42+
object RecoverableEitherResponseT : ResponseT<Any, Either<Any, Any>> {
43+
44+
override fun test(endpoint: Endpoint) = endpoint.returnType().let { returnType ->
45+
returnType.`is`(Either::class.java)
46+
&& returnType.parameterized()
47+
.map(JavaType.Parameterized::firstArg)
48+
.map(::argument)
49+
.map(JavaType::valueOf)
50+
.filter { left -> !left.compatible(Throwable::class.java) }
51+
.isPresent
52+
}
53+
54+
override fun adapted(endpoint: Endpoint): JavaType = endpoint.returnType().parameterized()
55+
.map { it.actualTypeArguments[1] }
56+
.map(::argument)
57+
.orElseGet { Any::class.java }
58+
.let(JavaType::valueOf)
59+
60+
private fun argument(type: Type) : Type =
61+
if (type is WildcardType && type.upperBounds.isNotEmpty())
62+
argument(type.upperBounds.first())
63+
else type
64+
65+
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Any>) = object : ResponseFn<A, Either<Any, Any>> {
66+
67+
override fun run(response: Promise<out Response<A, out Throwable>>, arguments: Arguments): Promise<Either<Any, Any>> {
68+
val leftClassType: Class<out Any> = JavaType.valueOf(endpoint.returnType().parameterized()
69+
.map(JavaType.Parameterized::firstArg)
70+
.orElse(Any::class.java))
71+
.rawClassType()
72+
73+
val left: Promise<Optional<Promise<Either<Any, Any>>>> = response.then { r ->
74+
r.cast(object : Kind<RecoverableHTTPResponse<A>>() {})
75+
.map { recoverable -> recoverable.recover(leftClassType) }
76+
.map { recovered -> Promise.done(recovered) }
77+
.map { recovered -> recovered.bind { rec -> rec.fold({ success -> Promise.done(success) }, { failure -> Promise.failed(failure)})} }
78+
.map { recovered -> recovered.then { it.left() } }
79+
}
80+
81+
val right = Supplier<Promise<Either<Any, Any>>> { next.run(response, arguments).then { it.right() } }
82+
83+
return left.bind { recovered -> recovered.orElseGet(right) }
84+
}
85+
86+
override fun returnType() = next.returnType()
87+
}
88+
}
89+
90+
class RecoverableEitherResponseTProxy : ResponseT<Any, Either<Any, Any>> by RecoverableEitherResponseT

arrow/src/main/kotlin/com/github/ljtfreitas/julian/k/arrow/fx/EffectResponseT.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ object EffectResponseT : ResponseT<Any, Effect<Exception, Any>> {
5050
override fun <A> bind(endpoint: Endpoint, next: ResponseFn<A, Any>) = object : ResponseFn<A, Effect<Exception, Any>> {
5151

5252
@Suppress("UNCHECKED_CAST")
53-
override fun join(response: Promise<out Response<A>>, arguments: Arguments): Effect<Exception, Any> {
53+
override fun join(response: Promise<out Response<A, out Throwable>>, arguments: Arguments): Effect<Exception, Any> {
5454
val leftClassType: Class<out Exception> = JavaType.valueOf(endpoint.returnType().parameterized()
5555
.map(JavaType.Parameterized::firstArg)
5656
.orElse(Exception::class.java))

arrow/src/main/resources/META-INF/services/com.github.ljtfreitas.julian.ResponseT

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ com.github.ljtfreitas.julian.k.arrow.EitherResponseTProxy
22
com.github.ljtfreitas.julian.k.arrow.EvalResponseTProxy
33
com.github.ljtfreitas.julian.k.arrow.NonEmptyListResponseTProxy
44
com.github.ljtfreitas.julian.k.arrow.OptionResponseTProxy
5+
com.github.ljtfreitas.julian.k.arrow.RecoverableEitherResponseTProxy
56
com.github.ljtfreitas.julian.k.arrow.fx.EffectResponseTProxy

arrow/src/test/kotlin/com/github/ljtfreitas/julian/k/arrow/EitherResponseTTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class EitherResponseTTest : DescribeSpec({
9494

9595
val promise: Promise<Either<Throwable, Any>> = fn.run(Promise.failed(failure), Arguments.empty())
9696

97-
promise.subscribe(object : Subscriber<Either<Throwable, Any>> {
97+
promise.subscribe(object : Subscriber<Either<Throwable, Any>, Throwable> {
9898

9999
override fun success(value: Either<Throwable, Any>) {
100100
fail("it was expected a failure, not a successful value :(")
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.github.ljtfreitas.julian.k.arrow
2+
3+
import arrow.core.Either
4+
import com.github.ljtfreitas.julian.Arguments
5+
import com.github.ljtfreitas.julian.Endpoint
6+
import com.github.ljtfreitas.julian.ObjectResponseT
7+
import com.github.ljtfreitas.julian.Promise
8+
import com.github.ljtfreitas.julian.Response
9+
import com.github.ljtfreitas.julian.Subscriber
10+
import com.github.ljtfreitas.julian.http.FailureHTTPResponse
11+
import com.github.ljtfreitas.julian.http.HTTPClientFailureResponseException.BadRequest
12+
import com.github.ljtfreitas.julian.http.HTTPHeader
13+
import com.github.ljtfreitas.julian.http.HTTPHeaders
14+
import com.github.ljtfreitas.julian.http.HTTPResponse
15+
import com.github.ljtfreitas.julian.http.RecoverableHTTPResponse
16+
import com.github.ljtfreitas.julian.http.UnrecoverableHTTPResponseException
17+
import com.github.ljtfreitas.julian.http.codec.HTTPResponseReaders
18+
import com.github.ljtfreitas.julian.http.codec.StringHTTPMessageCodec
19+
import com.github.ljtfreitas.julian.k.javaType
20+
import io.kotest.assertions.arrow.core.shouldBeLeft
21+
import io.kotest.assertions.arrow.core.shouldBeRight
22+
import io.kotest.assertions.fail
23+
import io.kotest.core.spec.style.DescribeSpec
24+
import io.kotest.matchers.shouldBe
25+
import io.kotest.matchers.types.shouldBeInstanceOf
26+
import io.mockk.every
27+
import io.mockk.mockk
28+
import java.io.IOException
29+
30+
class RecoverableEitherResponseTTest : DescribeSpec({
31+
32+
val subject = RecoverableEitherResponseT
33+
34+
val endpoint = mockk<Endpoint>()
35+
36+
describe("a ResponseT instance for Either<L, R> values (left is a recovered value!)") {
37+
38+
describe("predicates") {
39+
40+
it("supports Either<Left, Right> as function return type") {
41+
every { endpoint.returnType() } returns javaType<Either<String, String>>()
42+
43+
subject.test(endpoint) shouldBe true
44+
}
45+
46+
it("it does not support Throwable (or sub-exceptions) as left argument.") {
47+
every { endpoint.returnType() } returns javaType<Either<Throwable, String>>()
48+
49+
subject.test(endpoint) shouldBe false
50+
}
51+
52+
it("sub-exceptions does not supported as left argument, too") {
53+
every { endpoint.returnType() } returns javaType<Either<IOException, String>>()
54+
55+
subject.test(endpoint) shouldBe false
56+
}
57+
}
58+
59+
describe("adapt to expected type") {
60+
61+
it("we must to adapt to Right argument from either (Either<Left, Right> -> Right)") {
62+
every { endpoint.returnType() } returns javaType<Either<Any, String>>()
63+
64+
subject.adapted(endpoint) shouldBe javaType<String>()
65+
}
66+
}
67+
68+
describe("bind") {
69+
70+
every { endpoint.returnType() } returns javaType<Either<String, String>>()
71+
72+
val fn = subject.bind<String>(endpoint = endpoint, next = ObjectResponseT<Any>().bind(endpoint, null))
73+
74+
it("bind a value T to Either<String, T>") {
75+
val expected = "hello"
76+
77+
val either = fn.join(Promise.done(Response.done(expected)), Arguments.empty())
78+
79+
either shouldBeRight expected
80+
}
81+
82+
it("we are able to recover a response failure to Either<String, T>") {
83+
val expected = "oops"
84+
85+
val badRequest = BadRequest(
86+
HTTPHeaders(listOf(HTTPHeader(HTTPHeader.CONTENT_TYPE, "text/plain"))),
87+
Promise.done(expected.toByteArray())
88+
)
89+
90+
val failure = FailureHTTPResponse<String>(badRequest)
91+
92+
val response = Promise.done<HTTPResponse<String>>(
93+
RecoverableHTTPResponse(
94+
failure,
95+
HTTPResponseReaders(listOf(StringHTTPMessageCodec()))
96+
)
97+
)
98+
99+
val either = fn.join(response, Arguments.empty())
100+
101+
either shouldBeLeft expected
102+
}
103+
104+
it("in case it's impossible to convert the response to the desired recovered value, we get a failed Promise") {
105+
every { endpoint.returnType() } returns javaType<Either<MyFailure, String>>()
106+
107+
val badRequest = BadRequest(
108+
HTTPHeaders(listOf(HTTPHeader(HTTPHeader.CONTENT_TYPE, "text/plain"))),
109+
Promise.done("oops".toByteArray())
110+
)
111+
112+
val failure = FailureHTTPResponse<String>(badRequest)
113+
114+
val codec = StringHTTPMessageCodec() // isn't able to convert String to MyFailure
115+
116+
val response = Promise.done<HTTPResponse<String>>(RecoverableHTTPResponse(failure, HTTPResponseReaders(listOf(codec))))
117+
118+
val promise = fn.run(response, Arguments.empty())
119+
120+
promise.subscribe(object : Subscriber<Either<Any, Any>, Throwable> {
121+
122+
override fun success(value: Either<Any, Any>?) {
123+
fail("a success value was not expected here...")
124+
}
125+
126+
override fun failure(failure: Throwable) {
127+
failure.shouldBeInstanceOf<UnrecoverableHTTPResponseException>()
128+
}
129+
130+
})
131+
}
132+
}
133+
}
134+
})
135+
136+
class MyFailure

0 commit comments

Comments
 (0)