From 06a20eb8379e17b341f598bc29a05250093406cf Mon Sep 17 00:00:00 2001 From: Elizabeth Worstell Date: Wed, 25 Sep 2024 17:43:23 -0700 Subject: [PATCH] feat: support injected verb clients in Go https://hackmd.io/OULeRFQQQvaMURysN27eEw --- backend/schema/schema.go | 14 +- backend/schema/validate.go | 2 +- examples/go/time/go.mod | 13 + examples/go/time/go.sum | 200 +++++++++ .../.ftl.tmpl/go/main/main.go.tmpl | 30 +- .../compile/build-template/types.ftl.go.tmpl | 41 +- go-runtime/compile/build.go | 421 +++++++++++++----- .../external_module.go.tmpl | 12 + .../compile/testdata/go/external/go.mod | 11 + .../compile/testdata/go/external/go.sum | 200 +++++++++ .../ftl/ftltest/testdata/go/verbtypes/go.mod | 14 + .../ftl/ftltest/testdata/go/verbtypes/go.sum | 8 + .../testdata/go/verbtypes/types.ftl.go | 47 ++ go-runtime/ftl/reflection/reflection.go | 6 + go-runtime/ftl/reflection/singleton.go | 4 + go-runtime/ftl/reflection/type_registry.go | 2 + go-runtime/ftl/reflection/verb.go | 92 ++++ go-runtime/ftl/testdata/go/echo/echo.go | 4 +- go-runtime/schema/common/common.go | 67 +++ go-runtime/schema/common/fact.go | 28 +- go-runtime/schema/enum/analyzer.go | 25 +- go-runtime/schema/finalize/analyzer.go | 25 +- go-runtime/schema/metadata/analyzer.go | 3 + go-runtime/schema/schema_test.go | 3 +- go-runtime/schema/testdata/named/go.mod | 13 + go-runtime/schema/testdata/named/go.sum | 200 +++++++++ go-runtime/schema/transitive/analyzer.go | 9 +- go-runtime/schema/verb/analyzer.go | 81 +++- go-runtime/server/server.go | 134 +++++- internal/buildengine/stubs_test.go | 16 + internal/buildengine/testdata/alpha/alpha.go | 4 +- internal/buildengine/testdata/alpha/go.mod | 14 + internal/buildengine/testdata/alpha/go.sum | 26 ++ .../buildengine/testdata/alpha/pkg/pkg.go | 6 +- .../testdata/type_registry_main.go | 37 +- 35 files changed, 1612 insertions(+), 200 deletions(-) create mode 100644 go-runtime/ftl/ftltest/testdata/go/verbtypes/types.ftl.go create mode 100644 go-runtime/ftl/reflection/verb.go diff --git a/backend/schema/schema.go b/backend/schema/schema.go index adcc26e4dd..5ef240a4be 100644 --- a/backend/schema/schema.go +++ b/backend/schema/schema.go @@ -93,18 +93,24 @@ func (s *Schema) resolveToDataMonomorphised(n Node, parent Node) (*Data, error) } } -// Resolve a reference to a declaration. -func (s *Schema) Resolve(ref *Ref) optional.Option[Decl] { +// ResolveWithModule a reference to a declaration and its module. +func (s *Schema) ResolveWithModule(ref *Ref) (optional.Option[Decl], optional.Option[*Module]) { for _, module := range s.Modules { if module.Name == ref.Module { for _, decl := range module.Decls { if decl.GetName() == ref.Name { - return optional.Some(decl) + return optional.Some(decl), optional.Some(module) } } } } - return optional.None[Decl]() + return optional.None[Decl](), optional.None[*Module]() +} + +// Resolve a reference to a declaration. +func (s *Schema) Resolve(ref *Ref) optional.Option[Decl] { + decl, _ := s.ResolveWithModule(ref) + return decl } // ResolveToType resolves a reference to a declaration of the given type. diff --git a/backend/schema/validate.go b/backend/schema/validate.go index e5be9b0e54..e4694d0407 100644 --- a/backend/schema/validate.go +++ b/backend/schema/validate.go @@ -295,7 +295,7 @@ func ValidateModule(module *Module) error { switch n := n.(type) { case *Ref: mdecl := scopes.Resolve(*n) - if mdecl == nil && n.Module == "" { + if mdecl == nil && (n.Module == "" || n.Module == module.Name) { merr = append(merr, errorf(n, "unknown reference %q, is the type annotated and exported?", n)) } if mdecl != nil { diff --git a/examples/go/time/go.mod b/examples/go/time/go.mod index 52d4f4c4aa..06b1558e4d 100644 --- a/examples/go/time/go.mod +++ b/examples/go/time/go.mod @@ -3,3 +3,16 @@ module ftl/time go 1.23.0 replace github.com/TBD54566975/ftl => ../../.. + +require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 + +require ( + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/examples/go/time/go.sum b/examples/go/time/go.sum index e69de29bb2..8adb24eab1 100644 --- a/examples/go/time/go.sum +++ b/examples/go/time/go.sum @@ -0,0 +1,200 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +github.com/TBD54566975/scaffolder v1.1.0 h1:R92zjC4XiS/lGCxJ8Ebn93g8gC0LU9qo06AAKo9cEJE= +github.com/TBD54566975/scaffolder v1.1.0/go.mod h1:dRi67GryEhZ5u0XRSiR294SYaqAfnCkZ7u3rmc4W6iI= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl b/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl index 7fcfec4380..b920e4e985 100644 --- a/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl +++ b/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl @@ -10,19 +10,29 @@ import ( {{.}} {{- end}} ) -{{- if or .SumTypes .ExternalTypes }} +{{- if or .SumTypes .ExternalTypes $verbs }} func init() { reflection.Register( {{- range .SumTypes}} reflection.SumType[{{.TypeName}}]( {{- range .Variants}} - *new({{.Name}}), + *new({{.TypeName}}), {{- end}} ), {{- end}} {{- range .ExternalTypes}} reflection.ExternalType(*new({{.TypeName}})), +{{- end}} +{{- range $verbs}} + reflection.ProvideResourcesForVerb( + {{.TypeName}}, + {{- range .Resources}} + {{- with getVerbClient . }} + server.VerbClient[{{.TypeName}}, {{.Request.TypeName}}, {{.Response.TypeName}}](), + {{- end }} + {{- end}} + ), {{- end}} ) } @@ -31,15 +41,15 @@ func init() { func main() { verbConstructor := server.NewUserVerbServer("{{.ProjectName}}", "{{$name}}", {{- range $verbs }} - {{- if and .HasRequest .HasResponse}} - server.HandleCall({{.TypeName}}), - {{- else if .HasRequest}} - server.HandleSink({{.TypeName}}), - {{- else if .HasResponse}} - server.HandleSource({{.TypeName}}), - {{- else}} + {{- if and (eq .Request.TypeName "ftl.Unit") (eq .Response.TypeName "ftl.Unit") }} server.HandleEmpty({{.TypeName}}), - {{- end}} + {{- else if eq .Request.TypeName "ftl.Unit" }} + server.HandleSource[{{.Response.TypeName}}]({{.TypeName}}), + {{- else if eq .Response.TypeName "ftl.Unit" }} + server.HandleSink[{{.Request.TypeName}}]({{.TypeName}}), + {{- else }} + server.HandleCall[{{.Request.TypeName}}, {{.Response.TypeName}}]({{.TypeName}}), + {{- end -}} {{- end}} ) plugin.Start(context.Background(), "{{$name}}", verbConstructor, ftlv1connect.VerbServiceName, ftlv1connect.NewVerbServiceHandler) diff --git a/go-runtime/compile/build-template/types.ftl.go.tmpl b/go-runtime/compile/build-template/types.ftl.go.tmpl index 3cfd46e0c0..da162d4f93 100644 --- a/go-runtime/compile/build-template/types.ftl.go.tmpl +++ b/go-runtime/compile/build-template/types.ftl.go.tmpl @@ -1,12 +1,11 @@ -{{- $moduleName := .Name -}} {{- $verbs := .Verbs -}} {{- $name := .Name -}} {{- with .TypesCtx -}} +{{- $moduleName := .MainModulePkg -}} // Code generated by FTL. DO NOT EDIT. package {{$name}} -{{- if or .SumTypes .ExternalTypes }} {{ if .Imports -}} import ( {{- range .Imports }} @@ -15,17 +14,53 @@ import ( ) {{- end }} +{{ range $verbs -}} + {{ $req := .Request.LocalTypeName -}} + {{ $resp := .Response.LocalTypeName -}} + + {{ if and (eq .Request.TypeName "ftl.Unit") (eq .Response.TypeName "ftl.Unit")}} +type {{.Name|title}}Client func(context.Context) error + {{- else if eq .Request.TypeName "ftl.Unit" }} +type {{.Name|title}}Client func(context.Context) ({{$resp}}, error) + {{- else if eq .Response.TypeName "ftl.Unit" }} +type {{.Name|title}}Client func(context.Context, {{$req}}) error + {{- else }} +type {{.Name|title}}Client func(context.Context, {{$req}}) ({{$resp}}, error) + {{- end }} +{{ end -}} + +{{- if or .SumTypes .ExternalTypes $verbs }} func init() { reflection.Register( {{- range .SumTypes}} reflection.SumType[{{ trimModuleQualifier $moduleName .TypeName }}]( {{- range .Variants}} - *new({{ trimModuleQualifier $moduleName .Name }}), + *new({{ trimModuleQualifier $moduleName .TypeName }}), {{- end}} ), {{- end}} {{- range .ExternalTypes}} reflection.ExternalType(*new({{.TypeName}})), +{{- end}} +{{- range $verbs}} + reflection.ProvideResourcesForVerb( + {{ trimModuleQualifier $moduleName .TypeName }}, + {{- range .Resources}} + {{- with getVerbClient . }} + {{ $verb := trimModuleQualifier $moduleName .TypeName -}} + + {{ if and (eq .Request.TypeName "ftl.Unit") (eq .Response.TypeName "ftl.Unit")}} + server.EmptyClient[{{$verb}}](), + {{- else if eq .Request.TypeName "ftl.Unit" }} + server.SourceClient[{{$verb}}, {{.Response.LocalTypeName}}](), + {{- else if eq .Response.TypeName "ftl.Unit" }} + server.SinkClient[{{$verb}}, {{.Request.LocalTypeName}}](), + {{- else }} + server.VerbClient[{{$verb}}, {{.Request.LocalTypeName}}, {{.Response.LocalTypeName}}](), + {{- end }} + {{- end }} + {{- end}} + ), {{- end}} ) } diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index f9b38e7389..6ba19b7e6d 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -59,9 +59,9 @@ type mainModuleContext struct { TypesCtx typesFileContext } -func (c mainModuleContext) withImports() mainModuleContext { +func (c mainModuleContext) withImports(mainModuleImport string) mainModuleContext { c.MainCtx.Imports = c.generateMainImports() - c.TypesCtx.Imports = c.generateTypesImports() + c.TypesCtx.Imports = c.generateTypesImports(mainModuleImport) return c } @@ -76,16 +76,12 @@ func (c mainModuleContext) generateMainImports() []string { } for _, v := range c.Verbs { - imports.Add(v.importStatement()) + imports.Append(verbImports(v)...) } for _, st := range c.MainCtx.SumTypes { imports.Add(st.importStatement()) - for _, v := range st.Variants { - if i := strings.LastIndex(v.Type, "."); i != -1 { - lessTypeName := strings.TrimSuffix(v.Type, v.Type[i:]) - imports.Add(strconv.Quote(lessTypeName)) - } + imports.Add(v.importStatement()) } } for _, e := range c.MainCtx.ExternalTypes { @@ -94,30 +90,73 @@ func (c mainModuleContext) generateMainImports() []string { return formatGoImports(imports.ToSlice()) } -func (c mainModuleContext) generateTypesImports() []string { +func (c mainModuleContext) generateTypesImports(mainModuleImport string) []string { imports := sets.NewSet[string]() if len(c.TypesCtx.SumTypes) > 0 || len(c.TypesCtx.ExternalTypes) > 0 { imports.Add(`"github.com/TBD54566975/ftl/go-runtime/ftl/reflection"`) } + if len(c.Verbs) > 0 { + imports.Add(`"context"`) + } for _, st := range c.TypesCtx.SumTypes { - // if import path is more than 2 dirs deep, it's a subpackage - if len(strings.Split(st.importPath, "/")) > 2 { - imports.Add(st.importStatement()) - } + imports.Add(st.importStatement()) for _, v := range st.Variants { - if i := strings.LastIndex(v.Type, "."); i != -1 { - lessTypeName := strings.TrimSuffix(v.Type, v.Type[i:]) - // if import path is more than 2 dirs deep, it's a subpackage - if len(strings.Split(lessTypeName, "/")) > 2 { - imports.Add(strconv.Quote(lessTypeName)) - } - } + imports.Add(v.importStatement()) } } for _, et := range c.TypesCtx.ExternalTypes { imports.Add(et.importStatement()) } - return formatGoImports(imports.ToSlice()) + for _, v := range c.Verbs { + imports.Append(verbImports(v)...) + } + + var filteredImports []string + for _, im := range imports.ToSlice() { + if im == mainModuleImport { + continue + } + filteredImports = append(filteredImports, im) + } + return formatGoImports(filteredImports) +} + +func typeImports(t goSchemaType) []string { + imports := sets.NewSet[string]() + if nt, ok := t.nativeType.Get(); ok { + imports.Add(nt.importStatement()) + } + for _, c := range t.children { + imports.Append(typeImports(c)...) + } + return imports.ToSlice() +} + +func verbImports(v goVerb) []string { + imports := sets.NewSet[string]() + imports.Add(v.importStatement()) + imports.Add(`"github.com/TBD54566975/ftl/go-runtime/ftl/reflection"`) + + if nt, ok := v.Request.nativeType.Get(); ok && v.Request.TypeName != "ftl.Unit" { + imports.Add(nt.importStatement()) + } + if nt, ok := v.Response.nativeType.Get(); ok && v.Response.TypeName != "ftl.Unit" { + imports.Add(nt.importStatement()) + } + for _, r := range v.Request.children { + imports.Append(typeImports(r)...) + } + for _, r := range v.Response.children { + imports.Append(typeImports(r)...) + } + + for _, r := range v.Resources { + if c, ok := r.(verbClient); ok { + imports.Add(`"github.com/TBD54566975/ftl/go-runtime/server"`) + imports.Append(verbImports(c.goVerb)...) + } + } + return imports.ToSlice() } type mainFileContext struct { @@ -129,7 +168,8 @@ type mainFileContext struct { } type typesFileContext struct { - Imports []string + Imports []string + MainModulePkg string SumTypes []goSumType ExternalTypes []goExternalType @@ -137,19 +177,18 @@ type typesFileContext struct { type goType interface { getNativeType() nativeType - setDirectoryName(s string) } type nativeType struct { Name string pkg string importPath string - // empty if package and directory names are the same - directoryName optional.Option[string] + // true if the package name differs from the directory provided by the import path + importAlias bool } func (n nativeType) importStatement() string { - if _, ok := n.directoryName.Get(); ok { + if n.importAlias { return fmt.Sprintf("%s %q", n.pkg, n.importPath) } return strconv.Quote(n.importPath) @@ -160,23 +199,28 @@ func (n nativeType) TypeName() string { } type goVerb struct { - HasRequest bool - HasResponse bool - RequestName string - ResponseName string + Request goSchemaType + Response goSchemaType + Resources []verbResource nativeType } +type goSchemaType struct { + TypeName string + LocalTypeName string + children []goSchemaType + + nativeType optional.Option[nativeType] +} + func (g goVerb) getNativeType() nativeType { return g.nativeType } -func (g goVerb) setDirectoryName(n string) { g.directoryName = optional.Some(n) } type goExternalType struct { nativeType } func (g goExternalType) getNativeType() nativeType { return g.nativeType } -func (g goExternalType) setDirectoryName(n string) { g.directoryName = optional.Some(n) } type goSumType struct { Variants []goSumTypeVariant @@ -185,14 +229,25 @@ type goSumType struct { } func (g goSumType) getNativeType() nativeType { return g.nativeType } -func (g goSumType) setDirectoryName(n string) { g.directoryName = optional.Some(n) } type goSumTypeVariant struct { - Name string - Type string - SchemaType schema.Type + Type goSchemaType + + nativeType } +func (g goSumTypeVariant) getNativeType() nativeType { return g.nativeType } + +type verbResource interface { + resource() +} + +type verbClient struct { + goVerb +} + +func (v verbClient) resource() {} + type ModifyFilesTransaction interface { Begin() error ModifiedFiles(paths ...string) error @@ -517,8 +572,9 @@ func (b *mainModuleContextBuilder) build(goModVersion, ftlVersion, projectName s }, } + visited := sets.NewSet[string]() err := schema.Visit(b.mainModule, func(node schema.Node, next func() error) error { - maybeGoType, isLocal, err := b.getGoType(b.mainModule.Name, node) + maybeGoType, isLocal, err := b.getGoType(b.mainModule, node) if err != nil { return err } @@ -526,14 +582,10 @@ func (b *mainModuleContextBuilder) build(goModVersion, ftlVersion, projectName s if !ok { return next() } - nt := gotype.getNativeType() - b.imports = addImports(b.imports, nt) - // update with the resolved directory name, if necessary - if dirName, ok := b.imports[nt.importPath]; ok { - if existingDirName, ok := nt.directoryName.Get(); !ok || existingDirName != dirName { - gotype.setDirectoryName(dirName) - } + if visited.Contains(gotype.getNativeType().TypeName()) { + return next() } + visited.Add(gotype.getNativeType().TypeName()) switch n := gotype.(type) { case goVerb: @@ -562,90 +614,103 @@ func (b *mainModuleContextBuilder) build(goModVersion, ftlVersion, projectName s return strings.Compare(a.TypeName(), b.TypeName()) }) - return ctx.withImports(), nil + ctx.TypesCtx.MainModulePkg = b.mainModule.Name + mainModuleImport := fmt.Sprintf("ftl/%s", b.mainModule.Name) + if alias, ok := b.imports[mainModuleImport]; ok { + mainModuleImport = fmt.Sprintf("%s %q", alias, mainModuleImport) + ctx.TypesCtx.MainModulePkg = alias + } + return ctx.withImports(mainModuleImport), nil } -func (b *mainModuleContextBuilder) getGoType(moduleName string, node schema.Node) (gotype optional.Option[goType], isLocal bool, err error) { +func (b *mainModuleContextBuilder) getGoType(module *schema.Module, node schema.Node) (gotype optional.Option[goType], isLocal bool, err error) { + isLocal = b.visitingMainModule(module.Name) switch n := node.(type) { case *schema.Ref: if n.Module != "" && n.Module != b.mainModule.Name { - return optional.None[goType](), b.visitingMainModule(moduleName), nil + return optional.None[goType](), isLocal, nil + } + maybeResolved, maybeModule := b.sch.ResolveWithModule(n) + resolved, ok := maybeResolved.Get() + if !ok { + return optional.None[goType](), isLocal, nil } - resolved, ok := b.sch.Resolve(n).Get() + m, ok := maybeModule.Get() if !ok { - return optional.None[goType](), b.visitingMainModule(moduleName), nil + return optional.None[goType](), isLocal, nil } - gt, local, err := b.getGoType(n.Module, resolved) - return gt, local, err + return b.getGoType(m, resolved) case *schema.Verb: - if !b.visitingMainModule(moduleName) { - return optional.None[goType](), b.visitingMainModule(moduleName), nil + if !isLocal { + return optional.None[goType](), false, nil } goverb, err := b.processVerb(n) if err != nil { - return optional.None[goType](), b.visitingMainModule(moduleName), err + return optional.None[goType](), isLocal, err } - return optional.Some[goType](goverb), b.visitingMainModule(moduleName), nil + return optional.Some[goType](goverb), isLocal, nil case *schema.Enum: if n.IsValueEnum() { - return optional.None[goType](), b.visitingMainModule(moduleName), nil + return optional.None[goType](), isLocal, nil } - st, err := b.processSumType(moduleName, n) + st, err := b.processSumType(module, n) if err != nil { - return optional.None[goType](), b.visitingMainModule(moduleName), err + return optional.None[goType](), isLocal, err } - return optional.Some[goType](st), b.visitingMainModule(moduleName), nil + return optional.Some[goType](st), isLocal, nil case *schema.TypeAlias: if len(n.Metadata) == 0 { - return optional.None[goType](), b.visitingMainModule(moduleName), nil + return optional.None[goType](), isLocal, nil } - return b.processExternalTypeAlias(n), b.visitingMainModule(moduleName), nil + return b.processExternalTypeAlias(n), isLocal, nil default: } - return optional.None[goType](), b.visitingMainModule(moduleName), nil + return optional.None[goType](), isLocal, nil } func (b *mainModuleContextBuilder) visitingMainModule(moduleName string) bool { return moduleName == b.mainModule.Name } -func (b *mainModuleContextBuilder) processSumType(moduleName string, enum *schema.Enum) (goSumType, error) { - variants := make([]goSumTypeVariant, 0, len(enum.Variants)) +func (b *mainModuleContextBuilder) processSumType(module *schema.Module, enum *schema.Enum) (goSumType, error) { + moduleName := module.Name + var nt nativeType + var err error if !b.visitingMainModule(moduleName) { - for _, v := range enum.Variants { - variants = append(variants, goSumTypeVariant{ //nolint:forcetypeassert - Name: moduleName + "." + v.Name, - Type: "ftl/" + moduleName + "." + v.Name, - SchemaType: v.Value.(*schema.TypeValue).Value, - }) + nt, err = nativeTypeFromQualifiedName("ftl/" + moduleName + "." + enum.Name) + } else { + if nn, ok := b.nativeNames[enum]; ok { + nt, err = b.getNativeType(nn) + } else { + return goSumType{}, fmt.Errorf("missing native name for enum %s", enum.Name) } - return goSumType{ - Variants: variants, - nativeType: nativeType{ - Name: enum.Name, - pkg: moduleName, - importPath: "ftl/" + moduleName, - directoryName: optional.None[string](), - }, - }, nil } - - nt, err := nativeTypeFromQualifiedName(b.nativeNames[enum]) if err != nil { return goSumType{}, err } + variants := make([]goSumTypeVariant, 0, len(enum.Variants)) for _, v := range enum.Variants { - nativeName := b.nativeNames[v] - lastSlash := strings.LastIndex(nativeName, "/") - variants = append(variants, goSumTypeVariant{ //nolint:forcetypeassert - Name: nativeName[lastSlash+1:], - Type: nativeName, - SchemaType: v.Value.(*schema.TypeValue).Value, + nn, ok := b.nativeNames[v] + if !ok { + return goSumType{}, fmt.Errorf("missing native name for enum variant %s", v.Name) + } + vnt, err := b.getNativeType(nn) + if err != nil { + return goSumType{}, err + } + + typ, err := b.getGoSchemaType(v.Value.(*schema.TypeValue).Value) + if err != nil { + return goSumType{}, err + } + variants = append(variants, goSumTypeVariant{ + Type: typ, + nativeType: vnt, }) } @@ -671,25 +736,123 @@ func (b *mainModuleContextBuilder) processExternalTypeAlias(alias *schema.TypeAl } func (b *mainModuleContextBuilder) processVerb(verb *schema.Verb) (goVerb, error) { + var resources []verbResource + for _, m := range verb.Metadata { + switch md := m.(type) { + case *schema.MetadataCalls: + for _, call := range md.Calls { + resolved, ok := b.sch.Resolve(call).Get() + if !ok { + return goVerb{}, fmt.Errorf("failed to resolve %s client, used by %s.%s", call, + b.mainModule.Name, verb.Name) + } + callee, ok := resolved.(*schema.Verb) + if !ok { + return goVerb{}, fmt.Errorf("%s.%s uses %s client, but %s is not a verb", + b.mainModule.Name, verb.Name, call, call) + } + calleeNativeName, ok := b.nativeNames[call] + if !ok { + // TODO: skip for now because metadata from legacy ftl.Call(...) will not have native name + continue + // return goVerb{}, fmt.Errorf("missing native name for verb client %s", call) + } + calleeverb, err := b.getGoVerb(calleeNativeName, callee) + if err != nil { + return goVerb{}, err + } + resources = append(resources, verbClient{ + calleeverb, + }) + } + default: + // TODO: implement other resources + } + } + nativeName, ok := b.nativeNames[verb] if !ok { return goVerb{}, fmt.Errorf("missing native name for verb %s", verb.Name) } + return b.getGoVerb(nativeName, verb, resources...) +} - nt, err := nativeTypeFromQualifiedName(nativeName) +func (b *mainModuleContextBuilder) getGoVerb(nativeName string, verb *schema.Verb, resources ...verbResource) (goVerb, error) { + nt, err := b.getNativeType(nativeName) + if err != nil { + return goVerb{}, err + } + req, err := b.getGoSchemaType(verb.Request) + if err != nil { + return goVerb{}, err + } + resp, err := b.getGoSchemaType(verb.Response) if err != nil { return goVerb{}, err } - goverb := goVerb{ + return goVerb{ nativeType: nt, + Request: req, + Response: resp, + Resources: resources, + }, nil +} + +func (b *mainModuleContextBuilder) getGoSchemaType(typ schema.Type) (goSchemaType, error) { + result := goSchemaType{ + TypeName: genTypeWithNativeNames(nil, typ, b.nativeNames), + LocalTypeName: genTypeWithNativeNames(b.mainModule, typ, b.nativeNames), + children: []goSchemaType{}, + nativeType: optional.None[nativeType](), } - if _, ok := verb.Request.(*schema.Unit); !ok { - goverb.HasRequest = true + + nn, ok := b.nativeNames[typ] + if ok { + nt, err := b.getNativeType(nn) + if err != nil { + return goSchemaType{}, err + } + result.nativeType = optional.Some(nt) } - if _, ok := verb.Response.(*schema.Unit); !ok { - goverb.HasResponse = true + + switch t := typ.(type) { + case *schema.Ref: + if len(t.TypeParameters) > 0 { + for _, tp := range t.TypeParameters { + _r, err := b.getGoSchemaType(tp) + if err != nil { + return goSchemaType{}, err + } + result.children = append(result.children, _r) + } + } + case *schema.Time: + nt, err := b.getNativeType("time.Time") + if err != nil { + return goSchemaType{}, err + } + result.nativeType = optional.Some(nt) + default: } - return goverb, nil + + return result, nil +} + +func (b *mainModuleContextBuilder) getNativeType(qualifiedName string) (nativeType, error) { + nt, err := nativeTypeFromQualifiedName(qualifiedName) + if err != nil { + return nativeType{}, err + } + // we already have an alias name for this import path + if alias, ok := b.imports[nt.importPath]; ok { + if alias != path.Base(nt.importPath) { + nt.pkg = alias + nt.importAlias = true + } + return nt, nil + } + b.imports = addImports(b.imports, nt) + return nt, nil } var scaffoldFuncs = scaffolder.FuncMap{ @@ -782,6 +945,12 @@ var scaffoldFuncs = scaffolder.FuncMap{ } return genType(m, t.Type) }, + "getVerbClient": func(resource verbResource) *verbClient { + if c, ok := resource.(verbClient); ok { + return &c + } + return nil + }, } // returns the import path and the directory name for a type alias if there is an associated go library @@ -821,15 +990,33 @@ func schemaType(t schema.Type) string { } func genType(module *schema.Module, t schema.Type) string { + return genTypeWithNativeNames(module, t, nil) +} + +// TODO: this is a hack because we don't currently qualify schema refs. Using native names for now to ensure +// even if the module is the same, we qualify the type with a package name when it's a subpackage. +func genTypeWithNativeNames(module *schema.Module, t schema.Type, nativeNames extract.NativeNames) string { switch t := t.(type) { case *schema.Ref: + pkg := "ftl" + t.Module + name := t.Name + if nativeNames != nil { + if nn, ok := nativeNames[t]; ok { + nt, err := nativeTypeFromQualifiedName(nn) + if err == nil { + pkg = nt.pkg + name = nt.Name + } + } + } + desc := "" - if module != nil && t.Module == module.Name { - desc = t.Name + if module != nil && pkg == "ftl"+module.Name { + desc = name } else if t.Module == "" { - desc = t.Name + desc = name } else { - desc = "ftl" + t.Module + "." + t.Name + desc = pkg + "." + name } if len(t.TypeParameters) > 0 { desc += "[" @@ -986,19 +1173,25 @@ func nativeTypeFromQualifiedName(qualifiedName string) (nativeType, error) { pkgPath := qualifiedName[:lastDotIndex] typeName := qualifiedName[lastDotIndex+1:] pkgName := path.Base(pkgPath) - dirName := optional.None[string]() + aliased := false - if lastDotIndex = strings.LastIndex(pkgName, "."); lastDotIndex != -1 { - dirName = optional.Some(pkgName[:lastDotIndex]) - pkgName = pkgName[lastDotIndex+1:] - pkgPath = pkgPath[:strings.LastIndex(pkgPath, ".")] + if strings.LastIndex(pkgName, ".") != -1 { + lastDotIndex = strings.LastIndex(pkgPath, ".") + pkgName = pkgPath[lastDotIndex+1:] + pkgPath = pkgPath[:lastDotIndex] + aliased = true + } + + if parts := strings.Split(qualifiedName, "/"); len(parts) > 0 && parts[0] == "ftl" { + aliased = true + pkgName = "ftl" + pkgName } return nativeType{ - Name: typeName, - pkg: pkgName, - importPath: pkgPath, - directoryName: dirName, + Name: typeName, + pkg: pkgName, + importPath: pkgPath, + importAlias: aliased, }, nil } @@ -1040,7 +1233,7 @@ func imports(m *schema.Module, aliasesMustBeExported bool) map[string]string { return next() } if nt, ok := nativeTypeForWidenedType(n); ok { - if existing, ok := extraImports[nt.importPath]; !ok || !existing.directoryName.Ok() { + if existing, ok := extraImports[nt.importPath]; !ok || !existing.importAlias { extraImports[nt.importPath] = nt } } @@ -1065,10 +1258,13 @@ func addImports(existingImports map[string]string, newTypes ...nativeType) map[s possibleImportAliases[alias]++ } for _, nt := range newTypes { + if _, ok := imports[nt.importPath]; ok { + continue + } + importPath := nt.importPath - dirName := nt.directoryName pathComponents := strings.Split(importPath, "/") - if _, ok := dirName.Get(); ok { + if nt.importAlias { pathComponents = append(pathComponents, nt.pkg) } @@ -1115,9 +1311,14 @@ func addImports(existingImports map[string]string, newTypes ...nativeType) map[s func formatGoImports(imports []string) []string { getPriority := func(path string) int { + // ftl import if strings.HasPrefix(path, "\"ftl/") { return 2 } + // aliased ftl import + if parts := strings.SplitAfter(path, " "); len(parts) == 2 && strings.HasPrefix(parts[1], "\"ftl/") { + return 2 + } // stdlib imports don't contain a dot (e.g., "fmt", "strings") if !strings.Contains(path, ".") { return 0 diff --git a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl index 11c054ee38..587b3096dd 100644 --- a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl +++ b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl @@ -63,18 +63,30 @@ type {{.Name|title}} func {{.Name|title}}(context.Context) error { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallEmpty()") } + +//ftl:verb +type {{.Name|title}}Client func(context.Context) error {{- else if eq (type $.Module .Request) "ftl.Unit"}} func {{.Name|title}}(context.Context) ({{type $.Module .Response}}, error) { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallSource()") } + +//ftl:verb +type {{.Name|title}}Client func(context.Context) ({{type $.Module .Response}}, error) {{- else if eq (type $.Module .Response) "ftl.Unit"}} func {{.Name|title}}(context.Context, {{type $.Module .Request}}) error { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallSink()") } + +//ftl:verb +type {{.Name|title}}Client func(context.Context, {{type $.Module .Request}}) {{- else}} func {{.Name|title}}(context.Context, {{type $.Module .Request}}) ({{type $.Module .Response}}, error) { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.Call()") } + +//ftl:verb +type {{.Name|title}}Client func(context.Context, {{type $.Module .Request}}) ({{type $.Module .Response}}, error) {{- end}} {{- end}} {{- end}} diff --git a/go-runtime/compile/testdata/go/external/go.mod b/go-runtime/compile/testdata/go/external/go.mod index 1c63e4dc75..03bcf6f7ee 100644 --- a/go-runtime/compile/testdata/go/external/go.mod +++ b/go-runtime/compile/testdata/go/external/go.mod @@ -4,4 +4,15 @@ go 1.23.0 require github.com/TBD54566975/ftl v0.206.1 +require ( + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) + replace github.com/TBD54566975/ftl => ../../../../.. diff --git a/go-runtime/compile/testdata/go/external/go.sum b/go-runtime/compile/testdata/go/external/go.sum index e69de29bb2..8adb24eab1 100644 --- a/go-runtime/compile/testdata/go/external/go.sum +++ b/go-runtime/compile/testdata/go/external/go.sum @@ -0,0 +1,200 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +github.com/TBD54566975/scaffolder v1.1.0 h1:R92zjC4XiS/lGCxJ8Ebn93g8gC0LU9qo06AAKo9cEJE= +github.com/TBD54566975/scaffolder v1.1.0/go.mod h1:dRi67GryEhZ5u0XRSiR294SYaqAfnCkZ7u3rmc4W6iI= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod index 45acb7537f..e35a1b6701 100644 --- a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod +++ b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod @@ -18,6 +18,7 @@ require ( github.com/XSAM/otelsql v0.34.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/kong v1.2.1 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/alecthomas/repr v0.4.0 // indirect github.com/alecthomas/types v0.16.0 // indirect @@ -28,11 +29,14 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.33.1 // indirect github.com/aws/smithy-go v1.21.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -49,8 +53,15 @@ require ( github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect @@ -58,5 +69,8 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum index c786a8a32b..4a44305920 100644 --- a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum @@ -16,6 +16,8 @@ github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELk github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= +github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -132,6 +134,8 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -181,6 +185,10 @@ go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8d go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= diff --git a/go-runtime/ftl/ftltest/testdata/go/verbtypes/types.ftl.go b/go-runtime/ftl/ftltest/testdata/go/verbtypes/types.ftl.go new file mode 100644 index 0000000000..9b593c8a7b --- /dev/null +++ b/go-runtime/ftl/ftltest/testdata/go/verbtypes/types.ftl.go @@ -0,0 +1,47 @@ +// Code generated by FTL. DO NOT EDIT. +package verbtypes + +import ( + "context" + + "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" + "github.com/TBD54566975/ftl/go-runtime/server" + + +) + +type CalleeVerbClient func(context.Context, Request) (Response, error) + +type CallerVerbClient func(context.Context, Request) (Response, error) + +type EmptyClient func(context.Context) error + +type SinkClient func(context.Context, Request) error + +type SourceClient func(context.Context) (Response, error) + +type VerbClient func(context.Context, Request) (Response, error) + +func init() { + reflection.Register( + reflection.ProvideResourcesForVerb( + CalleeVerb, + ), + reflection.ProvideResourcesForVerb( + CallerVerb, + server.VerbClient[CalleeVerbClient, Request, Response](), + ), + reflection.ProvideResourcesForVerb( + Empty, + ), + reflection.ProvideResourcesForVerb( + Sink, + ), + reflection.ProvideResourcesForVerb( + Source, + ), + reflection.ProvideResourcesForVerb( + Verb, + ), + ) +} \ No newline at end of file diff --git a/go-runtime/ftl/reflection/reflection.go b/go-runtime/ftl/reflection/reflection.go index 3cdf00aaa4..0c465b3749 100644 --- a/go-runtime/ftl/reflection/reflection.go +++ b/go-runtime/ftl/reflection/reflection.go @@ -51,6 +51,12 @@ func CallingVerb() schema.RefKey { return schema.RefKey{Module: module, Name: verb} } +func ClientRef[T any]() Ref { + ref := TypeRef[T]() + ref.Name = strings.TrimSuffix(ref.Name, "Client") + return ref +} + // TypeRef returns the Ref for a Go type. // // Panics if called with a type outside of FTL. diff --git a/go-runtime/ftl/reflection/singleton.go b/go-runtime/ftl/reflection/singleton.go index 4272ef0612..8fecf751ed 100644 --- a/go-runtime/ftl/reflection/singleton.go +++ b/go-runtime/ftl/reflection/singleton.go @@ -44,6 +44,10 @@ func GetDiscriminatorByVariant(variant reflect.Type) optional.Option[reflect.Typ return singletonTypeRegistry.getDiscriminatorByVariant(variant) } +func CallVerb(ref Ref) VerbExec { + return singletonTypeRegistry.verbCalls[ref].Exec +} + // IsSumTypeDiscriminator returns true if the given type is a sum type discriminator. func IsSumTypeDiscriminator(discriminator reflect.Type) bool { return singletonTypeRegistry.isSumTypeDiscriminator(discriminator) diff --git a/go-runtime/ftl/reflection/type_registry.go b/go-runtime/ftl/reflection/type_registry.go index 8fa88fdf87..5f4d6eeab5 100644 --- a/go-runtime/ftl/reflection/type_registry.go +++ b/go-runtime/ftl/reflection/type_registry.go @@ -17,6 +17,7 @@ type TypeRegistry struct { variantsToDiscriminators map[reflect.Type]reflect.Type fsm map[string]ReflectedFSM externalTypes map[reflect.Type]struct{} + verbCalls map[Ref]verbCall } type sumTypeVariant struct { @@ -71,6 +72,7 @@ func newTypeRegistry(options ...Registree) *TypeRegistry { variantsToDiscriminators: map[reflect.Type]reflect.Type{}, fsm: map[string]ReflectedFSM{}, externalTypes: map[reflect.Type]struct{}{}, + verbCalls: map[Ref]verbCall{}, } for _, o := range options { o(t) diff --git a/go-runtime/ftl/reflection/verb.go b/go-runtime/ftl/reflection/verb.go new file mode 100644 index 0000000000..faeeb32dbe --- /dev/null +++ b/go-runtime/ftl/reflection/verb.go @@ -0,0 +1,92 @@ +package reflection + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/alecthomas/types/optional" +) + +// VerbResource is a function that registers a resource for a Verb. +type VerbResource func() reflect.Value + +// ProvideResourcesForVerb registers any resources that must be provided when calling the given verb. +func ProvideResourcesForVerb(verb any, rs ...VerbResource) Registree { + ref := FuncRef(verb) + ref.Name = strings.TrimSuffix(ref.Name, "Client") + return func(t *TypeRegistry) { + resources := make([]reflect.Value, 0, len(rs)) + for _, r := range rs { + resources = append(resources, r()) + } + vi := verbCall{ + args: resources, + fn: reflect.ValueOf(verb), + } + t.verbCalls[ref] = vi + } +} + +// VerbExec is a function for executing a verb. +type VerbExec func(ctx context.Context, req optional.Option[any]) (optional.Option[any], error) + +type verbCall struct { + args []reflect.Value + fn reflect.Value +} + +// Exec executes the verb with the given context and request, adding any resources that were provided. +func (v verbCall) Exec(ctx context.Context, req optional.Option[any]) (optional.Option[any], error) { + if v.fn.Kind() != reflect.Func { + return optional.None[any](), fmt.Errorf("error invoking verb %v", v.fn) + } + + var args []reflect.Value + args = append(args, reflect.ValueOf(ctx)) + if r, ok := req.Get(); ok { + args = append(args, reflect.ValueOf(r)) + } + + // try to call the function, with panic recovery defaulting to the original args + // TODO: remove once ftl.Call(...) is no longer supported + tryCall := func(args []reflect.Value) (results []reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic occurred: %v", r) + } + }() + results = v.fn.Call(args) + return results, err + } + + results, err := tryCall(append(args, v.args...)) + if err != nil { + // retry with original args if panic occurred + results, err = tryCall(args) + if err != nil { + return optional.None[any](), err + } + } + + var resp optional.Option[any] + var errValue reflect.Value + switch len(results) { + case 0: + return optional.None[any](), nil + case 1: + resp = optional.None[any]() + errValue = results[0] + case 2: + resp = optional.Some(results[0].Interface()) + errValue = results[1] + default: + return optional.None[any](), fmt.Errorf("unexpected number of return values from verb") + } + var fnError error + if e := errValue.Interface(); e != nil { + fnError = e.(error) //nolint:forcetypeassert + } + return resp, fnError +} diff --git a/go-runtime/ftl/testdata/go/echo/echo.go b/go-runtime/ftl/testdata/go/echo/echo.go index 233b4850cb..cfdb7a37e7 100644 --- a/go-runtime/ftl/testdata/go/echo/echo.go +++ b/go-runtime/ftl/testdata/go/echo/echo.go @@ -24,8 +24,8 @@ type EchoResponse struct { // Echo returns a greeting with the current time. // //ftl:verb -func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { - tresp, err := ftl.Call(ctx, time.Time, time.TimeRequest{}) +func Echo(ctx context.Context, req EchoRequest, tc time.TimeClient) (EchoResponse, error) { + tresp, err := tc(ctx, time.TimeRequest{}) if err != nil { return EchoResponse{}, err } diff --git a/go-runtime/schema/common/common.go b/go-runtime/schema/common/common.go index 479f91e6b9..4207399057 100644 --- a/go-runtime/schema/common/common.go +++ b/go-runtime/schema/common/common.go @@ -67,6 +67,19 @@ func NewDeclExtractor[T schema.Decl, N ast.Node](name string, extractFunc Extrac return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractDeclsFunc[T, N](extractFunc)) } +// NewResourceDeclExtractor creates a new schema declaration extractor to extract resources, e.g. Database, Subscription, +// Topics. +// +// Resources are extracted on the basis of their underlying type, e.g. ftl.PostgresDatabaseHandle, rather than +// an FTL directive. +func NewResourceDeclExtractor[T schema.Decl](name string, extractFunc ExtractResourceDeclFunc[T], typePaths ...string) *analysis.Analyzer { + type Tag struct{} // Tag uniquely identifies the fact type for this extractor. + return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractResourceDeclsFunc[T](extractFunc, typePaths...)) +} + +// ExtractResourceDeclFunc extracts a schema declaration from the given node, providing the path to the underlying resource type. +type ExtractResourceDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.TypeSpec, typePath string) optional.Option[T] + // ExtractCallDeclFunc extracts a schema declaration from the given node. type ExtractCallDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, callPath string) optional.Option[T] @@ -125,6 +138,48 @@ func runExtractDeclsFunc[T schema.Decl, N ast.Node](extractFunc ExtractDeclFunc[ } } +func runExtractResourceDeclsFunc[T schema.Decl](extractFunc ExtractResourceDeclFunc[T], typePaths ...string) func(pass *analysis.Pass) (interface{}, error) { + return func(pass *analysis.Pass) (interface{}, error) { + nodeFilter := []ast.Node{ + (*ast.TypeSpec)(nil), + } + in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + in.Preorder(nodeFilter, func(n ast.Node) { + node := n.(*ast.TypeSpec) //nolint:forcetypeassert + obj, ok := GetObjectForNode(pass.TypesInfo, n).Get() + if !ok { + return + } + if obj != nil && !IsPathInModule(pass.Pkg, obj.Pkg().Path()) { + return + } + + typeObj, ok := GetObjectForNode(pass.TypesInfo, node.Type).Get() + if !ok { + return + } + if typeObj.Pkg() == nil { + return + } + typePath := typeObj.Pkg().Path() + "." + typeObj.Name() + var matchesType bool + for _, path := range typePaths { + if typePath == path { + matchesType = true + } + } + if !matchesType { + return + } + decl := extractFunc(pass, obj, node, typePath) + if d, ok := decl.Get(); ok { + MarkSchemaDecl(pass, obj, d) + } + }) + return NewExtractorResult(pass), nil + } +} + func runExtractCallDeclsFunc[T schema.Decl](extractFunc ExtractCallDeclFunc[T], callPaths ...string) func(pass *analysis.Pass) (interface{}, error) { return func(pass *analysis.Pass) (interface{}, error) { in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert @@ -222,6 +277,18 @@ func IsPathInModule(pkg *types.Package, path string) bool { // ExtractType extracts the schema type for the given node. func ExtractType(pass *analysis.Pass, node ast.Node) optional.Option[schema.Type] { + maybeType := extractType(pass, node) + typ, ok := maybeType.Get() + if !ok { + return maybeType + } + if obj, ok := GetObjectForNode(pass.TypesInfo, node).Get(); ok && obj.Pkg() != nil { + MarkIncludeNativeName(pass, obj, typ) + } + return maybeType +} + +func extractType(pass *analysis.Pass, node ast.Node) optional.Option[schema.Type] { tnode := GetTypeInfoForNode(node, pass.TypesInfo) externalType := extractExternalType(pass, node) if externalType.Ok() { diff --git a/go-runtime/schema/common/fact.go b/go-runtime/schema/common/fact.go index 2ac4109b3a..cc43b05674 100644 --- a/go-runtime/schema/common/fact.go +++ b/go-runtime/schema/common/fact.go @@ -118,6 +118,14 @@ type VerbCall struct { func (*VerbCall) schemaFactValue() {} +// IncludeNativeName marks a node that needs to be added to the native names map provided in the extraction result. +type IncludeNativeName struct { + // The schema node associated with this native name. + Node schema.Node +} + +func (*IncludeNativeName) schemaFactValue() {} + // MarkSchemaDecl marks the given object as having been extracted to the given schema decl. func MarkSchemaDecl(pass *analysis.Pass, obj types.Object, decl schema.Decl) { fact := newFact(pass, obj) @@ -181,6 +189,13 @@ func MarkVerbCall(pass *analysis.Pass, obj types.Object, verbRef *schema.Ref) { pass.ExportObjectFact(obj, fact) } +// MarkIncludeNativeName marks the given object as needing to be added to the native names map. +func MarkIncludeNativeName(pass *analysis.Pass, obj types.Object, node schema.Node) { + fact := newFact(pass, obj) + fact.Add(&IncludeNativeName{Node: node}) + pass.ExportObjectFact(obj, fact) +} + // GetAllFactsExtractionStatus merges schema facts inclusive of all available results and the present pass facts. // For a given object, it provides the current extraction status. // @@ -232,18 +247,18 @@ func GetAllFactsExtractionStatus(pass *analysis.Pass) map[types.Object]SchemaFac // GetAllFactsOfType returns all facts of the provided type marked on objects, across the current pass and results from // prior passes. If multiple of the same fact type are marked on a single object, the first fact is returned. -func GetAllFactsOfType[T SchemaFactValue](pass *analysis.Pass) map[types.Object]T { +func GetAllFactsOfType[T SchemaFactValue](pass *analysis.Pass) map[types.Object][]T { return getFactsScoped[T](allFacts(pass)) } // GetCurrentPassFacts returns all facts of the provided type marked on objects during the current pass. // If multiple of the same fact type are marked on a single object, the first fact is returned. -func GetCurrentPassFacts[T SchemaFactValue](pass *analysis.Pass) map[types.Object]T { +func GetCurrentPassFacts[T SchemaFactValue](pass *analysis.Pass) map[types.Object][]T { return getFactsScoped[T](pass.AllObjectFacts()) } -func getFactsScoped[T SchemaFactValue](scope []analysis.ObjectFact) map[types.Object]T { - facts := make(map[types.Object]T) +func getFactsScoped[T SchemaFactValue](scope []analysis.ObjectFact) map[types.Object][]T { + facts := make(map[types.Object][]T) for _, fact := range scope { sf, ok := fact.Fact.(SchemaFact) if !ok { @@ -252,7 +267,10 @@ func getFactsScoped[T SchemaFactValue](scope []analysis.ObjectFact) map[types.Ob for _, f := range sf.Get() { if t, ok := f.(T); ok { - facts[fact.Object] = t + if _, exists := facts[fact.Object]; !exists { + facts[fact.Object] = []T{t} + } + facts[fact.Object] = append(facts[fact.Object], t) } } } diff --git a/go-runtime/schema/enum/analyzer.go b/go-runtime/schema/enum/analyzer.go index 71e1ab575f..bebdf71a00 100644 --- a/go-runtime/schema/enum/analyzer.go +++ b/go-runtime/schema/enum/analyzer.go @@ -67,7 +67,14 @@ func Extract(pass *analysis.Pass, node *ast.TypeSpec, obj types.Object) optional func findValueEnumVariants(pass *analysis.Pass, obj types.Object) []*schema.EnumVariant { var variants []*schema.EnumVariant - for o, fact := range common.GetAllFactsOfType[*common.MaybeValueEnumVariant](pass) { + for o, facts := range common.GetAllFactsOfType[*common.MaybeValueEnumVariant](pass) { + // there shouldn't be more than one of this type of fact on an object, but even if there are, + // we don't care. We just need to know if there are any. + if len(facts) < 1 { + continue + } + fact := facts[0] + if fact.Type == obj && validateVariant(pass, o, fact.Variant) { variants = append(variants, fact.Variant) } @@ -79,7 +86,12 @@ func findValueEnumVariants(pass *analysis.Pass, obj types.Object) []*schema.Enum } func validateVariant(pass *analysis.Pass, obj types.Object, variant *schema.EnumVariant) bool { - for _, fact := range common.GetAllFactsOfType[*common.ExtractedDecl](pass) { + for _, facts := range common.GetAllFactsOfType[*common.ExtractedDecl](pass) { + if len(facts) < 1 { + continue + } + fact := facts[0] + if fact.Decl == nil { continue } @@ -100,7 +112,14 @@ func validateVariant(pass *analysis.Pass, obj types.Object, variant *schema.Enum func findTypeValueVariants(pass *analysis.Pass, obj types.Object) []*schema.EnumVariant { var variants []*schema.EnumVariant - for vObj, fact := range common.GetAllFactsOfType[*common.MaybeTypeEnumVariant](pass) { + for vObj, facts := range common.GetAllFactsOfType[*common.MaybeTypeEnumVariant](pass) { + // there shouldn't be more than one of this type of fact on an object, but even if there are, + // we don't care. We just need to know if there are any. + if len(facts) < 1 { + continue + } + fact := facts[0] + if fact.Parent != obj { continue } diff --git a/go-runtime/schema/finalize/analyzer.go b/go-runtime/schema/finalize/analyzer.go index fc261a0024..8e6e67d225 100644 --- a/go-runtime/schema/finalize/analyzer.go +++ b/go-runtime/schema/finalize/analyzer.go @@ -69,8 +69,15 @@ func Run(pass *analysis.Pass) (interface{}, error) { failed[schema.RefKey{Module: moduleName, Name: strcase.ToUpperCamel(obj.Name())}] = obj } } - for obj, fact := range common.GetAllFactsOfType[*common.MaybeTypeEnumVariant](pass) { - nativeNames[fact.Variant] = common.GetNativeName(obj) + for obj, facts := range common.GetAllFactsOfType[*common.MaybeTypeEnumVariant](pass) { + for _, fact := range facts { + nativeNames[fact.Variant] = common.GetNativeName(obj) + } + } + for obj, facts := range common.GetAllFactsOfType[*common.IncludeNativeName](pass) { + for _, fact := range facts { + nativeNames[fact.Node] = common.GetNativeName(obj) + } } fnCalls, verbCalls := getCalls(pass) return Result{ @@ -86,7 +93,12 @@ func Run(pass *analysis.Pass) (interface{}, error) { func getCalls(pass *analysis.Pass) (functionCalls map[types.Object]sets.Set[types.Object], verbCalls map[types.Object]sets.Set[*schema.Ref]) { fnCalls := make(map[types.Object]sets.Set[types.Object]) - for obj, fnCall := range common.GetAllFactsOfType[*common.FunctionCall](pass) { + for obj, calls := range common.GetAllFactsOfType[*common.FunctionCall](pass) { + if len(calls) < 1 { + continue + } + fnCall := calls[0] + if fnCalls[obj] == nil { fnCalls[obj] = sets.NewSet[types.Object]() } @@ -94,7 +106,12 @@ func getCalls(pass *analysis.Pass) (functionCalls map[types.Object]sets.Set[type } vCalls := make(map[types.Object]sets.Set[*schema.Ref]) - for obj, vCall := range common.GetAllFactsOfType[*common.VerbCall](pass) { + for obj, calls := range common.GetAllFactsOfType[*common.VerbCall](pass) { + if len(calls) < 1 { + continue + } + vCall := calls[0] + if vCalls[obj] == nil { vCalls[obj] = sets.NewSet[*schema.Ref]() } diff --git a/go-runtime/schema/metadata/analyzer.go b/go-runtime/schema/metadata/analyzer.go index 958cfcbe8b..8bba01df15 100644 --- a/go-runtime/schema/metadata/analyzer.go +++ b/go-runtime/schema/metadata/analyzer.go @@ -38,6 +38,9 @@ func Extract(pass *analysis.Pass) (interface{}, error) { doc = n.Doc case *ast.GenDecl: doc = n.Doc + if len(n.Specs) == 0 { + return + } if ts, ok := n.Specs[0].(*ast.TypeSpec); len(n.Specs) > 0 && ok { if doc == nil { doc = ts.Doc diff --git a/go-runtime/schema/schema_test.go b/go-runtime/schema/schema_test.go index 3d98e1bf4e..0b910ba9c6 100644 --- a/go-runtime/schema/schema_test.go +++ b/go-runtime/schema/schema_test.go @@ -589,7 +589,8 @@ func TestErrorReporting(t *testing.T) { `38:16-29: call first argument must be a function in an ftl module, does it need to be exported?`, `39:2-46: call must have exactly three arguments`, `40:16-25: call first argument must be a function in an ftl module, does it need to be exported?`, - `45:1-2: must have at most two parameters (context.Context, struct)`, + `45:45-45: unsupported request type "ftl/failing.Request"`, + `45:54-66: unsupported verb parameter type "Request"; verbs must have the signature func(Context, Request?, Resources...)`, `45:69-69: unsupported response type "ftl/failing.Response"`, `50:22-27: first parameter must be of type context.Context but is ftl/failing.Request`, `50:53-53: unsupported response type "ftl/failing.Response"`, diff --git a/go-runtime/schema/testdata/named/go.mod b/go-runtime/schema/testdata/named/go.mod index 57ca0fb826..5342b7402e 100644 --- a/go-runtime/schema/testdata/named/go.mod +++ b/go-runtime/schema/testdata/named/go.mod @@ -3,3 +3,16 @@ module ftl/named go 1.23.0 replace github.com/TBD54566975/ftl => ../../../.. + +require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 + +require ( + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/go-runtime/schema/testdata/named/go.sum b/go-runtime/schema/testdata/named/go.sum index e69de29bb2..8adb24eab1 100644 --- a/go-runtime/schema/testdata/named/go.sum +++ b/go-runtime/schema/testdata/named/go.sum @@ -0,0 +1,200 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +github.com/TBD54566975/scaffolder v1.1.0 h1:R92zjC4XiS/lGCxJ8Ebn93g8gC0LU9qo06AAKo9cEJE= +github.com/TBD54566975/scaffolder v1.1.0/go.mod h1:dRi67GryEhZ5u0XRSiR294SYaqAfnCkZ7u3rmc4W6iI= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/go-runtime/schema/transitive/analyzer.go b/go-runtime/schema/transitive/analyzer.go index 7544d7f1a4..c1da9d3a65 100644 --- a/go-runtime/schema/transitive/analyzer.go +++ b/go-runtime/schema/transitive/analyzer.go @@ -116,7 +116,14 @@ func inferDeclType(pass *analysis.Pass, node ast.Node, obj types.Object) optiona } if !common.IsSelfReference(pass, obj, t) { // if this is a type alias and it has enum variants, infer to be a value enum - for _, fact := range common.GetAllFactsOfType[*common.MaybeValueEnumVariant](pass) { + for _, facts := range common.GetAllFactsOfType[*common.MaybeValueEnumVariant](pass) { + // there shouldn't be more than one of this type of fact on an object, but even if there are, + // we don't care. We just need to know if there are any. + if len(facts) < 1 { + continue + } + fact := facts[0] + if fact.Type == obj { return optional.Some[schema.Decl](&schema.Enum{}) } diff --git a/go-runtime/schema/verb/analyzer.go b/go-runtime/schema/verb/analyzer.go index 45ad9365c8..edca99a7cb 100644 --- a/go-runtime/schema/verb/analyzer.go +++ b/go-runtime/schema/verb/analyzer.go @@ -3,6 +3,7 @@ package verb import ( "go/ast" "go/types" + "strings" "unicode" "github.com/TBD54566975/golang-tools/go/analysis" @@ -14,6 +15,13 @@ import ( "github.com/TBD54566975/ftl/go-runtime/schema/initialize" ) +type resourceType int + +const ( + none resourceType = iota + verbClient +) + // Extractor extracts verbs to the module schema. var Extractor = common.NewDeclExtractor[*schema.Verb, *ast.FuncDecl]("verb", Extract) @@ -22,10 +30,35 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), Name: strcase.ToLowerCamel(node.Name.Name), } + + hasRequest := false if !common.ApplyMetadata[*schema.Verb](pass, obj, func(md *common.ExtractedMetadata) { verb.Comments = md.Comments verb.Export = md.IsExported verb.Metadata = md.Metadata + for idx, param := range node.Type.Params.List { + paramObj, hasObj := common.GetObjectForNode(pass.TypesInfo, param.Type).Get() + switch getParamResourceType(paramObj) { + case none: + if idx > 1 { + common.Errorf(pass, param, "unsupported verb parameter type %q; verbs must have the "+ + "signature func(Context, Request?, Resources...)", param.Type) + continue + } + if idx == 1 { + hasRequest = true + } + case verbClient: + if !hasObj { + common.Errorf(pass, param, "unsupported verb parameter type %q", param.Type) + continue + } + calleeRef := getResourceRef(paramObj, pass, param) + calleeRef.Name = strings.TrimSuffix(calleeRef.Name, "Client") + verb.AddCall(calleeRef) + common.MarkIncludeNativeName(pass, paramObj, calleeRef) + } + } }) { return optional.None[*schema.Verb]() } @@ -37,14 +70,15 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional return optional.None[*schema.Verb]() } - reqt, respt := checkSignature(pass, node, sig) + reqt, respt := checkSignature(pass, node, sig, hasRequest) req := optional.Some[schema.Type](&schema.Unit{}) if reqt.Ok() { - req = common.ExtractType(pass, node.Type.Params.List[1]) + req = common.ExtractType(pass, node.Type.Params.List[1].Type) } + resp := optional.Some[schema.Type](&schema.Unit{}) if respt.Ok() { - resp = common.ExtractType(pass, node.Type.Results.List[0]) + resp = common.ExtractType(pass, node.Type.Results.List[0].Type) } params := sig.Params() @@ -63,7 +97,7 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional return optional.Some(verb) } -func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signature) (req, resp optional.Option[*types.Var]) { +func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signature, hasRequest bool) (req, resp optional.Option[*types.Var]) { if node.Name.Name == "" { common.Errorf(pass, node, "verb function must be named") return optional.None[*types.Var](), optional.None[*types.Var]() @@ -75,10 +109,6 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur params := sig.Params() results := sig.Results() - if params.Len() > 2 { - common.Errorf(pass, node, "must have at most two parameters (context.Context, struct)") - } - loaded := pass.ResultOf[initialize.Analyzer].(initialize.Result) //nolint:forcetypeassert if params.Len() == 0 { common.Errorf(pass, node, "first parameter must be context.Context") @@ -86,12 +116,14 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur common.TokenErrorf(pass, params.At(0).Pos(), params.At(0).Name(), "first parameter must be of type context.Context but is %s", params.At(0).Type()) } - if params.Len() == 2 { + if params.Len() >= 2 { if params.At(1).Type().String() == common.FtlUnitTypePath { common.TokenErrorf(pass, params.At(1).Pos(), params.At(1).Name(), "second parameter must not be ftl.Unit") } - req = optional.Some(params.At(1)) + if hasRequest { + req = optional.Some(params.At(1)) + } } if results.Len() > 2 { @@ -110,3 +142,32 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur } return req, resp } + +func getParamResourceType(paramObj types.Object) resourceType { + if paramObj == nil { + return none + } + + switch t := paramObj.Type().(type) { + case *types.Named: + if _, ok := t.Underlying().(*types.Signature); !ok { + return none + } + + return verbClient + default: + return none + } +} + +func getResourceRef(paramObj types.Object, pass *analysis.Pass, param *ast.Field) *schema.Ref { + paramModule, err := common.FtlModuleFromGoPackage(paramObj.Pkg().Path()) + if err != nil { + common.Errorf(pass, param, "failed to resolve module for type %q: %v", paramObj.String(), err) + } + dbRef := &schema.Ref{ + Module: paramModule, + Name: strcase.ToLowerCamel(paramObj.Name()), + } + return dbRef +} diff --git a/go-runtime/server/server.go b/go-runtime/server/server.go index 19ec934944..c223e28690 100644 --- a/go-runtime/server/server.go +++ b/go-runtime/server/server.go @@ -4,12 +4,14 @@ import ( "context" "fmt" "net/url" + "reflect" "runtime/debug" + "strings" "connectrpc.com/connect" - ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/common/plugin" "github.com/TBD54566975/ftl/go-runtime/encoding" "github.com/TBD54566975/ftl/go-runtime/ftl" @@ -20,6 +22,7 @@ import ( "github.com/TBD54566975/ftl/internal/modulecontext" "github.com/TBD54566975/ftl/internal/observability" "github.com/TBD54566975/ftl/internal/rpc" + "github.com/alecthomas/types/optional" ) type UserVerbConfig struct { @@ -62,7 +65,8 @@ type Handler struct { fn func(ctx context.Context, req []byte, metadata map[internal.MetadataKey]string) ([]byte, error) } -func handler[Req, Resp any](ref reflection.Ref, verb func(ctx context.Context, req Req) (Resp, error)) Handler { +func HandleCall[Req, Resp any](verb any) Handler { + ref := reflection.FuncRef(verb) return Handler{ ref: ref, fn: func(ctx context.Context, reqdata []byte, metadata map[internal.MetadataKey]string) ([]byte, error) { @@ -76,7 +80,7 @@ func handler[Req, Resp any](ref reflection.Ref, verb func(ctx context.Context, r } // Call Verb. - resp, err := verb(ctx, req) + resp, err := Call[Req, Resp](ref)(ctx, req) if err != nil { return nil, fmt.Errorf("call to verb %s failed: %w", ref, err) } @@ -91,32 +95,106 @@ func handler[Req, Resp any](ref reflection.Ref, verb func(ctx context.Context, r } } -// HandleCall creates a Handler from a Verb. -func HandleCall[Req, Resp any](verb func(ctx context.Context, req Req) (Resp, error)) Handler { - return handler(reflection.FuncRef(verb), verb) +func HandleSink[Req any](verb any) Handler { + return HandleCall[Req, ftl.Unit](verb) +} + +func HandleSource[Resp any](verb any) Handler { + return HandleCall[ftl.Unit, Resp](verb) +} + +func HandleEmpty(verb any) Handler { + return HandleCall[ftl.Unit, ftl.Unit](verb) +} + +func VerbClient[Verb, Req, Resp any]() reflection.VerbResource { + typ := reflect.TypeFor[Verb]() + if typ.Kind() != reflect.Func { + panic(fmt.Sprintf("Cannot register %s: expected function, got %s", typ, typ.Kind())) + } + callee := reflection.TypeRef[Verb]() + callee.Name = strings.TrimSuffix(callee.Name, "Client") + fn := func(ctx context.Context, req Req) (resp Resp, err error) { + ref := reflection.Ref{Module: callee.Module, Name: callee.Name} + moduleCtx := modulecontext.FromContext(ctx).CurrentContext() + override, err := moduleCtx.BehaviorForVerb(schema.Ref{Module: ref.Module, Name: ref.Name}) + if err != nil { + return resp, fmt.Errorf("%s: %w", ref, err) + } + if behavior, ok := override.Get(); ok { + uncheckedResp, err := behavior.Call(ctx, modulecontext.Verb(widenVerb(Call[Req, Resp](ref))), req) + if err != nil { + return resp, fmt.Errorf("%s: %w", ref, err) + } + if r, ok := uncheckedResp.(Resp); ok { + return r, nil + } + return resp, fmt.Errorf("%s: overridden verb had invalid response type %T, expected %v", ref, + uncheckedResp, reflect.TypeFor[Resp]()) + } + + reqData, err := encoding.Marshal(req) + if err != nil { + return resp, fmt.Errorf("%s: failed to marshal request: %w", callee, err) + } + + client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx) + cresp, err := client.Call(ctx, connect.NewRequest(&ftlv1.CallRequest{Verb: callee.ToProto(), Body: reqData})) + if err != nil { + return resp, fmt.Errorf("%s: failed to call Verb: %w", callee, err) + } + switch cresp := cresp.Msg.Response.(type) { + case *ftlv1.CallResponse_Error_: + return resp, fmt.Errorf("%s: %s", callee, cresp.Error.Message) + + case *ftlv1.CallResponse_Body: + err = encoding.Unmarshal(cresp.Body, &resp) + if err != nil { + return resp, fmt.Errorf("%s: failed to decode response: %w", callee, err) + } + return resp, nil + + default: + panic(fmt.Sprintf("%s: invalid response type %T", callee, cresp)) + } + } + return func() reflect.Value { + return reflect.ValueOf(fn) + } +} + +func SinkClient[Verb, Req any]() reflection.VerbResource { + return VerbClient[Verb, Req, ftl.Unit]() } -// HandleSink creates a Handler from a Sink with no response. -func HandleSink[Req any](sink func(ctx context.Context, req Req) error) Handler { - return handler(reflection.FuncRef(sink), func(ctx context.Context, req Req) (ftl.Unit, error) { - err := sink(ctx, req) - return ftl.Unit{}, err - }) +func SourceClient[Verb, Resp any]() reflection.VerbResource { + return VerbClient[Verb, ftl.Unit, Resp]() } -// HandleSource creates a Handler from a Source with no request. -func HandleSource[Resp any](source func(ctx context.Context) (Resp, error)) Handler { - return handler(reflection.FuncRef(source), func(ctx context.Context, _ ftl.Unit) (Resp, error) { - return source(ctx) - }) +func EmptyClient[Verb any]() reflection.VerbResource { + return VerbClient[Verb, ftl.Unit, ftl.Unit]() } -// HandleEmpty creates a Handler from a Verb with no request or response. -func HandleEmpty(empty func(ctx context.Context) error) Handler { - return handler(reflection.FuncRef(empty), func(ctx context.Context, _ ftl.Unit) (ftl.Unit, error) { - err := empty(ctx) - return ftl.Unit{}, err - }) +func Call[Req, Resp any](ref reflection.Ref) func(ctx context.Context, req Req) (resp Resp, err error) { + return func(ctx context.Context, req Req) (resp Resp, err error) { + request := optional.Some[any](req) + if reflect.TypeFor[Req]() == reflect.TypeFor[ftl.Unit]() { + request = optional.None[any]() + } + + var respValue any + out, err := reflection.CallVerb(reflection.Ref{Module: ref.Module, Name: ref.Name})(ctx, request) + if r, ok := out.Get(); ok { + respValue = r + } else { + respValue = ftl.Unit{} + } + resp, ok := respValue.(Resp) + if !ok { + return resp, fmt.Errorf("unexpected response type from verb %s: %T", ref, resp) + } + return resp, err + } } var _ ftlv1connect.VerbServiceHandler = (*moduleServer)(nil) @@ -174,3 +252,13 @@ func (m *moduleServer) Call(ctx context.Context, req *connect.Request[ftlv1.Call func (m *moduleServer) Ping(_ context.Context, _ *connect.Request[ftlv1.PingRequest]) (*connect.Response[ftlv1.PingResponse], error) { return connect.NewResponse(&ftlv1.PingResponse{}), nil } + +func widenVerb[Req, Resp any](verb ftl.Verb[Req, Resp]) ftl.Verb[any, any] { + return func(ctx context.Context, uncheckedReq any) (any, error) { + req, ok := uncheckedReq.(Req) + if !ok { + return nil, fmt.Errorf("invalid request type %T for %v, expected %v", uncheckedReq, reflection.FuncRef(verb), reflect.TypeFor[Req]()) + } + return verb(ctx, req) + } +} diff --git a/internal/buildengine/stubs_test.go b/internal/buildengine/stubs_test.go index eebd76cf41..ca81effe6b 100644 --- a/internal/buildengine/stubs_test.go +++ b/internal/buildengine/stubs_test.go @@ -144,6 +144,9 @@ func Echo(context.Context, EchoRequest) (EchoResponse, error) { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.Call()") } +//ftl:verb +type EchoClient func(context.Context, EchoRequest) (EchoResponse, error) + type SinkReq struct { } @@ -156,6 +159,9 @@ func Sink(context.Context, SinkReq) error { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallSink()") } +//ftl:verb +type SinkClient func(context.Context, SinkReq) + type SourceResp struct { } @@ -164,11 +170,17 @@ func Source(context.Context) (SourceResp, error) { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallSource()") } +//ftl:verb +type SourceClient func(context.Context) (SourceResp, error) + //ftl:verb func Nothing(context.Context) error { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.CallEmpty()") } +//ftl:verb +type NothingClient func(context.Context) error + func init() { reflection.Register( reflection.SumType[TypeEnum]( @@ -238,6 +250,10 @@ type Resp struct { func Call(context.Context, Req) (Resp, error) { panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.Call()") } + +//ftl:verb +type CallClient func(context.Context, Req) (Resp, error) + ` ctx := log.ContextWithNewDefaultLogger(context.Background()) projectRoot := t.TempDir() diff --git a/internal/buildengine/testdata/alpha/alpha.go b/internal/buildengine/testdata/alpha/alpha.go index 1f04a1b80c..e67e191476 100644 --- a/internal/buildengine/testdata/alpha/alpha.go +++ b/internal/buildengine/testdata/alpha/alpha.go @@ -18,7 +18,7 @@ type EchoResponse struct { } //ftl:verb -func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { - ftl.Call(ctx, other.Echo, other.EchoRequest{}) +func Echo(ctx context.Context, req EchoRequest, oc other.EchoClient) (EchoResponse, error) { + oc(ctx, other.EchoRequest{}) return EchoResponse{Message: fmt.Sprintf("Hello, %s!", req.Name.Default("anonymous"))}, nil } diff --git a/internal/buildengine/testdata/alpha/go.mod b/internal/buildengine/testdata/alpha/go.mod index 2306019093..ecf69388c0 100644 --- a/internal/buildengine/testdata/alpha/go.mod +++ b/internal/buildengine/testdata/alpha/go.mod @@ -11,15 +11,19 @@ require ( github.com/XSAM/otelsql v0.34.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/kong v1.2.1 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/alecthomas/types v0.16.0 // indirect github.com/alessio/shellescape v1.4.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -34,8 +38,15 @@ require ( github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect @@ -43,6 +54,9 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/internal/buildengine/testdata/alpha/go.sum b/internal/buildengine/testdata/alpha/go.sum index f001543d0e..51cef7fe59 100644 --- a/internal/buildengine/testdata/alpha/go.sum +++ b/internal/buildengine/testdata/alpha/go.sum @@ -14,6 +14,8 @@ github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELk github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= +github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -28,6 +30,8 @@ github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -66,6 +70,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -110,6 +116,8 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -143,6 +151,12 @@ github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8L github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= @@ -151,6 +165,12 @@ go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792j go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= @@ -172,6 +192,12 @@ golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/buildengine/testdata/alpha/pkg/pkg.go b/internal/buildengine/testdata/alpha/pkg/pkg.go index b364d4a9fb..8d15ac0564 100644 --- a/internal/buildengine/testdata/alpha/pkg/pkg.go +++ b/internal/buildengine/testdata/alpha/pkg/pkg.go @@ -3,10 +3,8 @@ package pkg import ( "context" "ftl/another" - - "github.com/TBD54566975/ftl/go-runtime/ftl" ) -func Pkg() { - ftl.Call(context.Background(), another.Echo, another.EchoRequest{}) +func Pkg(ec another.EchoClient) { + ec(context.Background(), another.EchoRequest{}) } diff --git a/internal/buildengine/testdata/type_registry_main.go b/internal/buildengine/testdata/type_registry_main.go index 0eb77e2b08..7ef7a9cccd 100644 --- a/internal/buildengine/testdata/type_registry_main.go +++ b/internal/buildengine/testdata/type_registry_main.go @@ -9,34 +9,37 @@ import ( "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" "github.com/TBD54566975/ftl/go-runtime/server" - "ftl/other" + ftlother "ftl/other" ) func init() { reflection.Register( - reflection.SumType[other.SecondTypeEnum]( - *new(other.A), - *new(other.B), + reflection.SumType[ftlother.SecondTypeEnum]( + *new(ftlother.A), + *new(ftlother.B), ), - reflection.SumType[other.TypeEnum]( - *new(other.MyBool), - *new(other.MyBytes), - *new(other.MyFloat), - *new(other.MyInt), - *new(other.MyList), - *new(other.MyMap), - *new(other.MyOption), - *new(other.MyString), - *new(other.MyStruct), - *new(other.MyTime), - *new(other.MyUnit), + reflection.SumType[ftlother.TypeEnum]( + *new(ftlother.MyBool), + *new(ftlother.MyBytes), + *new(ftlother.MyFloat), + *new(ftlother.MyInt), + *new(ftlother.MyList), + *new(ftlother.MyMap), + *new(ftlother.MyOption), + *new(ftlother.MyString), + *new(ftlother.MyStruct), + *new(ftlother.MyTime), + *new(ftlother.MyUnit), + ), + reflection.ProvideResourcesForVerb( + ftlother.Echo, ), ) } func main() { verbConstructor := server.NewUserVerbServer("integration", "other", - server.HandleCall(other.Echo), + server.HandleCall[ftlother.EchoRequest, ftlother.EchoResponse](ftlother.Echo), ) plugin.Start(context.Background(), "other", verbConstructor, ftlv1connect.VerbServiceName, ftlv1connect.NewVerbServiceHandler) }