From 8f488f29af1f7631a3a840c9b61ab6da0773a848 Mon Sep 17 00:00:00 2001 From: Annie Fu <16651409+anniefu@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:31:17 -0800 Subject: [PATCH] feat: move declarative function API into `functions` package (#99) This separates the encouraged, declarative function signature API away from the `funcframework` package, which should primarily be intended only for local testing going forward. Also, refactor some of the internal/registry implementation so it's more obvious that `functions` package and `funcframework` package are sharing the same registry. --- funcframework/framework.go | 17 +-------- funcframework/framework_test.go | 9 +++-- functions/functions.go | 24 ++++++++++++ internal/registry/registry.go | 45 +++++++++++++++-------- internal/registry/registry_test.go | 22 ++++++----- testdata/conformance/function/function.go | 14 +++---- 6 files changed, 80 insertions(+), 51 deletions(-) create mode 100644 functions/functions.go diff --git a/funcframework/framework.go b/funcframework/framework.go index bfc9263..ba29ecd 100644 --- a/funcframework/framework.go +++ b/funcframework/framework.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" "os" "reflect" @@ -109,20 +108,6 @@ func RegisterCloudEventFunctionContext(ctx context.Context, path string, fn func return err } -// Declaratively registers a HTTP function. -func HTTP(name string, fn func(http.ResponseWriter, *http.Request)) { - if err := registry.RegisterHTTP(name, fn); err != nil { - log.Fatalf("failure to register function: %s", err) - } -} - -// Declaratively registers a CloudEvent function. -func CloudEvent(name string, fn func(context.Context, cloudevents.Event) error) { - if err := registry.RegisterCloudEvent(name, fn); err != nil { - log.Fatalf("failure to register function: %s", err) - } -} - // Start serves an HTTP server with registered function(s). func Start(port string) error { // If FUNCTION_TARGET, try to start with that registered function @@ -135,7 +120,7 @@ func Start(port string) error { } // Check if there's a registered function, and use if possible - if fn, ok := registry.GetRegisteredFunction(target); ok { + if fn, ok := registry.Default().GetRegisteredFunction(target); ok { ctx := context.Background() if fn.HTTPFn != nil { server, err := wrapHTTPFunction("/", fn.HTTPFn) diff --git a/funcframework/framework_test.go b/funcframework/framework_test.go index 785b5f0..4cea99f 100644 --- a/funcframework/framework_test.go +++ b/funcframework/framework_test.go @@ -25,6 +25,7 @@ import ( "os" "testing" + "github.com/GoogleCloudPlatform/functions-framework-go/functions" "github.com/GoogleCloudPlatform/functions-framework-go/internal/registry" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/google/go-cmp/cmp" @@ -455,11 +456,11 @@ func TestDeclarativeFunctionHTTP(t *testing.T) { } // register functions - HTTP(funcName, func(w http.ResponseWriter, r *http.Request) { + functions.HTTP(funcName, func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }) - if _, ok := registry.GetRegisteredFunction(funcName); !ok { + if _, ok := registry.Default().GetRegisteredFunction(funcName); !ok { t.Fatalf("could not get registered function: %s", funcName) } @@ -480,9 +481,9 @@ func TestDeclarativeFunctionCloudEvent(t *testing.T) { } // register functions - CloudEvent(funcName, dummyCloudEvent) + functions.CloudEvent(funcName, dummyCloudEvent) - if _, ok := registry.GetRegisteredFunction(funcName); !ok { + if _, ok := registry.Default().GetRegisteredFunction(funcName); !ok { t.Fatalf("could not get registered function: %s", funcName) } diff --git a/functions/functions.go b/functions/functions.go new file mode 100644 index 0000000..7312ed2 --- /dev/null +++ b/functions/functions.go @@ -0,0 +1,24 @@ +package functions + +import ( + "context" + "log" + "net/http" + + "github.com/GoogleCloudPlatform/functions-framework-go/internal/registry" + cloudevents "github.com/cloudevents/sdk-go/v2" +) + +// Declaratively registers a HTTP function. +func HTTP(name string, fn func(http.ResponseWriter, *http.Request)) { + if err := registry.Default().RegisterHTTP(name, fn); err != nil { + log.Fatalf("failure to register function: %s", err) + } +} + +// Declaratively registers a CloudEvent function. +func CloudEvent(name string, fn func(context.Context, cloudevents.Event) error) { + if err := registry.Default().RegisterCloudEvent(name, fn); err != nil { + log.Fatalf("failure to register function: %s", err) + } +} diff --git a/internal/registry/registry.go b/internal/registry/registry.go index e635be4..a33f863 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -8,23 +8,38 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" ) -// A declaratively registered function +// RegisteredFunction represents a function that has been +// registered with the registry. type RegisteredFunction struct { Name string // The name of the function CloudEventFn func(context.Context, cloudevents.Event) error // Optional: The user's CloudEvent function HTTPFn func(http.ResponseWriter, *http.Request) // Optional: The user's HTTP function } -var ( - function_registry = map[string]RegisteredFunction{} -) +// Registry is a registry of functions. +type Registry struct { + functions map[string]RegisteredFunction +} + +var defaultInstance = New() + +// Default returns the default, singleton registry instance. +func Default() *Registry { + return defaultInstance +} + +func New() *Registry { + return &Registry{ + functions: map[string]RegisteredFunction{}, + } +} -// Registers a HTTP function with a given name -func RegisterHTTP(name string, fn func(http.ResponseWriter, *http.Request)) error { - if _, ok := function_registry[name]; ok { +// RegisterHTTP a HTTP function with a given name +func (r *Registry) RegisterHTTP(name string, fn func(http.ResponseWriter, *http.Request)) error { + if _, ok := r.functions[name]; ok { return fmt.Errorf("function name already registered: %s", name) } - function_registry[name] = RegisteredFunction{ + r.functions[name] = RegisteredFunction{ Name: name, CloudEventFn: nil, HTTPFn: fn, @@ -32,12 +47,12 @@ func RegisterHTTP(name string, fn func(http.ResponseWriter, *http.Request)) erro return nil } -// Registers a CloudEvent function with a given name -func RegisterCloudEvent(name string, fn func(context.Context, cloudevents.Event) error) error { - if _, ok := function_registry[name]; ok { +// RegistryCloudEvent a CloudEvent function with a given name +func (r *Registry) RegisterCloudEvent(name string, fn func(context.Context, cloudevents.Event) error) error { + if _, ok := r.functions[name]; ok { return fmt.Errorf("function name already registered: %s", name) } - function_registry[name] = RegisteredFunction{ + r.functions[name] = RegisteredFunction{ Name: name, CloudEventFn: fn, HTTPFn: nil, @@ -45,8 +60,8 @@ func RegisterCloudEvent(name string, fn func(context.Context, cloudevents.Event) return nil } -// Gets a registered function by name -func GetRegisteredFunction(name string) (RegisteredFunction, bool) { - fn, ok := function_registry[name] +// GetRegisteredFunction a registered function by name +func (r *Registry) GetRegisteredFunction(name string) (RegisteredFunction, bool) { + fn, ok := r.functions[name] return fn, ok } diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index 2b9d705..edb2730 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -23,11 +23,12 @@ import ( ) func TestRegisterHTTP(t *testing.T) { - RegisterHTTP("httpfn", func(w http.ResponseWriter, r *http.Request) { + registry := New() + registry.RegisterHTTP("httpfn", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }) - fn, ok := GetRegisteredFunction("httpfn") + fn, ok := registry.GetRegisteredFunction("httpfn") if !ok { t.Fatalf("Expected function to be registered") } @@ -37,11 +38,12 @@ func TestRegisterHTTP(t *testing.T) { } func TestRegisterCE(t *testing.T) { - RegisterCloudEvent("cefn", func(context.Context, cloudevents.Event) error { + registry := New() + registry.RegisterCloudEvent("cefn", func(context.Context, cloudevents.Event) error { return nil }) - fn, ok := GetRegisteredFunction("cefn") + fn, ok := registry.GetRegisteredFunction("cefn") if !ok { t.Fatalf("Expected function to be registered") } @@ -51,17 +53,18 @@ func TestRegisterCE(t *testing.T) { } func TestRegisterMultipleFunctions(t *testing.T) { - if err := RegisterHTTP("multifn1", func(w http.ResponseWriter, r *http.Request) { + registry := New() + if err := registry.RegisterHTTP("multifn1", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }); err != nil { t.Error("Expected \"multifn1\" function to be registered") } - if err := RegisterHTTP("multifn2", func(w http.ResponseWriter, r *http.Request) { + if err := registry.RegisterHTTP("multifn2", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World 2!") }); err != nil { t.Error("Expected \"multifn2\" function to be registered") } - if err := RegisterCloudEvent("multifn3", func(context.Context, cloudevents.Event) error { + if err := registry.RegisterCloudEvent("multifn3", func(context.Context, cloudevents.Event) error { return nil }); err != nil { t.Error("Expected \"multifn3\" function to be registered") @@ -69,13 +72,14 @@ func TestRegisterMultipleFunctions(t *testing.T) { } func TestRegisterMultipleFunctionsError(t *testing.T) { - if err := RegisterHTTP("samename", func(w http.ResponseWriter, r *http.Request) { + registry := New() + if err := registry.RegisterHTTP("samename", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }); err != nil { t.Error("Expected no error registering function") } - if err := RegisterHTTP("samename", func(w http.ResponseWriter, r *http.Request) { + if err := registry.RegisterHTTP("samename", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World 2!") }); err == nil { t.Error("Expected error registering function with same name") diff --git a/testdata/conformance/function/function.go b/testdata/conformance/function/function.go index 92801c4..e8393aa 100644 --- a/testdata/conformance/function/function.go +++ b/testdata/conformance/function/function.go @@ -24,7 +24,7 @@ import ( "net/http" "cloud.google.com/go/functions/metadata" - "github.com/GoogleCloudPlatform/functions-framework-go/funcframework" + "github.com/GoogleCloudPlatform/functions-framework-go/functions" cloudevents "github.com/cloudevents/sdk-go/v2" ) @@ -32,6 +32,12 @@ const ( outputFile = "function_output.json" ) +// Register declarative functions +func init() { + functions.HTTP("declarativeHTTP", HTTP) + functions.CloudEvent("declarativeCloudEvent", CloudEvent) +} + // HTTP is a simple HTTP function that writes the request body to the response body. func HTTP(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) @@ -81,9 +87,3 @@ func CloudEvent(ctx context.Context, ce cloudevents.Event) error { return nil } - -// Register declarative functions -func init() { - funcframework.HTTP("declarativeHTTP", HTTP) - funcframework.CloudEvent("declarativeCloudEvent", CloudEvent) -}