diff --git a/examples/kotlin/ftl-module-ad/src/main/kotlin/ftl/ad/Ad.kt b/examples/kotlin/ftl-module-ad/src/main/kotlin/ftl/ad/Ad.kt index 5d2e7f44b5..eced970255 100644 --- a/examples/kotlin/ftl-module-ad/src/main/kotlin/ftl/ad/Ad.kt +++ b/examples/kotlin/ftl-module-ad/src/main/kotlin/ftl/ad/Ad.kt @@ -5,8 +5,7 @@ import com.google.gson.reflect.TypeToken import ftl.builtin.HttpRequest import ftl.builtin.HttpResponse import xyz.block.ftl.Context -import xyz.block.ftl.Ingress -import xyz.block.ftl.Ingress.Type.HTTP +import xyz.block.ftl.HttpIngress import xyz.block.ftl.Method import xyz.block.ftl.Verb import java.util.* @@ -19,7 +18,7 @@ class AdModule { private val database: Map = loadDatabase() @Verb - @Ingress(Method.GET, "/get", HTTP) + @HttpIngress(Method.GET, "/get") fun get(context: Context, req: HttpRequest): HttpResponse { val ads: List = when { req.body.contextKeys != null -> contextualAds(req.body.contextKeys) diff --git a/examples/kotlin/ftl-module-api/src/main/kotlin/ftl/api/Api.kt b/examples/kotlin/ftl-module-api/src/main/kotlin/ftl/api/Api.kt index 82ff83deb3..c48d46bce4 100644 --- a/examples/kotlin/ftl-module-api/src/main/kotlin/ftl/api/Api.kt +++ b/examples/kotlin/ftl-module-api/src/main/kotlin/ftl/api/Api.kt @@ -58,13 +58,13 @@ class Api { private val headers = mapOf("Content-Type" to arrayListOf("application/json")) @Verb - @Ingress(Method.GET, "/api/status") + @HttpIngress(Method.GET, "/api/status") fun status(context: Context, req: HttpRequest): HttpResponse { return HttpResponse(status = 200, headers = mapOf(), body = GetStatusResponse("OK")) } @Verb - @Ingress(Method.GET, "/api/todos/{id}") + @HttpIngress(Method.GET, "/api/todos/{id}") fun getTodo(context: Context, req: HttpRequest): HttpResponse { val todoId = req.pathParameters["id"]?.toIntOrNull() val todo = todos[todoId] @@ -81,7 +81,7 @@ class Api { } @Verb - @Ingress(Method.POST, "/api/todos") + @HttpIngress(Method.POST, "/api/todos") fun addTodo(context: Context, req: HttpRequest): HttpResponse { val todoReq = req.body val id = idCounter.incrementAndGet() diff --git a/examples/kotlin/ftl-module-echo/src/main/kotlin/ftl/echo/Echo.kt b/examples/kotlin/ftl-module-echo/src/main/kotlin/ftl/echo/Echo.kt index 081436ed59..b5cd156d9d 100644 --- a/examples/kotlin/ftl-module-echo/src/main/kotlin/ftl/echo/Echo.kt +++ b/examples/kotlin/ftl-module-echo/src/main/kotlin/ftl/echo/Echo.kt @@ -3,8 +3,6 @@ package ftl.echo import ftl.time.TimeModuleClient import ftl.time.TimeRequest import xyz.block.ftl.Context -import xyz.block.ftl.Ingress -import xyz.block.ftl.Ingress.Type.HTTP import xyz.block.ftl.Method import xyz.block.ftl.Verb diff --git a/integration/testdata/kotlin/httpingress/Echo.kt b/integration/testdata/kotlin/httpingress/Echo.kt index 80b3c3fde6..bd54397470 100644 --- a/integration/testdata/kotlin/httpingress/Echo.kt +++ b/integration/testdata/kotlin/httpingress/Echo.kt @@ -6,8 +6,7 @@ import kotlin.String import kotlin.Unit import xyz.block.ftl.Alias import xyz.block.ftl.Context -import xyz.block.ftl.Ingress -import xyz.block.ftl.Ingress.Type.HTTP +import xyz.block.ftl.HttpIngress import xyz.block.ftl.Method import xyz.block.ftl.Verb @@ -50,8 +49,8 @@ typealias HtmlRequest = Unit class Echo { @Verb - @Ingress( - Method.GET, "/users/{userID}/posts/{postID}", HTTP) + @HttpIngress( + Method.GET, "/users/{userID}/posts/{postID}") fun `get`(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -64,7 +63,7 @@ class Echo { } @Verb - @Ingress(Method.POST, "/users", HTTP) + @HttpIngress(Method.POST, "/users") fun post(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 201, @@ -74,7 +73,7 @@ class Echo { } @Verb - @Ingress(Method.PUT, "/users/{userId}", HTTP) + @HttpIngress(Method.PUT, "/users/{userId}") fun put(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -84,7 +83,7 @@ class Echo { } @Verb - @Ingress(Method.DELETE, "/users/{userId}", HTTP) + @HttpIngress(Method.DELETE, "/users/{userId}") fun delete(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -94,7 +93,7 @@ class Echo { } @Verb - @Ingress(Method.GET, "/html", HTTP) + @HttpIngress(Method.GET, "/html") fun html(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -104,7 +103,7 @@ class Echo { } @Verb - @Ingress(Method.POST, "/bytes", HTTP) + @HttpIngress(Method.POST, "/bytes") fun bytes(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -114,7 +113,7 @@ class Echo { } @Verb - @Ingress(Method.GET, "/empty", HTTP) + @HttpIngress(Method.GET, "/empty") fun empty(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, @@ -124,7 +123,7 @@ class Echo { } @Verb - @Ingress(Method.GET, "/string", HTTP) + @HttpIngress(Method.GET, "/string") fun string(context: Context, req: HttpRequest): HttpResponse { return HttpResponse( status = 200, diff --git a/kotlin-runtime/ftl-generator/src/main/kotlin/xyz/block/ftl/generator/ModuleGenerator.kt b/kotlin-runtime/ftl-generator/src/main/kotlin/xyz/block/ftl/generator/ModuleGenerator.kt index 161089313c..32763d34d9 100644 --- a/kotlin-runtime/ftl-generator/src/main/kotlin/xyz/block/ftl/generator/ModuleGenerator.kt +++ b/kotlin-runtime/ftl-generator/src/main/kotlin/xyz/block/ftl/generator/ModuleGenerator.kt @@ -4,7 +4,7 @@ import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import xyz.block.ftl.Context import xyz.block.ftl.Ignore -import xyz.block.ftl.Ingress +import xyz.block.ftl.HttpIngress import xyz.block.ftl.v1.schema.* import java.io.File import java.nio.file.Path @@ -108,10 +108,9 @@ class ModuleGenerator() { verb.metadata.forEach { metadata -> metadata.ingress?.let { verbFunBuilder.addAnnotation( - AnnotationSpec.builder(Ingress::class) + AnnotationSpec.builder(HttpIngress::class) .addMember("%T", ClassName("xyz.block.ftl.Method", it.method.replaceBefore(".", ""))) .addMember("%S", ingressPathString(it.path)) - .addMember("%T", ClassName("xyz.block.ftl.Ingress.Type", it.type.uppercase().replaceBefore(".", ""))) .build() ) } diff --git a/kotlin-runtime/ftl-generator/src/test/kotlin/xyz/block/ftl/generator/ModuleGeneratorTest.kt b/kotlin-runtime/ftl-generator/src/test/kotlin/xyz/block/ftl/generator/ModuleGeneratorTest.kt index ad10e6c80d..702adc837e 100644 --- a/kotlin-runtime/ftl-generator/src/test/kotlin/xyz/block/ftl/generator/ModuleGeneratorTest.kt +++ b/kotlin-runtime/ftl-generator/src/test/kotlin/xyz/block/ftl/generator/ModuleGeneratorTest.kt @@ -194,9 +194,8 @@ package ftl.test import kotlin.Unit import xyz.block.ftl.Context +import xyz.block.ftl.HttpIngress import xyz.block.ftl.Ignore -import xyz.block.ftl.Ingress -import xyz.block.ftl.Ingress.Type.HTTP import xyz.block.ftl.Method.GET import xyz.block.ftl.Verb @@ -223,10 +222,9 @@ public class TestModule() { * TestIngressVerb comments */ @Verb - @Ingress( + @HttpIngress( GET, "/test", - HTTP, ) public fun TestIngressVerb(context: Context, req: TestRequest): TestResponse = throw NotImplementedError("Verb stubs should not be called directly, instead use context.call(TestModule::TestIngressVerb, ...)") diff --git a/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/Ingress.kt b/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/HttpIngress.kt similarity index 80% rename from kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/Ingress.kt rename to kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/HttpIngress.kt index a1e95e2f48..80fe1303da 100644 --- a/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/Ingress.kt +++ b/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/HttpIngress.kt @@ -10,11 +10,7 @@ enum class Method { @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented -annotation class Ingress(val method: Method, val path: String, val type: Type = Type.HTTP) { - enum class Type { - HTTP - } -} +annotation class HttpIngress(val method: Method, val path: String) /** * A field marked with Alias will be renamed to the specified name on ingress from external inputs. diff --git a/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRule.kt b/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRule.kt index ae5da8b082..14f86b674b 100644 --- a/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRule.kt +++ b/kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRule.kt @@ -244,7 +244,7 @@ class SchemaExtractor( private fun extractIngress(requestType: Type, responseType: Type): MetadataIngress? { return verb.annotationEntries.firstOrNull { - bindingContext.get(BindingContext.ANNOTATION, it)?.fqName?.asString() == Ingress::class.qualifiedName + bindingContext.get(BindingContext.ANNOTATION, it)?.fqName?.asString() == HttpIngress::class.qualifiedName }?.let { annotationEntry -> val sourcePos = annotationEntry.getLineAndColumn() require(requestType.dataRef != null) { @@ -253,52 +253,38 @@ class SchemaExtractor( require(responseType.dataRef != null) { "$sourcePos ingress ${verb.name} response must be a data class" } + require(requestType.dataRef.compare("builtin", "HttpRequest")) { + "$sourcePos @HttpIngress-annotated ${verb.name} request must be ftl.builtin.HttpRequest" + } + require(responseType.dataRef.compare("builtin", "HttpResponse")) { + "$sourcePos @HttpIngress-annotated ${verb.name} response must be ftl.builtin.HttpResponse" + } require(annotationEntry.valueArguments.size >= 2) { - "$sourcePos ${verb.name} @Ingress annotation requires at least 2 arguments" + "$sourcePos ${verb.name} @HttpIngress annotation requires at least 2 arguments" } - var typeArg = Ingress.Type.HTTP.name - var pathArg: List? = null - var methodArg: String? = null - annotationEntry.valueArguments.map { arg -> + val args = annotationEntry.valueArguments.partition { arg -> // Method arg is named "method" or is of type xyz.block.ftl.Method (in the case where args are // positional rather than named). - if (arg.getArgumentName()?.asName?.asString() == "method" - || arg.getArgumentExpression()?.getType(bindingContext)?.fqNameOrNull() - ?.asString() == Method::class.qualifiedName - ) { - methodArg = arg.getArgumentExpression()?.text?.substringAfter(".") - // Type arg is named "type" or is of type xyz.block.ftl.Ingress.Type. - } else if (arg.getArgumentName()?.asName?.asString() == "type" - || arg.getArgumentExpression()?.getType(bindingContext)?.fqNameOrNull() - ?.asString() == Ingress.Type::class.qualifiedName - ) { - typeArg = requireNotNull(arg.getArgumentExpression()?.text?.substringAfter(".")) { - "$sourcePos Could not extract type from ${verb.name} @Ingress annotation" - } - // Path arg is named "path" or is of type kotlin.String. - } else if (arg.getArgumentName()?.asName?.asString() == "path" + arg.getArgumentName()?.asName?.asString() == "method" || arg.getArgumentExpression()?.getType(bindingContext)?.fqNameOrNull() - ?.asString() == String::class.qualifiedName - ) { - pathArg = arg.getArgumentExpression()?.text?.let { extractPathComponents(it.trim('\"')) } - } + ?.asString() == Method::class.qualifiedName } - // If it's HTTP ingress, validate the signature. - if (typeArg == Ingress.Type.HTTP.name) { - require(requestType.dataRef.compare("builtin", "HttpRequest")) { - "$sourcePos HTTP @Ingress-annotated ${verb.name} request must be ftl.builtin.HttpRequest" - } - require(responseType.dataRef.compare("builtin", "HttpResponse")) { - "$sourcePos HTTP @Ingress-annotated ${verb.name} response must be ftl.builtin.HttpResponse" - } + val methodArg = requireNotNull(args.first.single().getArgumentExpression()?.text?.substringAfter(".")) { + "Could not extract method from ${verb.name} @HttpIngress annotation" } + val pathArg = requireNotNull(args.second.single().getArgumentExpression()?.text?.let { + extractPathComponents(it.trim('\"')) + }) { + "Could not extract path from ${verb.name} @HttpIngress annotation" + } + MetadataIngress( - path = requireNotNull(pathArg) { "Could not extract path from ${verb.name} @Ingress annotation" }, - method = requireNotNull(methodArg) { "Could not extract method from ${verb.name} @Ingress annotation" }, - type = typeArg.lowercase(), + type = "http", + path = pathArg, + method = methodArg, pos = sourcePos.toPosition(verb.containingKtFile.name), ) } diff --git a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/registry/RegistryTest.kt b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/registry/RegistryTest.kt index 3269a52131..fa353e7d55 100644 --- a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/registry/RegistryTest.kt +++ b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/registry/RegistryTest.kt @@ -23,7 +23,7 @@ class RenamedVerb { class ExampleVerb { @Verb - @Ingress(Method.GET, "/test") + @HttpIngress(Method.GET, "/test") fun verb(context: Context, req: VerbRequest): VerbResponse { return VerbResponse("test") } diff --git a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRuleTest.kt b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRuleTest.kt index e67adecda2..2df60335c5 100644 --- a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRuleTest.kt +++ b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/ExtractSchemaRuleTest.kt @@ -37,7 +37,7 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { import ftl.time.TimeResponse import xyz.block.ftl.Alias import xyz.block.ftl.Context - import xyz.block.ftl.Ingress + import xyz.block.ftl.HttpIngress import xyz.block.ftl.Method import xyz.block.ftl.Verb @@ -61,7 +61,7 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { */ @Throws(InvalidInput::class) @Verb - @Ingress(Method.GET, "/echo") + @HttpIngress(Method.GET, "/echo") fun echo(context: Context, req: HttpRequest>): HttpResponse { callTime(context) @@ -256,7 +256,7 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { import ftl.time.TimeRequest import ftl.time.TimeResponse import xyz.block.ftl.Context - import xyz.block.ftl.Ingress + import xyz.block.ftl.HttpIngress import xyz.block.ftl.Method import xyz.block.ftl.Verb @@ -279,7 +279,7 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { */ @Throws(InvalidInput::class) @Verb - @Ingress(Method.GET, "/echo") + @HttpIngress(Method.GET, "/echo") fun echo(context: Context, req: EchoRequest): EchoResponse { callTime(context) return EchoResponse(messages = listOf(EchoMessage(message = "Hello!"))) @@ -301,7 +301,7 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { package ftl.echo import xyz.block.ftl.Context - import xyz.block.ftl.Ingress + import xyz.block.ftl.HttpIngress import xyz.block.ftl.Method import xyz.block.ftl.Verb @@ -320,13 +320,13 @@ internal class ExtractSchemaRuleTest(private val env: KotlinCoreEnvironment) { */ @Throws(InvalidInput::class) @Verb - @Ingress(Method.GET, "/echo") + @HttpIngress(Method.GET, "/echo") fun echo(context: Context, req: EchoRequest): EchoResponse { return EchoResponse(messages = listOf(EchoMessage(message = "Hello!"))) } } """ - assertThrows(message = "HTTP @Ingress-annotated echo request must be ftl.builtin.HttpRequest") { + assertThrows(message = "@HttpIngress-annotated echo request must be ftl.builtin.HttpRequest") { ExtractSchemaRule(Config.empty).compileAndLintWithContext(env, code) } } diff --git a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/testdata/dependencies/TimeModuleClient.kt b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/testdata/dependencies/TimeModuleClient.kt index 2a3abaae48..8ba396fa35 100644 --- a/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/testdata/dependencies/TimeModuleClient.kt +++ b/kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/schemaextractor/testdata/dependencies/TimeModuleClient.kt @@ -21,7 +21,7 @@ public class TimeModuleClient() { * Time returns the current time. */ @Verb - @Ingress( + @HttpIngress( GET, "/time", )