diff --git a/.editorconfig b/.editorconfig index 1a424d53..9ba0cc58 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,8 +3,15 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_size = 4 +indent_size = 2 indent_style = space -insert_final_newline = false +insert_final_newline = true max_line_length = 120 +tab_width = 2 + +[*.{kt,kts}] +indent_size = 4 tab_width = 4 + +[Makefile] +indent_style = tab diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b1ba36ad --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: gradle + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..569daf19 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + name: Test (Java ${{ matrix.java }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: ["8", "11", "17", "21"] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ matrix.java }} + java-package: jdk + cache: gradle + + - name: Test + run: make test + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: ktlint + uses: ScaCap/action-ktlint@master + with: + fail_on_error: true + github_token: ${{ secrets.github_token }} + reporter: github-pr-review + + # TODO: run integration tests + # integration: diff --git a/Makefile b/Makefile index e244b187..9593ae7f 100644 --- a/Makefile +++ b/Makefile @@ -5,3 +5,11 @@ dev: .PHONY: test test: gradle test inngest-core:test + +.PHONY: lint +lint: + ktlint --color + +.PHONY: fmt +fmt: + ktlint -F diff --git a/inngest-core/src/main/kotlin/com/inngest/Comm.kt b/inngest-core/src/main/kotlin/com/inngest/Comm.kt index f67e91a7..bfa3d17f 100644 --- a/inngest-core/src/main/kotlin/com/inngest/Comm.kt +++ b/inngest-core/src/main/kotlin/com/inngest/Comm.kt @@ -45,16 +45,20 @@ data class CommError( ) class CommHandler(val functions: HashMap) { - private fun getHeaders(): Map { return mapOf( "Content-Type" to "application/json", - "x-inngest-sdk" to "inngest-kt:v0.0.1", // TODO - Get this from the build - "x-inngest-framework" to "ktor", // TODO - Pull this from options + // TODO - Get this from the build + "x-inngest-sdk" to "inngest-kt:v0.0.1", + // TODO - Pull this from options + "x-inngest-framework" to "ktor", ) } - fun callFunction(functionId: String, requestBody: String): CommResponse { + fun callFunction( + functionId: String, + requestBody: String, + ): CommResponse { println(requestBody) try { @@ -62,13 +66,14 @@ class CommHandler(val functions: HashMap) { // TODO - check that payload is not null and throw error val function = functions[functionId] ?: throw Exception("Function not found") - val ctx = FunctionContext( - event = payload!!.event, - events = payload.events, - runId = payload.ctx.runId, - fnId = payload.ctx.fnId, - attempt = payload.ctx.attempt, - ) + val ctx = + FunctionContext( + event = payload!!.event, + events = payload.events, + runId = payload.ctx.runId, + fnId = payload.ctx.fnId, + attempt = payload.ctx.attempt, + ) val result = function.call( @@ -85,19 +90,19 @@ class CommHandler(val functions: HashMap) { return CommResponse( body = Klaxon().toJsonString(body), statusCode = result.statusCode, - headers = getHeaders() + headers = getHeaders(), ) } catch (e: Exception) { val err = CommError( name = e.toString(), message = e.message, - stack = e.stackTrace.joinToString(separator = "\n") + stack = e.stackTrace.joinToString(separator = "\n"), ) return CommResponse( body = Klaxon().toJsonString(err), statusCode = ResultStatusCode.Error, - headers = getHeaders() + headers = getHeaders(), ) } } @@ -118,7 +123,7 @@ class CommHandler(val functions: HashMap) { sdk = "kotlin", url = "http://localhost:8080/api/inngest", v = "0.0.1", - functions = getFunctionConfigs() + functions = getFunctionConfigs(), ) val requestBody = Klaxon().toJsonString(requestPayload) @@ -145,7 +150,7 @@ class CommHandler(val functions: HashMap) { sdk = "kotlin", url = "http://localhost:8080/api/inngest", v = "0.0.1", - functions = getFunctionConfigs() + functions = getFunctionConfigs(), ) return Klaxon().toJsonString(requestPayload) } diff --git a/inngest-core/src/main/kotlin/com/inngest/Event.kt b/inngest-core/src/main/kotlin/com/inngest/Event.kt index 16f2f259..84580716 100644 --- a/inngest-core/src/main/kotlin/com/inngest/Event.kt +++ b/inngest-core/src/main/kotlin/com/inngest/Event.kt @@ -11,4 +11,4 @@ data class Event( data class EventAPIResponse( val ids: Array, val status: String, -) \ No newline at end of file +) diff --git a/inngest-core/src/main/kotlin/com/inngest/Function.kt b/inngest-core/src/main/kotlin/com/inngest/Function.kt index f192be69..cece458f 100644 --- a/inngest-core/src/main/kotlin/com/inngest/Function.kt +++ b/inngest-core/src/main/kotlin/com/inngest/Function.kt @@ -24,7 +24,6 @@ enum class OpCode { // FUTURE: WaitForEvent, StepNotFound, - } enum class ResultStatusCode(val code: Int, val message: String) { @@ -33,7 +32,6 @@ enum class ResultStatusCode(val code: Int, val message: String) { Error(500, "Function Error"), } - abstract class StepOp( // The hashed ID of a step open val id: String = "", @@ -69,7 +67,7 @@ data class FunctionConfig( val id: String, val name: String, val triggers: Array, - val steps: Map + val steps: Map, ) /** @@ -86,6 +84,7 @@ data class FunctionContext( ) // TODO - Determine if we should merge config + trigger + /** * A function that can be called by the Inngest system * @@ -94,11 +93,14 @@ data class FunctionContext( */ open class InngestFunction( val config: FunctionOptions, - val handler: (ctx: FunctionContext, step: Step) -> kotlin.Any? + val handler: (ctx: FunctionContext, step: Step) -> kotlin.Any?, ) { // TODO - Validate options and trigger - fun call(ctx: FunctionContext, requestBody: String): StepOp { + fun call( + ctx: FunctionContext, + requestBody: String, + ): StepOp { val state = State(requestBody) val step = Step(state) @@ -112,16 +114,15 @@ open class InngestFunction( id = "", name = "", op = OpCode.StepRun, - statusCode = ResultStatusCode.FunctionComplete + statusCode = ResultStatusCode.FunctionComplete, ) } catch (e: StepInterruptSleepException) { return StepOptions( opts = hashMapOf("duration" to e.data), - id = e.hashedId, name = e.id, op = OpCode.Sleep, - statusCode = ResultStatusCode.StepComplete + statusCode = ResultStatusCode.StepComplete, ) } catch (e: StepInterruptException) { // NOTE - Currently this error could be caught in the user's own function @@ -132,7 +133,7 @@ open class InngestFunction( id = e.hashedId, name = e.id, op = OpCode.StepRun, - statusCode = ResultStatusCode.StepComplete + statusCode = ResultStatusCode.StepComplete, ) } catch (e: StepInvalidStateTypeException) { // TODO - Handle this with the proper op code @@ -141,7 +142,7 @@ open class InngestFunction( id = e.hashedId, name = e.id, op = OpCode.StepStateFailed, - statusCode = ResultStatusCode.Error + statusCode = ResultStatusCode.Error, ) } } @@ -152,24 +153,25 @@ open class InngestFunction( name = config.name, triggers = config.triggers, steps = - mapOf( - "step" to - StepConfig( - id = "step", - name = "step", - retries = - mapOf( - "attempts" to 3 - ), // TODO - Pull from FunctionOptions - runtime = - hashMapOf( - "type" to "http", - // TODO - Create correct URL - "url" to - "http://localhost:8080/api/inngest?fnId=${config.id}&stepId=step" - ) - ) - ) + mapOf( + "step" to + StepConfig( + id = "step", + name = "step", + retries = + mapOf( + // TODO - Pull from FunctionOptions + "attempts" to 3, + ), + runtime = + hashMapOf( + "type" to "http", + // TODO - Create correct URL + "url" to + "http://localhost:8080/api/inngest?fnId=${config.id}&stepId=step", + ), + ), + ), ) } } diff --git a/inngest-core/src/main/kotlin/com/inngest/Inngest.kt b/inngest-core/src/main/kotlin/com/inngest/Inngest.kt index 8bceac7a..5256ef6c 100644 --- a/inngest-core/src/main/kotlin/com/inngest/Inngest.kt +++ b/inngest-core/src/main/kotlin/com/inngest/Inngest.kt @@ -1,16 +1,11 @@ package com.inngest -import com.beust.klaxon.Klaxon - -//import okhttp3.RequestBody.Companion.toRequestBody - +// import okhttp3.RequestBody.Companion.toRequestBody class Inngest { - constructor( app_id: String, - - ) { + ) { // TODO - Fetch INNGEST_EVENT_KEY env variable } @@ -26,4 +21,4 @@ class Inngest { // val body = Klaxon().parse(response) // return body; // } -} \ No newline at end of file +} diff --git a/inngest-core/src/main/kotlin/com/inngest/State.kt b/inngest-core/src/main/kotlin/com/inngest/State.kt index 49bdb291..8df9176f 100644 --- a/inngest-core/src/main/kotlin/com/inngest/State.kt +++ b/inngest-core/src/main/kotlin/com/inngest/State.kt @@ -1,14 +1,12 @@ package com.inngest -import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper import java.security.MessageDigest - class StateNotFound() : Throwable("State not found for id") class State(val payloadJson: String) { - fun getHashFromId(id: String): String { val bytes = id.toByteArray(Charsets.UTF_8) val digest = MessageDigest.getInstance("SHA-1") @@ -26,14 +24,13 @@ class State(val payloadJson: String) { val stepResult = node.path("steps").get(hashedId) ?: throw StateNotFound() if (stepResult.has("data")) { val dataNode = stepResult.get("data") - return mapper.treeToValue(dataNode, T::class.java); + return mapper.treeToValue(dataNode, T::class.java) } else if (stepResult.has("error")) { // TODO - Parse the error and throw it return null } // NOTE - Sleep steps will be stored as null // TODO - Check the state is actually null - return null; + return null } - -} \ No newline at end of file +} diff --git a/inngest-core/src/main/kotlin/com/inngest/Step.kt b/inngest-core/src/main/kotlin/com/inngest/Step.kt index 583b6e75..f8fdfe53 100644 --- a/inngest-core/src/main/kotlin/com/inngest/Step.kt +++ b/inngest-core/src/main/kotlin/com/inngest/Step.kt @@ -6,26 +6,29 @@ typealias MemoizedRecord = HashMap typealias MemoizedState = HashMap class StepInvalidStateTypeException(val id: String, val hashedId: String) : Throwable("Step execution interrupted") + class StepStateTypeMismatchException(val id: String, val hashedId: String) : Throwable("Step execution interrupted") open class StepInterruptException(val id: String, val hashedId: String, open val data: kotlin.Any?) : - Throwable("Interrupt $id") {} + Throwable("Interrupt $id") class StepInterruptSleepException(id: String, hashedId: String, override val data: String) : - StepInterruptException(id, hashedId, data) {} + StepInterruptException(id, hashedId, data) // TODO: Add name, stack, etc. if poss class StepError(message: String) : Exception(message) class Step(val state: State) { - /** * Run a function * * @param id unique step id for memoization * @param fn the function to run */ - inline fun run(id: String, fn: () -> T): T { + inline fun run( + id: String, + fn: () -> T, + ): T { val hashedId = state.getHashFromId(id) try { @@ -50,7 +53,10 @@ class Step(val state: State) { * @param id unique step id for memoization * @param duration the duration of time to sleep for */ - fun sleep(id: String, duration: Duration) { + fun sleep( + id: String, + duration: Duration, + ) { val hashedId = state.getHashFromId(id) try { @@ -66,4 +72,3 @@ class Step(val state: State) { } } } - diff --git a/inngest-core/src/test/kotlin/com/inngest/StateTest.kt b/inngest-core/src/test/kotlin/com/inngest/StateTest.kt index 4eaaaa93..e68645f6 100644 --- a/inngest-core/src/test/kotlin/com/inngest/StateTest.kt +++ b/inngest-core/src/test/kotlin/com/inngest/StateTest.kt @@ -8,21 +8,21 @@ import kotlin.test.assertNotNull data class DummyClass( @JsonProperty("sum") - val sum: Int + val sum: Int, ) internal class StateTest { - @Test - fun testGetHashedId(): Unit { + fun testGetHashedId() { val state = State("{}") val hashedId = state.getHashFromId("add-ten") assertEquals("a5b1e458ee54a384e87fff1486df43e9b3e0c4b8", hashedId) } @Test - fun testNoExistingState(): Unit { - val json = """ + fun testNoExistingState() { + val json = + """ { "steps": { "a5b1e458ee54a384e87fff1486df43e9b3e0c4b8": { @@ -30,7 +30,7 @@ internal class StateTest { } } } - """.trimIndent() + """.trimIndent() val state = State(json) val hashedId = state.getHashFromId("something-not-in-state") assertFailsWith { @@ -39,8 +39,9 @@ internal class StateTest { } @Test - fun testGetIntState(): Unit { - val json = """ + fun testGetIntState() { + val json = + """ { "steps": { "a5b1e458ee54a384e87fff1486df43e9b3e0c4b8": { @@ -48,7 +49,7 @@ internal class StateTest { } } } - """.trimIndent() + """.trimIndent() val state = State(json) val hashedId = state.getHashFromId("add-ten") val stepState = state.getState(hashedId) @@ -56,8 +57,9 @@ internal class StateTest { } @Test - fun testGetStateAsClass(): Unit { - val json = """ + fun testGetStateAsClass() { + val json = + """ { "steps": { "a5b1e458ee54a384e87fff1486df43e9b3e0c4b8": { @@ -65,7 +67,7 @@ internal class StateTest { } } } - """.trimIndent() + """.trimIndent() val state = State(json) val hashedId = state.getHashFromId("add-ten") val stepState = state.getState(hashedId) @@ -73,4 +75,3 @@ internal class StateTest { assertEquals(30, stepState.sum, "state value should be correctly deserialized") } } - diff --git a/inngest-test-server/src/main/kotlin/com/inngest/App.kt b/inngest-test-server/src/main/kotlin/com/inngest/App.kt index f4045c67..41c9e352 100644 --- a/inngest-test-server/src/main/kotlin/com/inngest/App.kt +++ b/inngest-test-server/src/main/kotlin/com/inngest/App.kt @@ -1,5 +1,6 @@ package com.inngest.testserver +import com.fasterxml.jackson.annotation.JsonProperty import com.inngest.CommHandler import com.inngest.FunctionOptions import com.inngest.FunctionTrigger @@ -11,7 +12,6 @@ import io.ktor.server.netty.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import com.fasterxml.jackson.annotation.JsonProperty import java.time.Duration data class IngestData(val message: String) @@ -28,10 +28,10 @@ fun Application.module() { val response = comm.callFunction(functionId, body) call.response.header( HttpHeaders.ContentType, - ContentType.Application.Json.toString() + ContentType.Application.Json.toString(), ) call.response.status( - HttpStatusCode(response.statusCode.code, response.statusCode.message) + HttpStatusCode(response.statusCode.code, response.statusCode.message), ) println("response: " + response.body) call.respond(response.body) @@ -61,7 +61,7 @@ val fn = FunctionOptions( id = "fn-id-slug", name = "My function!", - triggers = arrayOf(FunctionTrigger(event = "user.signup")) + triggers = arrayOf(FunctionTrigger(event = "user.signup")), ), ) { ctx, step -> val x = 10 @@ -98,7 +98,6 @@ val fn = val comm = CommHandler(functions = hashMapOf("fn-id-slug" to fn)) fun main() { - var port = 8080 println("Test server running on port " + port) diff --git a/inngest-test-server/src/test/kotlin/com/inngest/AppTest.kt b/inngest-test-server/src/test/kotlin/com/inngest/AppTest.kt index 0f91b53a..0aac0923 100644 --- a/inngest-test-server/src/test/kotlin/com/inngest/AppTest.kt +++ b/inngest-test-server/src/test/kotlin/com/inngest/AppTest.kt @@ -6,14 +6,6 @@ package io.inngest.testserver // import kotlin.test.Test // import kotlin.test.assertNotNull - - - - - - - - // class AppTest { // @Test fun appHasAGreeting() { // val classUnderTest = App()