Skip to content
This repository was archived by the owner on May 21, 2025. It is now read-only.

Commit 431809a

Browse files
author
Arran Ubels
committed
Switchable request and response added using JSON Serialization / Deserialization interfaces for passive switching.
1 parent d241f75 commit 431809a

File tree

6 files changed

+390
-21
lines changed

6 files changed

+390
-21
lines changed

core/switchablerequest.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package core
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/aws/aws-lambda-go/events"
7+
)
8+
9+
type SwitchableAPIGatewayRequest struct {
10+
v interface{} // v is Always nil, or a pointer of APIGatewayProxyRequest or APIGatewayV2HTTPRequest
11+
}
12+
13+
// NewSwitchableAPIGatewayRequestV1 creates a new SwitchableAPIGatewayRequest from APIGatewayProxyRequest
14+
func NewSwitchableAPIGatewayRequestV1(v *events.APIGatewayProxyRequest) *SwitchableAPIGatewayRequest {
15+
return &SwitchableAPIGatewayRequest{
16+
v: v,
17+
}
18+
}
19+
// NewSwitchableAPIGatewayRequestV2 creates a new SwitchableAPIGatewayRequest from APIGatewayV2HTTPRequest
20+
func NewSwitchableAPIGatewayRequestV2(v *events.APIGatewayV2HTTPRequest) *SwitchableAPIGatewayRequest {
21+
return &SwitchableAPIGatewayRequest{
22+
v: v,
23+
}
24+
}
25+
26+
// MarshalJSON is a pass through serialization
27+
func (s *SwitchableAPIGatewayRequest) MarshalJSON() ([]byte, error) {
28+
return json.Marshal(s.v)
29+
}
30+
31+
// UnmarshalJSON is a switching serialization based on the presence of fields in the
32+
// source JSON, multiValueQueryStringParameters for APIGatewayProxyRequest and rawQueryString for
33+
// APIGatewayV2HTTPRequest.
34+
func (s *SwitchableAPIGatewayRequest) UnmarshalJSON(b []byte) error {
35+
delta := map[string]json.RawMessage{}
36+
if err := json.Unmarshal(b, &delta); err != nil {
37+
return err
38+
}
39+
_, v1test := delta["multiValueQueryStringParameters"]
40+
_, v2test := delta["rawQueryString"]
41+
s.v = nil
42+
if v1test && !v2test {
43+
s.v = &events.APIGatewayProxyRequest{}
44+
} else if !v1test && v2test {
45+
s.v = &events.APIGatewayV2HTTPRequest{}
46+
} else {
47+
return errors.New("unable to determine request version")
48+
}
49+
return json.Unmarshal(b, s.v)
50+
}
51+
52+
// Version1 returns the contained events.APIGatewayProxyRequest or nil
53+
func (s *SwitchableAPIGatewayRequest) Version1() *events.APIGatewayProxyRequest {
54+
switch v := s.v.(type) {
55+
case *events.APIGatewayProxyRequest:
56+
return v
57+
case events.APIGatewayProxyRequest:
58+
return &v
59+
}
60+
return nil
61+
}
62+
63+
// Version2 returns the contained events.APIGatewayV2HTTPRequest or nil
64+
func (s *SwitchableAPIGatewayRequest) Version2() *events.APIGatewayV2HTTPRequest {
65+
switch v := s.v.(type) {
66+
case *events.APIGatewayV2HTTPRequest:
67+
return v
68+
case events.APIGatewayV2HTTPRequest:
69+
return &v
70+
}
71+
return nil
72+
}

core/switchablerequest_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package core
2+
3+
import (
4+
"encoding/json"
5+
"github.com/aws/aws-lambda-go/events"
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
var _ = Describe("SwitchableAPIGatewayRequest", func() {
11+
Context("Serialization", func() {
12+
It("v1 serialized okay", func() {
13+
e := NewSwitchableAPIGatewayRequestV1(&events.APIGatewayProxyRequest{
14+
MultiValueQueryStringParameters: map[string][]string{},
15+
})
16+
b, err := json.Marshal(e)
17+
Expect(err).To(BeNil())
18+
m := map[string]interface{}{}
19+
err = json.Unmarshal(b, &m)
20+
Expect(err).To(BeNil())
21+
Expect(m["multiValueQueryStringParameters"]).To(Equal(map[string]interface {}{}))
22+
Expect(m["body"]).To(Equal(""))
23+
})
24+
It("v2 serialized okay", func() {
25+
e := NewSwitchableAPIGatewayRequestV2(&events.APIGatewayV2HTTPRequest{})
26+
b, err := json.Marshal(e)
27+
Expect(err).To(BeNil())
28+
m := map[string]interface{}{}
29+
err = json.Unmarshal(b, &m)
30+
Expect(err).To(BeNil())
31+
Expect(m["rawQueryString"]).To(Equal(""))
32+
Expect(m["isBase64Encoded"]).To(Equal(false))
33+
})
34+
})
35+
Context("Deserialization", func() {
36+
It("v1 deserialized okay", func() {
37+
input := &events.APIGatewayProxyRequest{
38+
Body: "234",
39+
MultiValueQueryStringParameters: map[string][]string{
40+
"Test": []string{ "Value1", "Value2", },
41+
},
42+
}
43+
b, _ := json.Marshal(input)
44+
s := SwitchableAPIGatewayRequest{}
45+
err := s.UnmarshalJSON(b)
46+
Expect(err).To(BeNil())
47+
Expect(s.Version2()).To(BeNil())
48+
Expect(s.Version1()).To(BeEquivalentTo(input))
49+
})
50+
It("v2 deserialized okay", func() {
51+
input := &events.APIGatewayV2HTTPRequest{
52+
IsBase64Encoded: true,
53+
RawQueryString: "a=b&c=d",
54+
}
55+
b, _ := json.Marshal(input)
56+
s := SwitchableAPIGatewayRequest{}
57+
err := s.UnmarshalJSON(b)
58+
Expect(err).To(BeNil())
59+
Expect(s.Version1()).To(BeNil())
60+
Expect(s.Version2()).To(BeEquivalentTo(input))
61+
})
62+
})})
63+
64+
func getProxyRequestV2(path string, method string) events.APIGatewayV2HTTPRequest {
65+
return events.APIGatewayV2HTTPRequest{
66+
RequestContext: events.APIGatewayV2HTTPRequestContext{
67+
HTTP: events.APIGatewayV2HTTPRequestContextHTTPDescription{
68+
Path: path,
69+
Method: method,
70+
},
71+
},
72+
RawPath: path,
73+
}
74+
}

core/switchableresponse.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package core
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/aws/aws-lambda-go/events"
7+
)
8+
9+
// SwitchableAPIGatewayResponse is a container for an APIGatewayProxyResponse or an APIGatewayV2HTTPResponse object which
10+
// handles serialization and deserialization and switching between the entities based on the presence of fields in the
11+
// source JSON, multiValueQueryStringParameters for APIGatewayProxyResponse and rawQueryString for
12+
// APIGatewayV2HTTPResponse. It also provides some simple switching functions (wrapped type switching.)
13+
type SwitchableAPIGatewayResponse struct {
14+
v interface{}
15+
}
16+
17+
// NewSwitchableAPIGatewayResponseV1 creates a new SwitchableAPIGatewayResponse from APIGatewayProxyResponse
18+
func NewSwitchableAPIGatewayResponseV1(v *events.APIGatewayProxyResponse) *SwitchableAPIGatewayResponse {
19+
return &SwitchableAPIGatewayResponse{
20+
v: v,
21+
}
22+
}
23+
24+
// NewSwitchableAPIGatewayResponseV2 creates a new SwitchableAPIGatewayResponse from APIGatewayV2HTTPResponse
25+
func NewSwitchableAPIGatewayResponseV2(v *events.APIGatewayV2HTTPResponse) *SwitchableAPIGatewayResponse {
26+
return &SwitchableAPIGatewayResponse{
27+
v: v,
28+
}
29+
}
30+
31+
// MarshalJSON is a pass through serialization
32+
func (s *SwitchableAPIGatewayResponse) MarshalJSON() ([]byte, error) {
33+
return json.Marshal(s.v)
34+
}
35+
36+
// UnmarshalJSON is a switching serialization based on the presence of fields in the
37+
// source JSON, statusCode to verify that it's either APIGatewayProxyResponse or APIGatewayV2HTTPResponse and then
38+
// rawQueryString for to determine if it is APIGatewayV2HTTPResponse or not.
39+
func (s *SwitchableAPIGatewayResponse) UnmarshalJSON(b []byte) error {
40+
delta := map[string]json.RawMessage{}
41+
if err := json.Unmarshal(b, &delta); err != nil {
42+
return err
43+
}
44+
_, test := delta["statusCode"]
45+
_, v2test := delta["cookies"]
46+
s.v = nil
47+
if test && !v2test {
48+
s.v = &events.APIGatewayProxyResponse{}
49+
} else if test && v2test {
50+
s.v = &events.APIGatewayV2HTTPResponse{}
51+
} else {
52+
return errors.New("unable to determine response version")
53+
}
54+
return json.Unmarshal(b, s.v)
55+
}
56+
57+
// Version1 returns the contained events.APIGatewayProxyResponse or nil
58+
func (s *SwitchableAPIGatewayResponse) Version1() *events.APIGatewayProxyResponse {
59+
switch v := s.v.(type) {
60+
case *events.APIGatewayProxyResponse:
61+
return v
62+
case events.APIGatewayProxyResponse:
63+
return &v
64+
}
65+
return nil
66+
}
67+
68+
// Version2 returns the contained events.APIGatewayV2HTTPResponse or nil
69+
func (s *SwitchableAPIGatewayResponse) Version2() *events.APIGatewayV2HTTPResponse {
70+
switch v := s.v.(type) {
71+
case *events.APIGatewayV2HTTPResponse:
72+
return v
73+
case events.APIGatewayV2HTTPResponse:
74+
return &v
75+
}
76+
return nil
77+
}

core/switchableresponse_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package core
2+
3+
import (
4+
"encoding/json"
5+
"github.com/aws/aws-lambda-go/events"
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
var _ = Describe("SwitchableAPIGatewayResponse", func() {
11+
Context("Serialization", func() {
12+
It("v1 serialized okay", func() {
13+
e := NewSwitchableAPIGatewayResponseV1(&events.APIGatewayProxyResponse{})
14+
b, err := json.Marshal(e)
15+
Expect(err).To(BeNil())
16+
m := map[string]interface{}{}
17+
err = json.Unmarshal(b, &m)
18+
Expect(err).To(BeNil())
19+
Expect(m["statusCode"]).To(Equal(0.0))
20+
Expect(m["body"]).To(Equal(""))
21+
})
22+
It("v2 serialized okay", func() {
23+
e := NewSwitchableAPIGatewayResponseV2(&events.APIGatewayV2HTTPResponse{})
24+
b, err := json.Marshal(e)
25+
Expect(err).To(BeNil())
26+
m := map[string]interface{}{}
27+
err = json.Unmarshal(b, &m)
28+
Expect(err).To(BeNil())
29+
Expect(m["statusCode"]).To(Equal(0.0))
30+
Expect(m["body"]).To(Equal(""))
31+
})
32+
})
33+
Context("Deserialization", func() {
34+
It("v1 deserialized okay", func() {
35+
input := &events.APIGatewayProxyResponse{
36+
StatusCode: 123,
37+
Body: "234",
38+
}
39+
b, _ := json.Marshal(input)
40+
s := SwitchableAPIGatewayResponse{}
41+
err := s.UnmarshalJSON(b)
42+
Expect(err).To(BeNil())
43+
Expect(s.Version2()).To(BeNil())
44+
Expect(s.Version1()).To(BeEquivalentTo(input))
45+
})
46+
It("v2 deserialized okay", func() {
47+
input := &events.APIGatewayV2HTTPResponse{
48+
StatusCode: 123,
49+
Body: "234",
50+
Cookies: []string{"4", "5"},
51+
}
52+
b, _ := json.Marshal(input)
53+
s := SwitchableAPIGatewayResponse{}
54+
err := s.UnmarshalJSON(b)
55+
Expect(err).To(BeNil())
56+
Expect(s.Version1()).To(BeNil())
57+
Expect(s.Version2()).To(BeEquivalentTo(input))
58+
})
59+
})
60+
})
61+

gorillamux/adapter.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package gorillamux
22

33
import (
44
"context"
5+
"errors"
56
"net/http"
67

7-
"github.com/aws/aws-lambda-go/events"
88
"github.com/awslabs/aws-lambda-go-api-proxy/core"
99
"github.com/gorilla/mux"
1010
)
1111

1212
type GorillaMuxAdapter struct {
13-
core.RequestAccessor
13+
RequestAccessor core.RequestAccessor
14+
RequestAccessorV2 core.RequestAccessorV2
1415
router *mux.Router
1516
}
1617

@@ -20,34 +21,68 @@ func New(router *mux.Router) *GorillaMuxAdapter {
2021
}
2122
}
2223

23-
// Proxy receives an API Gateway proxy event, transforms it into an http.Request
24+
// Proxy receives an API Gateway proxy event or API Gateway V2 event, transforms it into an http.Request
2425
// object, and sends it to the mux.Router for routing.
2526
// It returns a proxy response object generated from the http.ResponseWriter.
26-
func (h *GorillaMuxAdapter) Proxy(event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
27-
req, err := h.ProxyEventToHTTPRequest(event)
28-
return h.proxyInternal(req, err)
27+
func (h *GorillaMuxAdapter) Proxy(event core.SwitchableAPIGatewayRequest) (*core.SwitchableAPIGatewayResponse, error) {
28+
if event.Version1() != nil {
29+
req, err := h.RequestAccessor.ProxyEventToHTTPRequest(*event.Version1())
30+
return h.proxyInternal(req, err)
31+
} else if event.Version2() != nil {
32+
req, err := h.RequestAccessorV2.ProxyEventToHTTPRequest(*event.Version2())
33+
return h.proxyInternalV2(req, err)
34+
} else {
35+
return &core.SwitchableAPIGatewayResponse{}, core.NewLoggedError("Could not convert proxy event to request: %v", errors.New("Unable to determine version "))
36+
}
2937
}
3038

31-
// ProxyWithContext receives context and an API Gateway proxy event,
39+
// ProxyWithContext receives context and an API Gateway proxy event or API Gateway V2 event,
3240
// transforms them into an http.Request object, and sends it to the mux.Router for routing.
3341
// It returns a proxy response object generated from the http.ResponseWriter.
34-
func (h *GorillaMuxAdapter) ProxyWithContext(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
35-
req, err := h.EventToRequestWithContext(ctx, event)
36-
return h.proxyInternal(req, err)
42+
func (h *GorillaMuxAdapter) ProxyWithContext(ctx context.Context, event core.SwitchableAPIGatewayRequest) (*core.SwitchableAPIGatewayResponse, error) {
43+
if event.Version1() != nil {
44+
req, err := h.RequestAccessor.EventToRequestWithContext(ctx, *event.Version1())
45+
return h.proxyInternal(req, err)
46+
} else if event.Version2() != nil {
47+
req, err := h.RequestAccessorV2.EventToRequestWithContext(ctx, *event.Version2())
48+
return h.proxyInternalV2(req, err)
49+
} else {
50+
return &core.SwitchableAPIGatewayResponse{}, core.NewLoggedError("Could not convert proxy event to request: %v", errors.New("Unable to determine version "))
51+
}
3752
}
3853

39-
func (h *GorillaMuxAdapter) proxyInternal(req *http.Request, err error) (events.APIGatewayProxyResponse, error) {
54+
func (h *GorillaMuxAdapter) proxyInternal(req *http.Request, err error) (*core.SwitchableAPIGatewayResponse, error) {
4055
if err != nil {
41-
return core.GatewayTimeout(), core.NewLoggedError("Could not convert proxy event to request: %v", err)
56+
timeout := core.GatewayTimeout()
57+
return core.NewSwitchableAPIGatewayResponseV1(&timeout), core.NewLoggedError("Could not convert proxy event to request: %v", err)
4258
}
4359

4460
w := core.NewProxyResponseWriter()
4561
h.router.ServeHTTP(http.ResponseWriter(w), req)
4662

4763
resp, err := w.GetProxyResponse()
4864
if err != nil {
49-
return core.GatewayTimeout(), core.NewLoggedError("Error while generating proxy response: %v", err)
65+
timeout := core.GatewayTimeout()
66+
return core.NewSwitchableAPIGatewayResponseV1(&timeout), core.NewLoggedError("Error while generating proxy response: %v", err)
67+
}
68+
69+
return core.NewSwitchableAPIGatewayResponseV1(&resp), nil
70+
}
71+
72+
func (h *GorillaMuxAdapter) proxyInternalV2(req *http.Request, err error) (*core.SwitchableAPIGatewayResponse, error) {
73+
if err != nil {
74+
timeout := core.GatewayTimeoutV2()
75+
return core.NewSwitchableAPIGatewayResponseV2(&timeout), core.NewLoggedError("Could not convert proxy event to request: %v", err)
76+
}
77+
78+
w := core.NewProxyResponseWriterV2()
79+
h.router.ServeHTTP(http.ResponseWriter(w), req)
80+
81+
resp, err := w.GetProxyResponse()
82+
if err != nil {
83+
timeout := core.GatewayTimeoutV2()
84+
return core.NewSwitchableAPIGatewayResponseV2(&timeout), core.NewLoggedError("Error while generating proxy response: %v", err)
5085
}
5186

52-
return resp, nil
87+
return core.NewSwitchableAPIGatewayResponseV2(&resp), nil
5388
}

0 commit comments

Comments
 (0)