Skip to content

Commit

Permalink
Merge branch 'main' into add-more-http-ingress-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman authored Feb 1, 2024
2 parents 1925465 + 6157cbc commit abb20c5
Show file tree
Hide file tree
Showing 15 changed files with 262 additions and 94 deletions.
2 changes: 1 addition & 1 deletion backend/schema/metadataingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type MetadataIngress struct {
Pos Position `parser:"" protobuf:"1,optional"`

Type string `parser:"'ingress' @('http' | 'ftl')?" protobuf:"2"`
Type string `parser:"'ingress' @('http')?" protobuf:"2"`
Method string `parser:"@('GET' | 'POST' | 'PUT' | 'DELETE')" protobuf:"3"`
Path []IngressPathComponent `parser:"('/' @@)+" protobuf:"4"`
}
Expand Down
1 change: 1 addition & 0 deletions backend/schema/normalise.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func Normalise[T Node](n T) T {
c.Pos = zero

case *DataRef:
c.TypeParameters = normaliseSlice(c.TypeParameters)
c.Pos = zero

case *Field:
Expand Down
39 changes: 18 additions & 21 deletions backend/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ module todo {
calls todo.destroy
verb destroy(todo.DestroyRequest) todo.DestroyResponse
ingress ftl GET /todo/destroy/{id}
verb destroy(builtin.HttpRequest<todo.DestroyRequest>) builtin.HttpResponse<todo.DestroyResponse>
ingress http GET /todo/destroy/{id}
}
`
assert.Equal(t, normaliseString(expected), normaliseString(testSchema.String()))
Expand Down Expand Up @@ -119,7 +119,6 @@ func TestParserRoundTrip(t *testing.T) {
assert.NoError(t, err, "%s", testSchema.String())
actual, err = Validate(actual)
assert.NoError(t, err)
actual = Normalise(actual)
assert.Equal(t, Normalise(testSchema), Normalise(actual), "%s", testSchema.String())
}

Expand Down Expand Up @@ -230,8 +229,8 @@ func TestParsing(t *testing.T) {
message String
}
verb echo(echo.EchoRequest) echo.EchoResponse
ingress ftl GET /echo
verb echo(builtin.HttpRequest<echo.EchoRequest>) builtin.HttpResponse<echo.EchoResponse>
ingress http GET /echo
calls time.time
}
Expand All @@ -244,8 +243,8 @@ func TestParsing(t *testing.T) {
time Time
}
verb time(time.TimeRequest) time.TimeResponse
ingress ftl GET /time
verb time(builtin.HttpRequest<time.TimeRequest>) builtin.HttpResponse<time.TimeResponse>
ingress http GET /time
}
`,
expected: &Schema{
Expand All @@ -256,10 +255,10 @@ func TestParsing(t *testing.T) {
&Data{Name: "EchoResponse", Fields: []*Field{{Name: "message", Type: &String{}}}},
&Verb{
Name: "echo",
Request: &DataRef{Module: "echo", Name: "EchoRequest"},
Response: &DataRef{Module: "echo", Name: "EchoResponse"},
Request: &DataRef{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&DataRef{Module: "echo", Name: "EchoRequest"}}},
Response: &DataRef{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&DataRef{Module: "echo", Name: "EchoResponse"}}},
Metadata: []Metadata{
&MetadataIngress{Type: "ftl", Method: "GET", Path: []IngressPathComponent{&IngressPathLiteral{Text: "echo"}}},
&MetadataIngress{Type: "http", Method: "GET", Path: []IngressPathComponent{&IngressPathLiteral{Text: "echo"}}},
&MetadataCalls{Calls: []*VerbRef{{Module: "time", Name: "time"}}},
},
},
Expand All @@ -271,10 +270,10 @@ func TestParsing(t *testing.T) {
&Data{Name: "TimeResponse", Fields: []*Field{{Name: "time", Type: &Time{}}}},
&Verb{
Name: "time",
Request: &DataRef{Module: "time", Name: "TimeRequest"},
Response: &DataRef{Module: "time", Name: "TimeResponse"},
Request: &DataRef{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&DataRef{Module: "time", Name: "TimeRequest"}}},
Response: &DataRef{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&DataRef{Module: "time", Name: "TimeResponse"}}},
Metadata: []Metadata{
&MetadataIngress{Type: "ftl", Method: "GET", Path: []IngressPathComponent{&IngressPathLiteral{Text: "time"}}},
&MetadataIngress{Type: "http", Method: "GET", Path: []IngressPathComponent{&IngressPathLiteral{Text: "time"}}},
},
},
},
Expand Down Expand Up @@ -385,16 +384,14 @@ module todo {
}
verb create(todo.CreateRequest) todo.CreateResponse
calls todo.destroy
verb destroy(todo.DestroyRequest) todo.DestroyResponse
ingress ftl GET /todo/destroy/{id}
verb destroy(builtin.HttpRequest<todo.DestroyRequest>) builtin.HttpResponse<todo.DestroyResponse>
ingress http GET /todo/destroy/{id}
}
`
actual, err := ParseModuleString("", input)
assert.NoError(t, err)
actual = Normalise(actual)
fmt.Printf("Modules %v\n", Normalise(testSchema.Modules[1]))
fmt.Printf("Modules %v\n", Normalise(actual))
assert.Equal(t, Normalise(testSchema.Modules[1]), actual)
assert.Equal(t, Normalise(testSchema.Modules[1]), actual, assert.Exclude[Position])
}

var testSchema = MustValidate(&Schema{
Expand Down Expand Up @@ -433,11 +430,11 @@ var testSchema = MustValidate(&Schema{
Response: &DataRef{Module: "todo", Name: "CreateResponse"},
Metadata: []Metadata{&MetadataCalls{Calls: []*VerbRef{{Module: "todo", Name: "destroy"}}}}},
&Verb{Name: "destroy",
Request: &DataRef{Module: "todo", Name: "DestroyRequest"},
Response: &DataRef{Module: "todo", Name: "DestroyResponse"},
Request: &DataRef{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&DataRef{Module: "todo", Name: "DestroyRequest"}}},
Response: &DataRef{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&DataRef{Module: "todo", Name: "DestroyResponse"}}},
Metadata: []Metadata{
&MetadataIngress{
Type: "ftl",
Type: "http",
Method: "GET",
Path: []IngressPathComponent{
&IngressPathLiteral{Text: "todo"},
Expand Down
42 changes: 28 additions & 14 deletions examples/kotlin/ftl-module-api/src/main/kotlin/ftl/api/Api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ data class Todo(
val completed: Boolean = false,
)

typealias GetStatusRequest = Unit

data class GetStatusResponse(
val status: String,
)

data class GetTodoRequest(
val id: Int,
)

data class GetTodoResponse(
val todo: Todo?,
)

data class CreateTodoRequest(
val title: String,
)
Expand All @@ -44,32 +58,32 @@ class Api {
private val headers = mapOf("Content-Type" to arrayListOf("application/json"))

@Verb
@HttpIngress(Method.GET, "/api/status")
fun status(context: Context, req: HttpRequest): HttpResponse {
return HttpResponse(status = 200, headers = mapOf(), body = "OK".toByteArray())
@Ingress(Method.GET, "/api/status")
fun status(context: Context, req: HttpRequest<GetStatusRequest>): HttpResponse<GetStatusResponse> {
return HttpResponse<GetStatusResponse>(status = 200, headers = mapOf(), body = GetStatusResponse("OK"))
}

@Verb
@HttpIngress(Method.GET, "/api/todos/{todoId}")
fun getTodo(context: Context, req: HttpRequest): HttpResponse {
val todoId = req.pathParameters["todoId"]?.toIntOrNull()
@Ingress(Method.GET, "/api/todos/{id}")
fun getTodo(context: Context, req: HttpRequest<GetTodoRequest>): HttpResponse<GetTodoResponse> {
val todoId = req.pathParameters["id"]?.toIntOrNull()
val todo = todos[todoId]

return if (todo != null) {
HttpResponse(
HttpResponse<GetTodoResponse>(
status = 200,
headers = mapOf(),
body = gson.toJson(todo).toByteArray()
body = GetTodoResponse(todo)
)
} else {
HttpResponse(status = 404, headers = mapOf(), body = "Todo not found".toByteArray())
HttpResponse<GetTodoResponse>(status = 404, headers = mapOf(), body = GetTodoResponse(null))
}
}

@Verb
@HttpIngress(Method.POST, "/api/todos")
fun addTodo(context: Context, req: HttpRequest): HttpResponse {
val todoReq = gson.fromJson(req.body.toString(Charsets.UTF_8), CreateTodoRequest::class.java)
@Ingress(Method.POST, "/api/todos")
fun addTodo(context: Context, req: HttpRequest<CreateTodoRequest>): HttpResponse<CreateTodoResponse> {
val todoReq = req.body
val id = idCounter.incrementAndGet()
todos.put(
id, Todo(
Expand All @@ -78,10 +92,10 @@ class Api {
)
)

return HttpResponse(
return HttpResponse<CreateTodoResponse>(
status = 201,
headers = headers,
body = gson.toJson(CreateTodoResponse(id)).toByteArray(),
body = CreateTodoResponse(id),
)
}
}
12 changes: 9 additions & 3 deletions examples/time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package time
import (
"context"
"time"

"ftl/builtin"
)

type TimeRequest struct{}
Expand All @@ -14,7 +16,11 @@ type TimeResponse struct {
// Time returns the current time.
//
//ftl:verb
//ftl:ingress GET /timef
func Time(ctx context.Context, req TimeRequest) (TimeResponse, error) {
return TimeResponse{Time: time.Now()}, nil
//ftl:ingress GET /time
func Time(ctx context.Context, req builtin.HttpRequest[TimeRequest]) (builtin.HttpResponse[TimeResponse], error) {
return builtin.HttpResponse[TimeResponse]{
Status: 200,
Headers: map[string][]string{"Get": {"Header from FTL"}},
Body: TimeResponse{Time: time.Now()},
}, nil
}
6 changes: 5 additions & 1 deletion go-runtime/compile/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,13 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb, e
isVerb = true

case *directiveIngress:
typ := dir.Type
if typ == "" {
typ = "http"
}
metadata = append(metadata, &schema.MetadataIngress{
Pos: dir.Pos,
Type: dir.Type,
Type: typ,
Method: dir.Method,
Path: dir.Path,
})
Expand Down
14 changes: 9 additions & 5 deletions integration/testdata/kotlin/httpingress/Echo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import kotlin.String
import kotlin.Unit
import xyz.block.ftl.Alias
import xyz.block.ftl.Context
import xyz.block.ftl.HttpIngress
import xyz.block.ftl.Ingress
import xyz.block.ftl.Ingress.Type.HTTP
import xyz.block.ftl.Method
import xyz.block.ftl.Verb

Expand Down Expand Up @@ -47,9 +48,10 @@ data class DeleteResponse(

class Echo {
@Verb
@HttpIngress(
@Ingress(
Method.GET,
"/echo/users/{userID}/posts/{postID}",
HTTP
)
fun `get`(context: Context, req: HttpRequest<GetRequest>): HttpResponse<GetResponse> {
return HttpResponse<GetResponse>(
Expand All @@ -60,9 +62,10 @@ class Echo {
}

@Verb
@HttpIngress(
@Ingress(
Method.POST,
"/echo/users",
HTTP
)
fun post(context: Context, req: HttpRequest<PostRequest>): HttpResponse<PostResponse> {
return HttpResponse<PostResponse>(
Expand All @@ -73,9 +76,10 @@ class Echo {
}

@Verb
@HttpIngress(
@Ingress(
Method.PUT,
"/echo/users/{userID}",
HTTP
)
fun put(context: Context, req: HttpRequest<PutRequest>): HttpResponse<PutResponse> {
return HttpResponse<PutResponse>(
Expand All @@ -86,7 +90,7 @@ class Echo {
}

@Verb
@HttpIngress(Method.DELETE, "/echo/users/{userID}")
@Ingress(Method.DELETE, "/echo/users/{userID}", HTTP)
fun delete(context: Context, req: HttpRequest<DeleteRequest>): HttpResponse<DeleteResponse> {
return HttpResponse<DeleteResponse>(
status = 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package xyz.block.ftl.generator
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import xyz.block.ftl.Context
import xyz.block.ftl.HttpIngress
import xyz.block.ftl.Ignore
import xyz.block.ftl.Ingress
import xyz.block.ftl.v1.schema.*
Expand Down Expand Up @@ -109,9 +108,10 @@ class ModuleGenerator() {
verb.metadata.forEach { metadata ->
metadata.ingress?.let {
verbFunBuilder.addAnnotation(
AnnotationSpec.builder(if (it.type == "ftl") Ingress::class else HttpIngress::class)
AnnotationSpec.builder(Ingress::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()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public class TestModule()
metadata = listOf(
Metadata(
ingress = MetadataIngress(
type = "ftl",
type = "http",
path = listOf(IngressPathComponent(ingressPathLiteral = IngressPathLiteral(text = "test"))),
method = "GET"
)
Expand All @@ -196,6 +196,7 @@ import kotlin.Unit
import xyz.block.ftl.Context
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
Expand Down Expand Up @@ -225,6 +226,7 @@ public class TestModule() {
@Ingress(
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, ...)")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ enum class Method {
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Ingress(val method: Method, val path: String)
annotation class Ingress(val method: Method, val path: String, val type: Type = Type.HTTP) {
enum class Type {
HTTP
}
}

/**
* A field marked with Alias will be renamed to the specified name on ingress from external inputs.
Expand Down
Loading

0 comments on commit abb20c5

Please sign in to comment.