Skip to content

Commit e7c6ea0

Browse files
authored
Update funky to use read context as a generic map (#4)
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.
1 parent ea92b50 commit e7c6ea0

File tree

8 files changed

+90
-22
lines changed

8 files changed

+90
-22
lines changed

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type funkyHandler struct {
2828
}
2929

3030
func (f funkyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
31-
var body funky.Message
31+
var body funky.Request
3232
err := json.NewDecoder(r.Body).Decode(&body)
3333
if err != nil {
3434
resp := funky.Message{

pkg/funky/mocks/server.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/funky/mocks/server_factory.go

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/funky/response.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ const (
99
SystemError = "SystemError"
1010
)
1111

12+
// Request a struct to hold the request body sent to a Dispatch function
13+
type Request struct {
14+
Context map[string]interface{} `json:"context"`
15+
Payload interface{} `json:"payload"`
16+
}
17+
1218
// Message a struct to hold the response to a Dispatch function invocation
1319
type Message struct {
1420
Context *Context `json:"context"`

pkg/funky/router.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var Healthy = make(chan struct{})
2121

2222
// Router an interface for delegating function invocations to idle servers
2323
type Router interface {
24-
Delegate(input *Message) (*Message, error)
24+
Delegate(input *Request) (*Message, error)
2525
Shutdown() error
2626
}
2727

@@ -53,7 +53,7 @@ func NewRouter(numServers int, serverFactory ServerFactory) (*DefaultRouter, err
5353
}
5454

5555
// Delegate delegates function invocation to an idle server
56-
func (r *DefaultRouter) Delegate(input *Message) (*Message, error) {
56+
func (r *DefaultRouter) Delegate(input *Request) (*Message, error) {
5757
server, err := r.findFreeServer()
5858
if err != nil {
5959
return nil, err

pkg/funky/server.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
// Server an interface for managing function servers
2222
type Server interface {
2323
GetPort() uint16
24-
Invoke(input *Message) (interface{}, error)
24+
Invoke(input *Request) (interface{}, error)
2525
Stdout() []string
2626
Stderr() []string
2727
Start() error
@@ -92,12 +92,18 @@ func (s *DefaultServer) GetPort() uint16 {
9292
}
9393

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

9898
timeout := time.Duration(0)
99-
if input.Context.Deadline != nil {
100-
timeout = time.Until(*input.Context.Deadline)
99+
if deadline, ok := input.Context["deadline"]; ok {
100+
if dl, ok := deadline.(string); ok {
101+
t, err := time.Parse(time.RFC3339, dl)
102+
if err != nil {
103+
return nil, BadRequestError(fmt.Sprintf("Unable to parse deadline: %s", err))
104+
}
105+
timeout = time.Until(t)
106+
}
101107
}
102108

103109
if timeout < 0 {

pkg/funky/test/router_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestDelegateSuccess(t *testing.T) {
7272
server.On("Start").Return(nil)
7373
server.On("IsIdle").Return(true)
7474
server.On("SetIdle", mock.AnythingOfType("bool")).Return().Return()
75-
server.On("Invoke", &funky.Message{}).Return(nil, nil)
75+
server.On("Invoke", &funky.Request{}).Return(nil, nil)
7676
server.On("Stdout").Return([]string{})
7777
server.On("Stderr").Return([]string{})
7878

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

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

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

8686
if err != nil {
8787
t.Error("Received unexpected error calling Delegate")

pkg/funky/test/server_test.go

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ func TestInvokeSuccess(t *testing.T) {
5757
t.Fatalf("Failed to create new server: %+v", err)
5858
}
5959

60-
req := funky.Message{
61-
Context: &funky.Context{},
60+
req := funky.Request{
61+
Context: map[string]interface{}{},
6262
}
6363

6464
resp, err := server.Invoke(&req)
@@ -82,9 +82,8 @@ func TestInvokeBadRequest(t *testing.T) {
8282
resp := map[string]string{
8383
"myField": "Hello, Jon from Winterfell",
8484
}
85-
respBytes, _ := json.Marshal(resp)
8685
w.WriteHeader(400)
87-
fmt.Fprint(w, string(respBytes))
86+
json.NewEncoder(w).Encode(resp)
8887
}))
8988
defer ts.Close()
9089

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

102-
req := funky.Message{
103-
Context: &funky.Context{},
101+
req := funky.Request{
102+
Context: map[string]interface{}{},
104103
}
105104

106105
_, err = server.Invoke(&req)
@@ -113,3 +112,62 @@ func TestInvokeBadRequest(t *testing.T) {
113112
t.Errorf("Expected FunctionServerError got %s", err)
114113
}
115114
}
115+
116+
func TestInvokeContextPassthrough(t *testing.T) {
117+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118+
var req funky.Request
119+
err := json.NewDecoder(r.Body).Decode(&req)
120+
if err != nil {
121+
t.Errorf("Failed parsing json payload: %s", err)
122+
}
123+
124+
resp := map[string]interface{}{
125+
"myField": "Hello, Jon from Winterfell",
126+
"context": req.Context,
127+
}
128+
129+
json.NewEncoder(w).Encode(resp)
130+
}))
131+
defer ts.Close()
132+
133+
urlParts := strings.Split(ts.URL, ":")
134+
port, err := strconv.Atoi(urlParts[len(urlParts)-1])
135+
if err != nil {
136+
t.Fatalf("Could not convert port %s", urlParts[len(urlParts)-1])
137+
}
138+
139+
server, err := funky.NewServer(uint16(port), exec.Command("echo"))
140+
if err != nil {
141+
t.Fatalf("Failed to create new server: %+v", err)
142+
}
143+
144+
context := map[string]interface{}{
145+
"unknown": "field",
146+
}
147+
148+
req := funky.Request{
149+
Context: context,
150+
}
151+
152+
resp, err := server.Invoke(&req)
153+
154+
if err != nil {
155+
t.Fatalf("Failed to invoke function. Expected success")
156+
}
157+
158+
if result, ok := resp.(map[string]interface{}); ok {
159+
if obj, ok := result["context"]; ok {
160+
if ctx, ok := obj.(map[string]interface{}); ok {
161+
if unknown, ok := ctx["unknown"]; !ok || unknown != "field" {
162+
t.Errorf("Did not properly pass along context value. Received: %s", unknown)
163+
}
164+
} else {
165+
t.Errorf("Expected context to be a map[string]interface")
166+
}
167+
} else {
168+
t.Errorf("Expected context key not found")
169+
}
170+
} else {
171+
t.Errorf("Result from invoke was not a map[string]interface{} like expected")
172+
}
173+
}

0 commit comments

Comments
 (0)