Skip to content

Commit f434786

Browse files
BailHook default handler (#33)
* add default handler to bail hook * tests * reverse, call handler if not bail * smarter way to do the same thing * bail hook generation * multiple call function generation * extract callBuilder * rename second call * remove git add . * fixed other bail hooks * apiDump * Update processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/Poet.kt Co-authored-by: Jeremiah Zucker <[email protected]> --------- Co-authored-by: Jeremiah Zucker <[email protected]>
1 parent 6314b81 commit f434786

File tree

7 files changed

+160
-29
lines changed

7 files changed

+160
-29
lines changed

hooks/api/hooks.api

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public abstract class com/intuit/hooks/AsyncParallelHook : com/intuit/hooks/Asyn
1616

1717
public abstract class com/intuit/hooks/AsyncSeriesBailHook : com/intuit/hooks/AsyncBaseHook {
1818
public fun <init> ()V
19-
protected final fun call (Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
19+
protected final fun call (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
20+
public static synthetic fun call$default (Lcom/intuit/hooks/AsyncSeriesBailHook;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
2021
}
2122

2223
public abstract class com/intuit/hooks/AsyncSeriesHook : com/intuit/hooks/AsyncBaseHook {
@@ -97,7 +98,8 @@ public final class com/intuit/hooks/LoopResult$Companion {
9798

9899
public abstract class com/intuit/hooks/SyncBailHook : com/intuit/hooks/SyncBaseHook {
99100
public fun <init> ()V
100-
protected final fun call (Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
101+
protected final fun call (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
102+
public static synthetic fun call$default (Lcom/intuit/hooks/SyncBailHook;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
101103
}
102104

103105
public abstract class com/intuit/hooks/SyncBaseHook : com/intuit/hooks/BaseHook {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package com.intuit.hooks
22

33
public abstract class AsyncSeriesBailHook<F : Function<BailResult<R>>, R> : AsyncBaseHook<F>("AsyncSeriesBailHook") {
4-
protected suspend fun call(invokeWithContext: suspend (F, HookContext) -> BailResult<R>): R? {
4+
protected suspend fun call(invokeWithContext: suspend (F, HookContext) -> BailResult<R>, default: (suspend (HookContext) -> R)? = null): R? {
55
val context = setup(invokeWithContext)
66

77
taps.forEach { tapInfo ->
88
when (val result = invokeWithContext(tapInfo.f, context)) {
99
is BailResult.Bail<R> -> return@call result.value
10+
is BailResult.Continue -> {}
1011
}
1112
}
1213

13-
return null
14+
return default?.invoke(context)
1415
}
1516
}

hooks/src/main/kotlin/com/intuit/hooks/SyncBailHook.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ public sealed class BailResult<T> {
77
}
88

99
public abstract class SyncBailHook<F : Function<BailResult<R>>, R> : SyncBaseHook<F>("SyncBailHook") {
10-
protected fun call(invokeWithContext: (F, HookContext) -> BailResult<R>): R? {
10+
protected fun call(invokeWithContext: (F, HookContext) -> BailResult<R>, default: ((HookContext) -> R)? = null): R? {
1111
val context = setup(invokeWithContext)
1212

1313
taps.forEach { tapInfo ->
1414
when (val result = invokeWithContext(tapInfo.f, context)) {
1515
is BailResult.Bail<R> -> return@call result.value
16+
is BailResult.Continue -> {}
1617
}
1718
}
1819

19-
return null
20+
return default?.invoke(context)
2021
}
2122
}

hooks/src/test/kotlin/com/intuit/hooks/AsyncSeriesBailHookTests.kt

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@ import org.junit.jupiter.api.Test
77

88
class AsyncSeriesBailHookTests {
99
class Hook1<T1, R : Any?> : AsyncSeriesBailHook<suspend (HookContext, T1) -> BailResult<R>, R>() {
10-
suspend fun call(p1: T1): R? = super.call { f, context -> f(context, p1) }
10+
suspend fun call(p1: T1, default: (suspend (HookContext, T1) -> R)? = null): R? = super.call(
11+
{ f, context -> f(context, p1) },
12+
default?.let {
13+
{ context -> default(context, p1) }
14+
}
15+
)
16+
17+
suspend fun call(p1: T1, default: (suspend (T1) -> R)) = call(p1) { _, arg1 ->
18+
default.invoke(arg1)
19+
}
1120
}
1221

1322
@Test
@@ -41,12 +50,37 @@ class AsyncSeriesBailHookTests {
4150
}
4251

4352
@Test
44-
fun `bail taps can bail without return value`() {
45-
val h = SyncBailHookTests.Hook1<String, Unit>()
53+
fun `bail taps can bail without return value`() = runBlocking {
54+
val h = Hook1<String, Unit>()
4655
h.tap("continue") { _, _ -> BailResult.Continue() }
4756
h.tap("bail") { _, _ -> BailResult.Bail(Unit) }
4857
h.tap("continue again") { _, _ -> Assertions.fail("Should never have gotten here!") }
4958

5059
Assertions.assertEquals(Unit, h.call("David"))
5160
}
61+
62+
@Test
63+
fun `bail call with default handler invokes without taps bailing`() = runBlocking {
64+
val h = Hook1<String, String>()
65+
h.tap("continue") { _, _ -> BailResult.Continue() }
66+
h.tap("continue again") { _, _ -> BailResult.Continue() }
67+
68+
val result = h.call("David") { _, str ->
69+
str
70+
}
71+
72+
Assertions.assertEquals("David", result)
73+
}
74+
75+
@Test
76+
fun `bail call with default handler does not invoke with bail`() = runBlocking {
77+
val h = Hook1<String, String>()
78+
h.tap("continue") { _, _ -> BailResult.Continue() }
79+
h.tap("bail") { _, _ -> BailResult.Bail("bailing") }
80+
h.tap("continue again") { _, _ -> Assertions.fail("Should never have gotten here!") }
81+
82+
val result = h.call("David") { str -> str }
83+
84+
Assertions.assertEquals("bailing", result)
85+
}
5286
}

hooks/src/test/kotlin/com/intuit/hooks/SyncBailHookTests.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ import org.junit.jupiter.api.Test
88

99
class SyncBailHookTests {
1010
class Hook1<T1, R : Any?> : SyncBailHook<(HookContext, T1) -> BailResult<R>, R>() {
11-
fun call(p1: T1) = super.call { f, context -> f(context, p1) }
11+
fun call(p1: T1, default: ((HookContext, T1) -> R)? = null) = super.call(
12+
{ f, context -> f(context, p1) },
13+
default?.let {
14+
{ context -> default(context, p1) }
15+
}
16+
)
17+
18+
fun call(p1: T1, default: ((T1) -> R)) = call(p1) { _, arg1 ->
19+
default.invoke(arg1)
20+
}
1221
}
1322

1423
@Test
@@ -51,4 +60,29 @@ class SyncBailHookTests {
5160

5261
Assertions.assertEquals(Unit, h.call("David"))
5362
}
63+
64+
@Test
65+
fun `bail call with default handler invokes without taps bailing`() {
66+
val h = Hook1<String, String>()
67+
h.tap("continue") { _, _ -> BailResult.Continue() }
68+
h.tap("continue again") { _, _ -> BailResult.Continue() }
69+
70+
val result = h.call("David") { _, str ->
71+
str
72+
}
73+
74+
Assertions.assertEquals("David", result)
75+
}
76+
77+
@Test
78+
fun `bail call with default handler does not invoke with bail`() {
79+
val h = Hook1<String, String>()
80+
h.tap("continue") { _, _ -> BailResult.Continue() }
81+
h.tap("bail") { _, _ -> BailResult.Bail("bailing") }
82+
h.tap("continue again") { _, _ -> Assertions.fail("Should never have gotten here!") }
83+
84+
val result = h.call("David") { str -> str }
85+
86+
Assertions.assertEquals("bailing", result)
87+
}
5488
}

processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/Poet.kt

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,24 @@ private fun HooksContainer.generateContainerClass(): TypeSpec {
4040
}.build()
4141
}
4242

43+
internal val HookInfo.callBuilder get() = FunSpec.builder("call")
44+
.addParameters(parameterSpecs)
45+
.apply {
46+
if (isAsync)
47+
addModifiers(KModifier.SUSPEND)
48+
}
49+
4350
internal fun HookInfo.generateClass(): TypeSpec {
44-
val callBuilder = FunSpec.builder("call")
45-
.addParameters(parameterSpecs)
46-
.apply {
47-
if (this@generateClass.isAsync)
48-
addModifiers(KModifier.SUSPEND)
49-
}
5051

51-
val (superclass, call) = when (hookType) {
52+
val (superclass, calls) = when (hookType) {
5253
HookType.SyncHook, HookType.AsyncSeriesHook, HookType.AsyncParallelHook -> {
5354
val superclass = createSuperClass()
5455

5556
val call = callBuilder
5657
.returns(UNIT)
5758
.addStatement("return super.call { f, context -> f(context, $paramsWithoutTypes) }")
5859

59-
Pair(superclass, call)
60+
Pair(superclass, listOf(call))
6061
}
6162
HookType.SyncLoopHook, HookType.AsyncSeriesLoopHook -> {
6263
val superclass = createSuperClass(interceptParameter)
@@ -69,7 +70,7 @@ internal fun HookInfo.generateClass(): TypeSpec {
6970
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }")
7071
)
7172

72-
Pair(superclass, call)
73+
Pair(superclass, listOf(call))
7374
}
7475
HookType.SyncWaterfallHook, HookType.AsyncSeriesWaterfallHook -> {
7576
val superclass = createSuperClass(params.first().type)
@@ -84,31 +85,51 @@ internal fun HookInfo.generateClass(): TypeSpec {
8485
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }")
8586
)
8687

87-
Pair(superclass, call)
88+
Pair(superclass, listOf(call))
8889
}
8990
HookType.SyncBailHook, HookType.AsyncSeriesBailHook -> {
9091
requireNotNull(hookSignature.nullableReturnTypeType)
9192
val superclass = createSuperClass(hookSignature.returnTypeType)
9293

9394
val call = callBuilder
95+
.addParameter(
96+
ParameterSpec.builder(
97+
"default",
98+
LambdaTypeName.get(
99+
parameters = parameterSpecs,
100+
returnType = hookSignature.returnTypeType!!
101+
)
102+
).build()
103+
)
94104
.returns(hookSignature.nullableReturnTypeType)
95-
.addStatement("return super.call { f, context -> f(context, $paramsWithoutTypes) }")
105+
.addStatement("return call ($paramsWithoutTypes) { _, $paramsWithoutTypes -> default.invoke($paramsWithoutTypes) }")
106+
107+
val contextCall = callBuilder
108+
.addParameter(
109+
ParameterSpec.builder(
110+
"default",
111+
createHookContextLambda(hookSignature.returnTypeType).copy(nullable = true)
112+
).defaultValue(CodeBlock.of("null")).build()
113+
)
114+
.returns(hookSignature.nullableReturnTypeType)
115+
.addStatement("return super.call ({ f, context -> f(context, $paramsWithoutTypes) }, default?.let { { context -> default(context, $paramsWithoutTypes) } } )")
96116

97-
Pair(superclass, call)
117+
Pair(superclass, listOf(call, contextCall))
98118
}
99119
// parallel bail requires the concurrency parameter, otherwise it would be just like the other bail hooks
100120
HookType.AsyncParallelBailHook -> {
101121
requireNotNull(hookSignature.nullableReturnTypeType)
102122
val superclass = createSuperClass(hookSignature.returnTypeType)
103123

104-
// force the concurrency parameter to be first
105-
callBuilder.parameters.add(0, ParameterSpec("concurrency", INT))
124+
val call = with(callBuilder) {
125+
// force the concurrency parameter to be first
126+
parameters.add(0, ParameterSpec("concurrency", INT))
106127

107-
val call = callBuilder
108-
.returns(hookSignature.nullableReturnTypeType)
109-
.addStatement("return super.call(concurrency) { f, context -> f(context, $paramsWithoutTypes) }")
128+
returns(hookSignature.nullableReturnTypeType)
129+
.addStatement("return super.call(concurrency) { f, context -> f(context, $paramsWithoutTypes) }")
130+
}
110131

111-
Pair(superclass, call)
132+
Pair(superclass, listOf(call))
112133
}
113134
}
114135

@@ -117,7 +138,7 @@ internal fun HookInfo.generateClass(): TypeSpec {
117138
addFunctions(tapMethods)
118139
hookType.addedAnnotation?.let(::addAnnotation)
119140
superclass(superclass)
120-
addFunction(call.build())
141+
addFunctions(calls.map { it.build() })
121142
}.build()
122143
}
123144

processor/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,44 @@ class HooksProcessorTest {
282282
result.runCompiledAssertions()
283283
}
284284

285+
@Test fun `generates bail hook class`() {
286+
val testHooks = SourceFile.kotlin(
287+
"TestBailHooks.kt",
288+
"""
289+
import com.intuit.hooks.BailResult
290+
import com.intuit.hooks.Hook
291+
import com.intuit.hooks.dsl.Hooks
292+
293+
internal abstract class TestBailHooks : Hooks() {
294+
@SyncBail<(String) -> BailResult<String>>
295+
abstract val testSyncBailHook: Hook
296+
}
297+
"""
298+
)
299+
300+
val assertions = SourceFile.kotlin(
301+
"Assertions.kt",
302+
"""
303+
import com.intuit.hooks.BailResult
304+
import org.junit.jupiter.api.Assertions.*
305+
306+
fun testHook() {
307+
val hooks = TestBailHooksImpl()
308+
hooks.testSyncBailHook.tap("test") { _, _ -> BailResult.Continue() }
309+
val result = hooks.testSyncBailHook.call("hello") { ctx, str ->
310+
str + " world"
311+
}
312+
assertEquals("hello world", result)
313+
}
314+
"""
315+
)
316+
317+
val (compilation, result) = compile(testHooks, assertions)
318+
result.assertOk()
319+
compilation.assertKspGeneratedSources("TestBailHooksHooks.kt")
320+
result.runCompiledAssertions()
321+
}
322+
285323
@Test fun `generates nested hook class`() {
286324
val testHooks = SourceFile.kotlin(
287325
"TestHooks.kt",

0 commit comments

Comments
 (0)