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 5a98242f..1e82ffa1 100644 --- a/inngest/src/main/kotlin/com/inngest/Event.kt +++ b/inngest/src/main/kotlin/com/inngest/Event.kt @@ -1,6 +1,11 @@ package com.inngest -data class Event( +import com.beust.klaxon.Json + +/** + * 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 +14,61 @@ 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 + @JvmOverloads + constructor( + val name: String, + val data: Map, + @Json(serializeNull = false) + val user: Map? = 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 + */ +class InngestEventBuilder( + val name: String, + val data: Map, +) { + private var id: String? = null + private var user: Map? = null + private var ts: Long? = null + private var v: String? = null + + fun id(id: String): InngestEventBuilder = apply { this.id = id } + + fun ts(ts: Long): InngestEventBuilder = apply { this.ts = ts } + + fun user(user: Map) = apply { this.user = user } + + fun v(v: String): InngestEventBuilder = apply { this.v = v } + + fun build(): InngestEvent { + return InngestEvent( + name, + data, + user, + id, + 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: 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 5190faee..249a12e4 100644 --- a/inngest/src/main/kotlin/com/inngest/Inngest.kt +++ b/inngest/src/main/kotlin/com/inngest/Inngest.kt @@ -1,7 +1,7 @@ package com.inngest import com.beust.klaxon.Klaxon -import java.io.IOException +import java.net.ConnectException class Inngest @JvmOverloads @@ -36,11 +36,58 @@ 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()) + } + // If we haven't successfully parsed and returned a valid SendEventsResponse + // by this point, throw an exception + 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..97030d44 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, @@ -234,7 +214,7 @@ class Step( 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) { 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) + } +}