Skip to content

Commit 3c78bf0

Browse files
authored
instanced default go templates (#3378)
1 parent 990fd9d commit 3c78bf0

File tree

63 files changed

+10505
-10597
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+10505
-10597
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
/pkg/functions/testdata/migrations/*/.gitignore
1616
/pkg/functions/testdata/default_home/go
17+
/pkg/functions/testdata/default_home/.config
1718
/pkg/functions/testdata/default_home/.cache
1819
/pkg/functions/testdata/migrations/*/.gitignore
1920
/pkg/functions/testdata/default_home/Library

docs/function-templates/golang.md

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ fn
1616
├── func.yaml
1717
├── go.mod
1818
├── go.sum
19-
├── handle.go
20-
└── handle_test.go
19+
├── function.go
20+
└── function_test.go
2121
```
2222

2323
Aside from the `func.yaml` file, this looks like the beginning of just about
@@ -69,8 +69,8 @@ You can get the URL for your deployed function with the `info` command.
6969

7070

7171
Go functions can be tested locally on your computer. In the project there is
72-
a `handle_test.go` file which contains simple test which can be extended as needed.
73-
Yo can run this test locally as you would do with any Go project.
72+
a `function_test.go` file which contains a simple test which can be extended as needed.
73+
You can run this test locally as you would do with any Go project.
7474

7575
```
7676
❯ go test
@@ -79,9 +79,10 @@ Yo can run this test locally as you would do with any Go project.
7979
## Function reference
8080

8181
Boson Go functions have very few restrictions. You can add any required dependencies
82-
in `go.mod` and you may include additional local Go files. The only real requirement are
83-
that your project is defined in a `function` module and exports the function `Handle()`
84-
(supported contracts of this function will be discussed more deeply later).
82+
in `go.mod` and you may include additional local Go files. The only real requirement is
83+
that your project is defined in a `function` module and exports a `New()` constructor
84+
that returns a struct with a `Handle()` method (supported contracts of this method
85+
will be discussed more deeply later).
8586
In this section, we will look in a little more detail at how Boson functions are invoked,
8687
and what APIs are available to you as a developer.
8788

@@ -94,14 +95,18 @@ They each will listen and respond to incoming HTTP events.
9495

9596
#### Function triggered by HTTP request
9697

97-
When an incoming request is received, your function will be invoked with two parameters:
98+
When an incoming request is received, your function's `Handle` method will be invoked with two parameters:
9899
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter) and [http.Request](https://golang.org/pkg/net/http/#Request).
99100

100101
Then you can use standard Golang techniques to access the request (eg. read the body)
101102
and set a proper HTTP response of your function, as you can see on the following example:
102103

103104
```go
104-
func Handle(res http.ResponseWriter, req *http.Request) {
105+
type Function struct{}
106+
107+
func New() *Function { return &Function{} }
108+
109+
func (f *Function) Handle(res http.ResponseWriter, req *http.Request) {
105110

106111
// Read body
107112
body, err := ioutil.ReadAll(req.Body)
@@ -121,22 +126,22 @@ func Handle(res http.ResponseWriter, req *http.Request) {
121126
If the incoming request is a `CloudEvent`, the event is provided via
122127
[CloudEvents Golang SDK](https://cloudevents.github.io/sdk-go/) and its `Event` type
123128
as a parameter. There's possibility to leverage Golang's
124-
[Context](https://golang.org/pkg/context/) as the optional parameter in the function contract,
125-
as you can see in the list of supported function signatures:
129+
[Context](https://golang.org/pkg/context/) as the optional parameter in the method contract,
130+
as you can see in the list of supported `Handle` method signatures:
126131

127132
```go
128-
Handle()
129-
Handle() error
130-
Handle(context.Context)
131-
Handle(context.Context) error
132-
Handle(cloudevents.Event)
133-
Handle(cloudevents.Event) error
134-
Handle(context.Context, cloudevents.Event)
135-
Handle(context.Context, cloudevents.Event) error
136-
Handle(cloudevents.Event) *cloudevents.Event
137-
Handle(cloudevents.Event) (*cloudevents.Event, error)
138-
Handle(context.Context, cloudevents.Event) *cloudevents.Event
139-
Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)
133+
(f *Function) Handle()
134+
(f *Function) Handle() error
135+
(f *Function) Handle(context.Context)
136+
(f *Function) Handle(context.Context) error
137+
(f *Function) Handle(cloudevents.Event)
138+
(f *Function) Handle(cloudevents.Event) error
139+
(f *Function) Handle(context.Context, cloudevents.Event)
140+
(f *Function) Handle(context.Context, cloudevents.Event) error
141+
(f *Function) Handle(cloudevents.Event) *cloudevents.Event
142+
(f *Function) Handle(cloudevents.Event) (*cloudevents.Event, error)
143+
(f *Function) Handle(context.Context, cloudevents.Event) *cloudevents.Event
144+
(f *Function) Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)
140145
```
141146

142147
For example, a `CloudEvent` is received which contains a JSON string such as this in its data property,
@@ -157,7 +162,11 @@ type Purchase struct {
157162
ProductId string `json:"productId"`
158163
}
159164

160-
func Handle(ctx context.Context, event cloudevents.Event) err error {
165+
type Function struct{}
166+
167+
func New() *Function { return &Function{} }
168+
169+
func (f *Function) Handle(ctx context.Context, event cloudevents.Event) err error {
161170

162171
purchase := &Purchase{}
163172
if err = cloudevents.DataAs(purchase); err != nil {
@@ -172,8 +181,12 @@ func Handle(ctx context.Context, event cloudevents.Event) err error {
172181
Or we can use Golang's `encoding/json` package to access the `CloudEvent` directly as
173182
a JSON in form of bytes array:
174183

175-
```golang
176-
func Handle(ctx context.Context, event cloudevents.Event) {
184+
```go
185+
type Function struct{}
186+
187+
func New() *Function { return &Function{} }
188+
189+
func (f *Function) Handle(ctx context.Context, event cloudevents.Event) {
177190

178191
bytes, err := json.Marshal(event)
179192

@@ -186,7 +199,11 @@ As mentioned above, HTTP triggered functions can set the response directly via
186199
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter).
187200

188201
```go
189-
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
202+
type Function struct{}
203+
204+
func New() *Function { return &Function{} }
205+
206+
func (f *Function) Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
190207

191208
// Set response
192209
res.Header().Add("Content-Type", "text/plain")
@@ -206,7 +223,11 @@ to set a unique `ID`, proper `Source` and a `Type` of the CloudEvent. The data c
206223
from a defined structure or from a `map`.
207224

208225
```go
209-
func Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) {
226+
type Function struct{}
227+
228+
func New() *Function { return &Function{} }
229+
230+
func (f *Function) Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) {
210231

211232
// ...
212233

e2e/e2e_core_test.go

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,13 @@ func TestCore_Update(t *testing.T) {
187187
package function
188188
import "fmt"
189189
import "net/http"
190-
func Handle(w http.ResponseWriter, _ *http.Request) {
190+
type Function struct{}
191+
func New() *Function { return &Function{} }
192+
func (f *Function) Handle(w http.ResponseWriter, _ *http.Request) {
191193
fmt.Fprintln(w, "UPDATED")
192194
}
193195
`
194-
err := os.WriteFile(filepath.Join(root, "handle.go"), []byte(update), 0644)
196+
err := os.WriteFile(filepath.Join(root, "function.go"), []byte(update), 0644)
195197
if err != nil {
196198
t.Fatal(err)
197199
}
@@ -260,7 +262,9 @@ func TestCore_Invoke(t *testing.T) {
260262
name := "func-e2e-test-core-invoke"
261263
_ = fromCleanEnv(t, name)
262264

263-
if err := newCmd(t, "init", "-l=go").Run(); err != nil {
265+
if err := newCmd(t, "init", "-l=go",
266+
"--repository", "https://github.com/functions-dev/templates",
267+
"-t", "echo").Run(); err != nil {
264268
t.Fatal(err)
265269
}
266270

@@ -279,7 +283,8 @@ func TestCore_Invoke(t *testing.T) {
279283
}
280284
}()
281285

282-
if !waitFor(t, address) {
286+
if !waitFor(t, address+"?test-echo-param&message=test-echo-param",
287+
withContentMatch("test-echo-param")) {
283288
t.Fatal("service does not appear to have started correctly.")
284289
}
285290

@@ -308,13 +313,69 @@ func TestCore_Invoke(t *testing.T) {
308313
defer func() {
309314
clean(t, name, Namespace)
310315
}()
311-
if !waitFor(t, ksvcUrl(name)) {
316+
317+
if !waitFor(t, ksvcUrl(name)+"?test-echo-param&message=test-echo-param",
318+
withContentMatch("test-echo-param")) {
312319
t.Fatal("function did not deploy correctly")
313320
}
314321

315322
checkInvoke("func-e2e-test-core-invoke-remote")
316323
}
317324

325+
// TestCore_StaticSignature ensures backward compatibility with the static
326+
// (non-instanced) function signature. Functions can use either:
327+
// - Instanced: type MyFunction struct{} + New() + Handle method (default)
328+
// - Static: package-level func Handle(...) in handle.go (legacy, still supported)
329+
func TestCore_StaticSignature(t *testing.T) {
330+
name := "func-e2e-test-core-static"
331+
root := fromCleanEnv(t, name)
332+
333+
// Create func.yaml
334+
funcYaml := fmt.Sprintf(`specVersion: %s
335+
name: %s
336+
runtime: go
337+
created: 2025-01-01T00:00:00Z
338+
`, fn.LastSpecVersion(), name)
339+
if err := os.WriteFile(filepath.Join(root, "func.yaml"), []byte(funcYaml), 0644); err != nil {
340+
t.Fatal(err)
341+
}
342+
343+
// Create go.mod
344+
goMod := "module function\n\ngo 1.23\n"
345+
if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte(goMod), 0644); err != nil {
346+
t.Fatal(err)
347+
}
348+
349+
// Create handle.go with static signature
350+
handleGo := `package function
351+
352+
import (
353+
"fmt"
354+
"net/http"
355+
)
356+
357+
func Handle(w http.ResponseWriter, r *http.Request) {
358+
fmt.Fprintln(w, "OK")
359+
}
360+
`
361+
if err := os.WriteFile(filepath.Join(root, "handle.go"), []byte(handleGo), 0644); err != nil {
362+
t.Fatal(err)
363+
}
364+
365+
// Deploy
366+
if err := newCmd(t, "deploy").Run(); err != nil {
367+
t.Fatal(err)
368+
}
369+
defer func() {
370+
clean(t, name, Namespace)
371+
}()
372+
373+
// Verify - waitFor defaults to checking for "OK"
374+
if !waitFor(t, ksvcUrl(name)) {
375+
t.Fatal("static signature function did not deploy correctly")
376+
}
377+
}
378+
318379
// TestCore_Delete ensures that a function registered as deleted when deleted.
319380
// Also tests list as a side-effect.
320381
//

0 commit comments

Comments
 (0)