Skip to content

Commit

Permalink
Update funky to use read context as a generic map (#4)
Browse files Browse the repository at this point in the history
Serializing the context to a predefined go struct causes any property not in the struct to be ignored. This change serializes the context into a generic map so that any new fields will be passed to the language server.
  • Loading branch information
tenczar authored Aug 1, 2018
1 parent ea92b50 commit e7c6ea0
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 22 deletions.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type funkyHandler struct {
}

func (f funkyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var body funky.Message
var body funky.Request
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
resp := funky.Message{
Expand Down
6 changes: 3 additions & 3 deletions pkg/funky/mocks/server.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions pkg/funky/mocks/server_factory.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/funky/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const (
SystemError = "SystemError"
)

// Request a struct to hold the request body sent to a Dispatch function
type Request struct {
Context map[string]interface{} `json:"context"`
Payload interface{} `json:"payload"`
}

// Message a struct to hold the response to a Dispatch function invocation
type Message struct {
Context *Context `json:"context"`
Expand Down
4 changes: 2 additions & 2 deletions pkg/funky/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var Healthy = make(chan struct{})

// Router an interface for delegating function invocations to idle servers
type Router interface {
Delegate(input *Message) (*Message, error)
Delegate(input *Request) (*Message, error)
Shutdown() error
}

Expand Down Expand Up @@ -53,7 +53,7 @@ func NewRouter(numServers int, serverFactory ServerFactory) (*DefaultRouter, err
}

// Delegate delegates function invocation to an idle server
func (r *DefaultRouter) Delegate(input *Message) (*Message, error) {
func (r *DefaultRouter) Delegate(input *Request) (*Message, error) {
server, err := r.findFreeServer()
if err != nil {
return nil, err
Expand Down
14 changes: 10 additions & 4 deletions pkg/funky/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
// Server an interface for managing function servers
type Server interface {
GetPort() uint16
Invoke(input *Message) (interface{}, error)
Invoke(input *Request) (interface{}, error)
Stdout() []string
Stderr() []string
Start() error
Expand Down Expand Up @@ -92,12 +92,18 @@ func (s *DefaultServer) GetPort() uint16 {
}

// Invoke calls the server with the given input to invoke a Dispatch function
func (s *DefaultServer) Invoke(input *Message) (interface{}, error) {
func (s *DefaultServer) Invoke(input *Request) (interface{}, error) {
p, err := json.Marshal(input)

timeout := time.Duration(0)
if input.Context.Deadline != nil {
timeout = time.Until(*input.Context.Deadline)
if deadline, ok := input.Context["deadline"]; ok {
if dl, ok := deadline.(string); ok {
t, err := time.Parse(time.RFC3339, dl)
if err != nil {
return nil, BadRequestError(fmt.Sprintf("Unable to parse deadline: %s", err))
}
timeout = time.Until(t)
}
}

if timeout < 0 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/funky/test/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestDelegateSuccess(t *testing.T) {
server.On("Start").Return(nil)
server.On("IsIdle").Return(true)
server.On("SetIdle", mock.AnythingOfType("bool")).Return().Return()
server.On("Invoke", &funky.Message{}).Return(nil, nil)
server.On("Invoke", &funky.Request{}).Return(nil, nil)
server.On("Stdout").Return([]string{})
server.On("Stderr").Return([]string{})

Expand All @@ -81,7 +81,7 @@ func TestDelegateSuccess(t *testing.T) {

router, _ := funky.NewRouter(1, serverFactory)

_, err := router.Delegate(&funky.Message{})
_, err := router.Delegate(&funky.Request{})

if err != nil {
t.Error("Received unexpected error calling Delegate")
Expand Down
70 changes: 64 additions & 6 deletions pkg/funky/test/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func TestInvokeSuccess(t *testing.T) {
t.Fatalf("Failed to create new server: %+v", err)
}

req := funky.Message{
Context: &funky.Context{},
req := funky.Request{
Context: map[string]interface{}{},
}

resp, err := server.Invoke(&req)
Expand All @@ -82,9 +82,8 @@ func TestInvokeBadRequest(t *testing.T) {
resp := map[string]string{
"myField": "Hello, Jon from Winterfell",
}
respBytes, _ := json.Marshal(resp)
w.WriteHeader(400)
fmt.Fprint(w, string(respBytes))
json.NewEncoder(w).Encode(resp)
}))
defer ts.Close()

Expand All @@ -99,8 +98,8 @@ func TestInvokeBadRequest(t *testing.T) {
t.Fatalf("Failed to create new server: %+v", err)
}

req := funky.Message{
Context: &funky.Context{},
req := funky.Request{
Context: map[string]interface{}{},
}

_, err = server.Invoke(&req)
Expand All @@ -113,3 +112,62 @@ func TestInvokeBadRequest(t *testing.T) {
t.Errorf("Expected FunctionServerError got %s", err)
}
}

func TestInvokeContextPassthrough(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req funky.Request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
t.Errorf("Failed parsing json payload: %s", err)
}

resp := map[string]interface{}{
"myField": "Hello, Jon from Winterfell",
"context": req.Context,
}

json.NewEncoder(w).Encode(resp)
}))
defer ts.Close()

urlParts := strings.Split(ts.URL, ":")
port, err := strconv.Atoi(urlParts[len(urlParts)-1])
if err != nil {
t.Fatalf("Could not convert port %s", urlParts[len(urlParts)-1])
}

server, err := funky.NewServer(uint16(port), exec.Command("echo"))
if err != nil {
t.Fatalf("Failed to create new server: %+v", err)
}

context := map[string]interface{}{
"unknown": "field",
}

req := funky.Request{
Context: context,
}

resp, err := server.Invoke(&req)

if err != nil {
t.Fatalf("Failed to invoke function. Expected success")
}

if result, ok := resp.(map[string]interface{}); ok {
if obj, ok := result["context"]; ok {
if ctx, ok := obj.(map[string]interface{}); ok {
if unknown, ok := ctx["unknown"]; !ok || unknown != "field" {
t.Errorf("Did not properly pass along context value. Received: %s", unknown)
}
} else {
t.Errorf("Expected context to be a map[string]interface")
}
} else {
t.Errorf("Expected context key not found")
}
} else {
t.Errorf("Result from invoke was not a map[string]interface{} like expected")
}
}

0 comments on commit e7c6ea0

Please sign in to comment.