From 994846a8510ae177b05f3826d76c6d3dea67201e Mon Sep 17 00:00:00 2001 From: Dan Farrelly Date: Tue, 23 Jul 2024 18:29:38 -0400 Subject: [PATCH 1/4] Fix inngest.send. Add Event Builder --- inngest/src/main/kotlin/com/inngest/Event.kt | 83 +++++++++++++++++-- .../src/main/kotlin/com/inngest/Inngest.kt | 55 +++++++++++- inngest/src/main/kotlin/com/inngest/Step.kt | 29 ++----- 3 files changed, 134 insertions(+), 33 deletions(-) diff --git a/inngest/src/main/kotlin/com/inngest/Event.kt b/inngest/src/main/kotlin/com/inngest/Event.kt index 5a98242f..0ff5a42b 100644 --- a/inngest/src/main/kotlin/com/inngest/Event.kt +++ b/inngest/src/main/kotlin/com/inngest/Event.kt @@ -1,6 +1,9 @@ package com.inngest -data class Event( +/** + * An internal class used for parsing events sent to Inngest functions + */ +internal data class Event( val id: String, val name: String, val data: LinkedHashMap, @@ -9,7 +12,77 @@ data class Event( val v: Any? = null, ) -// data class EventAPIResponse( -// val ids: Array, -// val status: String, -// ) +/** + * Create an event to send to Inngest + */ +data class InngestEvent( + val id: String?, + val name: String, + val data: Any, + val user: Any?, + val ts: Long?, + val v: String? = null, +) + +/** + * Construct a new Inngest Event via builder + */ +class InngestEventBuilder( + var id: String?, + var name: String?, + var data: Any?, + private var user: Any?, + private var ts: Long?, + private var v: String? = null, +) { + fun id(id: String): InngestEventBuilder { + this.id = id + return this + } + + fun name(name: String): InngestEventBuilder { + this.name = name + return this + } + + fun data(data: Any): InngestEventBuilder { + this.data = data + return this + } + + fun ts(ts: Long): InngestEventBuilder { + this.ts = ts + return this + } + + fun v(v: String): InngestEventBuilder { + this.v = v + return this + } + + fun build(): InngestEvent { + if (name == null) { + throw IllegalArgumentException("name is required") + } + if (data == null) { + throw IllegalArgumentException("data is required") + } + return InngestEvent( + id, + name!!, + data!!, + user, + ts, + v, + ) + } +} + +/** + * The response from the Inngest Event API including the ids of any event created + * in the order of which they were included in the request + */ +data class SendEventsResponse( + val ids: List, + val status: Int, +) diff --git a/inngest/src/main/kotlin/com/inngest/Inngest.kt b/inngest/src/main/kotlin/com/inngest/Inngest.kt index 5190faee..f5f9da63 100644 --- a/inngest/src/main/kotlin/com/inngest/Inngest.kt +++ b/inngest/src/main/kotlin/com/inngest/Inngest.kt @@ -2,6 +2,8 @@ package com.inngest import com.beust.klaxon.Klaxon import java.io.IOException +import io.ktor.http.* +import java.net.ConnectException class Inngest @JvmOverloads @@ -36,11 +38,56 @@ class Inngest fun send(events: Array): SendEventsResponse? { val request = httpClient.build("$baseUrl/e/$eventKey", events) - return httpClient.send(request) lambda@{ response -> - // TODO: Handle error case - if (!response.isSuccessful) throw IOException("Unexpected code $response") + try { + return httpClient.send(request) lambda@{ response -> + if (!response.isSuccessful) { + // TODO - Attempt to parse the HTTP response and get error from JSON body to pass here + throw InngestSendEventBadResponseCodeException(response.code) + } - return@lambda Klaxon().parse(response.body!!.charStream()) + val responseBody = response.body!!.charStream() + try { + val sendEventsResponse = Klaxon().parse(responseBody) + if (sendEventsResponse != null) { + return@lambda sendEventsResponse + } + } catch (e: Exception) { + throw InngestSendEventInvalidResponseException(responseBody.toString()) + } + throw InngestSendEventInvalidResponseException(responseBody.toString()) + } + } catch (e: ConnectException) { + throw InngestSendEventConnectException(e.message!!) + } catch (e: Exception) { + throw InngestSendEventException(e.message!!) } } } + +/** + * A generic exception occurred while sending events + */ +open class InngestSendEventException( + message: String, +) : Exception("Failed to send event: $message") + +/** + * A failure occurred establishing a connection to the Inngest Event API + */ +class InngestSendEventConnectException( + message: String, +) : InngestSendEventException(message) + +/** + * The Inngest Event API returned a non-successful HTTP status code + */ +class InngestSendEventBadResponseCodeException( + code: Int, +) : InngestSendEventException("Bad response code: $code") + +/** + * The Inngest Event API returned a response that was not parsable + */ +class InngestSendEventInvalidResponseException( + message: String, +) : InngestSendEventException("Unable to parse response: $message") diff --git a/inngest/src/main/kotlin/com/inngest/Step.kt b/inngest/src/main/kotlin/com/inngest/Step.kt index eeebb9a5..bee9d7ec 100644 --- a/inngest/src/main/kotlin/com/inngest/Step.kt +++ b/inngest/src/main/kotlin/com/inngest/Step.kt @@ -5,26 +5,6 @@ import java.time.Duration typealias MemoizedRecord = HashMap typealias MemoizedState = HashMap -data class InngestEvent( - val name: String, - val data: Any, -) - -data class SendEventsResponse( - val ids: Array, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as SendEventsResponse - - return ids.contentEquals(other.ids) - } - - override fun hashCode(): Int = ids.contentHashCode() -} - class StepInvalidStateTypeException( val id: String, val hashedId: String, @@ -51,7 +31,7 @@ class StepInterruptSleepException( class StepInterruptSendEventException( id: String, hashedId: String, - val eventIds: Array, + val eventIds: List, ) : StepInterruptException(id, hashedId, eventIds) class StepInterruptInvokeException( @@ -231,15 +211,16 @@ class Step( val hashedId = state.getHashFromId(id) try { - val stepState = state.getState>(hashedId, "event_ids") + val stepState = state.getState>(hashedId, "event_ids") if (stepState != null) { - return SendEventsResponse(stepState) + return SendEventsResponse(stepState, 200) } throw Exception("step state expected sendEvent, got something else") } catch (e: StateNotFound) { val response = client.send(events) - throw StepInterruptSendEventException(id, hashedId, response!!.ids) + //throw StepInterruptSendEventException(id, hashedId, response!!.ids) + throw StepInterruptSendEventException(id, hashedId, response.ids) } } From 9b5e153ae84fcecb1105a7269a6dddd76c8df7e6 Mon Sep 17 00:00:00 2001 From: Dan Farrelly Date: Tue, 23 Jul 2024 18:38:09 -0400 Subject: [PATCH 2/4] Fix order of optional parameters --- inngest/src/main/kotlin/com/inngest/Event.kt | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/inngest/src/main/kotlin/com/inngest/Event.kt b/inngest/src/main/kotlin/com/inngest/Event.kt index 0ff5a42b..8368836a 100644 --- a/inngest/src/main/kotlin/com/inngest/Event.kt +++ b/inngest/src/main/kotlin/com/inngest/Event.kt @@ -1,5 +1,7 @@ package com.inngest +import com.beust.klaxon.Json + /** * An internal class used for parsing events sent to Inngest functions */ @@ -15,14 +17,20 @@ internal data class Event( /** * Create an event to send to Inngest */ -data class InngestEvent( - val id: String?, - val name: String, - val data: Any, - val user: Any?, - val ts: Long?, - val v: String? = null, -) +data class InngestEvent + @JvmOverloads + constructor( + val name: String, + val data: Any, + @Json(serializeNull = false) + val user: Any? = null, + @Json(serializeNull = false) + val id: String? = null, + @Json(serializeNull = false) + val ts: Long? = null, + @Json(serializeNull = false) + val v: String? = null, + ) /** * Construct a new Inngest Event via builder @@ -68,10 +76,10 @@ class InngestEventBuilder( throw IllegalArgumentException("data is required") } return InngestEvent( - id, name!!, data!!, user, + id, ts, v, ) From 51801113af10ef505630e2e96f7bfc3a4de42983 Mon Sep 17 00:00:00 2001 From: Albert Chae Date: Sun, 8 Sep 2024 22:52:44 -0700 Subject: [PATCH 3/4] Fixed and rebased - moved Array -> List conversion out of scope of this PR, we can do that in a follow up --- .../java/com/inngest/springbootdemo/FollowupFunction.java | 4 ++-- inngest/src/main/kotlin/com/inngest/Comm.kt | 4 ++-- inngest/src/main/kotlin/com/inngest/Event.kt | 8 ++++---- inngest/src/main/kotlin/com/inngest/Function.kt | 4 ++-- inngest/src/main/kotlin/com/inngest/Inngest.kt | 6 +++--- inngest/src/main/kotlin/com/inngest/Step.kt | 7 +++---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/inngest-spring-boot-demo/src/main/java/com/inngest/springbootdemo/FollowupFunction.java b/inngest-spring-boot-demo/src/main/java/com/inngest/springbootdemo/FollowupFunction.java index df3f0823..3029c1d5 100644 --- a/inngest-spring-boot-demo/src/main/java/com/inngest/springbootdemo/FollowupFunction.java +++ b/inngest-spring-boot-demo/src/main/java/com/inngest/springbootdemo/FollowupFunction.java @@ -3,7 +3,7 @@ import com.inngest.*; import org.jetbrains.annotations.NotNull; -import java.util.LinkedHashMap; +import java.util.Map; public class FollowupFunction extends InngestFunction { @@ -18,7 +18,7 @@ public InngestFunctionConfigBuilder config(InngestFunctionConfigBuilder builder) } @Override - public LinkedHashMap execute(@NotNull FunctionContext ctx, @NotNull Step step) { + public Map execute(@NotNull FunctionContext ctx, @NotNull Step step) { System.out.println("-> follow up handler called " + ctx.getEvent().getName()); return ctx.getEvent().getData(); } diff --git a/inngest/src/main/kotlin/com/inngest/Comm.kt b/inngest/src/main/kotlin/com/inngest/Comm.kt index e27e3cc2..54ecdf9a 100644 --- a/inngest/src/main/kotlin/com/inngest/Comm.kt +++ b/inngest/src/main/kotlin/com/inngest/Comm.kt @@ -9,8 +9,8 @@ import java.io.IOException data class ExecutionRequestPayload( val ctx: ExecutionContext, - val event: Event, - val events: List, + val event: InngestEvent, + val events: List, val steps: MemoizedState, ) diff --git a/inngest/src/main/kotlin/com/inngest/Event.kt b/inngest/src/main/kotlin/com/inngest/Event.kt index 8368836a..b2f61b35 100644 --- a/inngest/src/main/kotlin/com/inngest/Event.kt +++ b/inngest/src/main/kotlin/com/inngest/Event.kt @@ -21,7 +21,7 @@ data class InngestEvent @JvmOverloads constructor( val name: String, - val data: Any, + val data: Map, @Json(serializeNull = false) val user: Any? = null, @Json(serializeNull = false) @@ -38,7 +38,7 @@ data class InngestEvent class InngestEventBuilder( var id: String?, var name: String?, - var data: Any?, + var data: Map?, private var user: Any?, private var ts: Long?, private var v: String? = null, @@ -53,7 +53,7 @@ class InngestEventBuilder( return this } - fun data(data: Any): InngestEventBuilder { + fun data(data: Map): InngestEventBuilder { this.data = data return this } @@ -91,6 +91,6 @@ class InngestEventBuilder( * in the order of which they were included in the request */ data class SendEventsResponse( - val ids: List, + val ids: Array, val status: Int, ) diff --git a/inngest/src/main/kotlin/com/inngest/Function.kt b/inngest/src/main/kotlin/com/inngest/Function.kt index f1a83171..366a136e 100644 --- a/inngest/src/main/kotlin/com/inngest/Function.kt +++ b/inngest/src/main/kotlin/com/inngest/Function.kt @@ -100,8 +100,8 @@ internal class InternalFunctionConfig * Includes event(s) and other run information */ data class FunctionContext( - val event: Event, - val events: List, + val event: InngestEvent, + val events: List, val runId: String, val fnId: String, val attempt: Int, diff --git a/inngest/src/main/kotlin/com/inngest/Inngest.kt b/inngest/src/main/kotlin/com/inngest/Inngest.kt index f5f9da63..249a12e4 100644 --- a/inngest/src/main/kotlin/com/inngest/Inngest.kt +++ b/inngest/src/main/kotlin/com/inngest/Inngest.kt @@ -1,8 +1,6 @@ package com.inngest import com.beust.klaxon.Klaxon -import java.io.IOException -import io.ktor.http.* import java.net.ConnectException class Inngest @@ -47,13 +45,15 @@ class Inngest val responseBody = response.body!!.charStream() try { - val sendEventsResponse = Klaxon().parse(responseBody) + val sendEventsResponse = Klaxon().parse(responseBody) if (sendEventsResponse != null) { return@lambda sendEventsResponse } } catch (e: Exception) { throw InngestSendEventInvalidResponseException(responseBody.toString()) } + // If we haven't successfully parsed and returned a valid SendEventsResponse + // by this point, throw an exception throw InngestSendEventInvalidResponseException(responseBody.toString()) } } catch (e: ConnectException) { diff --git a/inngest/src/main/kotlin/com/inngest/Step.kt b/inngest/src/main/kotlin/com/inngest/Step.kt index bee9d7ec..97030d44 100644 --- a/inngest/src/main/kotlin/com/inngest/Step.kt +++ b/inngest/src/main/kotlin/com/inngest/Step.kt @@ -31,7 +31,7 @@ class StepInterruptSleepException( class StepInterruptSendEventException( id: String, hashedId: String, - val eventIds: List, + val eventIds: Array, ) : StepInterruptException(id, hashedId, eventIds) class StepInterruptInvokeException( @@ -211,7 +211,7 @@ class Step( val hashedId = state.getHashFromId(id) try { - val stepState = state.getState>(hashedId, "event_ids") + val stepState = state.getState>(hashedId, "event_ids") if (stepState != null) { return SendEventsResponse(stepState, 200) @@ -219,8 +219,7 @@ class Step( throw Exception("step state expected sendEvent, got something else") } catch (e: StateNotFound) { val response = client.send(events) - //throw StepInterruptSendEventException(id, hashedId, response!!.ids) - throw StepInterruptSendEventException(id, hashedId, response.ids) + throw StepInterruptSendEventException(id, hashedId, response!!.ids) } } From 4f9f31f582b84815fc93f2dccd6adbc1e68b9faa Mon Sep 17 00:00:00 2001 From: Albert Chae Date: Thu, 12 Sep 2024 18:15:59 -0700 Subject: [PATCH 4/4] Refactor InngestEventBuilder - make name and data required params of the constructor - all the optional fields have setters - add tests - also modify user on InngestEvent to have a type of map matching data per https://github.com/inngest/inngest/blob/3f0b9cb28d38fa6f37abe82ded4734eea2f7dbc9/docs/SDK_SPEC.md?plain=1#L162 --- inngest/src/main/kotlin/com/inngest/Event.kt | 48 +++++-------------- .../com/inngest/InngestEventBuilderTest.kt | 26 ++++++++++ 2 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 inngest/src/test/kotlin/com/inngest/InngestEventBuilderTest.kt diff --git a/inngest/src/main/kotlin/com/inngest/Event.kt b/inngest/src/main/kotlin/com/inngest/Event.kt index b2f61b35..1e82ffa1 100644 --- a/inngest/src/main/kotlin/com/inngest/Event.kt +++ b/inngest/src/main/kotlin/com/inngest/Event.kt @@ -23,7 +23,7 @@ data class InngestEvent val name: String, val data: Map, @Json(serializeNull = false) - val user: Any? = null, + val user: Map? = null, @Json(serializeNull = false) val id: String? = null, @Json(serializeNull = false) @@ -36,48 +36,26 @@ data class InngestEvent * Construct a new Inngest Event via builder */ class InngestEventBuilder( - var id: String?, - var name: String?, - var data: Map?, - private var user: Any?, - private var ts: Long?, - private var v: String? = null, + val name: String, + val data: Map, ) { - fun id(id: String): InngestEventBuilder { - this.id = id - return this - } + private var id: String? = null + private var user: Map? = null + private var ts: Long? = null + private var v: String? = null - fun name(name: String): InngestEventBuilder { - this.name = name - return this - } + fun id(id: String): InngestEventBuilder = apply { this.id = id } - fun data(data: Map): InngestEventBuilder { - this.data = data - return this - } + fun ts(ts: Long): InngestEventBuilder = apply { this.ts = ts } - fun ts(ts: Long): InngestEventBuilder { - this.ts = ts - return this - } + fun user(user: Map) = apply { this.user = user } - fun v(v: String): InngestEventBuilder { - this.v = v - return this - } + fun v(v: String): InngestEventBuilder = apply { this.v = v } fun build(): InngestEvent { - if (name == null) { - throw IllegalArgumentException("name is required") - } - if (data == null) { - throw IllegalArgumentException("data is required") - } return InngestEvent( - name!!, - data!!, + name, + data, user, id, ts, diff --git a/inngest/src/test/kotlin/com/inngest/InngestEventBuilderTest.kt b/inngest/src/test/kotlin/com/inngest/InngestEventBuilderTest.kt new file mode 100644 index 00000000..6d69e2db --- /dev/null +++ b/inngest/src/test/kotlin/com/inngest/InngestEventBuilderTest.kt @@ -0,0 +1,26 @@ +package com.inngest + +import kotlin.test.Test +import kotlin.test.assertEquals + +class InngestEventBuilderTest { + @Test + fun constructorOnlyWithRequiredParameters() { + val event = + InngestEventBuilder("test-name", mapOf()) + .build() + assertEquals(InngestEvent("test-name", mapOf()), event) + } + + @Test + fun optionalParameters() { + val event = + InngestEventBuilder("test-name", mapOf()) + .user(mapOf("userId" to 5)) + .id("test-id") + .ts(100) + .v("1.0") + .build() + assertEquals(InngestEvent("test-name", mapOf(), mapOf("userId" to 5), "test-id", 100, "1.0"), event) + } +}