Skip to content

Commit

Permalink
feat: move declarative function API into functions package (#99)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anniefu authored Nov 10, 2021
1 parent 0e41555 commit 8f488f2
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 51 deletions.
17 changes: 1 addition & 16 deletions funcframework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"reflect"
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
9 changes: 5 additions & 4 deletions funcframework/framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand Down
24 changes: 24 additions & 0 deletions functions/functions.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
45 changes: 30 additions & 15 deletions internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,60 @@ 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,
}
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,
}
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
}
22 changes: 13 additions & 9 deletions internal/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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")
}
Expand All @@ -51,31 +53,33 @@ 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")
}
}

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")
Expand Down
14 changes: 7 additions & 7 deletions testdata/conformance/function/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ 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"
)

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)
Expand Down Expand Up @@ -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)
}

0 comments on commit 8f488f2

Please sign in to comment.