Skip to content

Commit eabfd69

Browse files
Introduce HandlerTrace
1 parent fb8f88d commit eabfd69

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

lambda/handler.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,42 @@ func validateReturns(handler reflect.Type) error {
7070
return nil
7171
}
7272

73+
// HandlerTrace allows handlers which wrap the return value of NewHandler to
74+
// access to the request and response events.
75+
type HandlerTrace struct {
76+
RequestEvent func(context.Context, interface{})
77+
ResponseEvent func(context.Context, interface{})
78+
}
79+
80+
func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Context, interface{}) {
81+
return func(ctx context.Context, event interface{}) {
82+
if nil != f1 {
83+
f1(ctx, event)
84+
}
85+
if nil != f2 {
86+
f2(ctx, event)
87+
}
88+
}
89+
}
90+
91+
type handlerTraceKey struct{}
92+
93+
// WithHandlerTrace adds callbacks to the provided context which allows handlers
94+
// which wrap the return value of NewHandler to access to the request and
95+
// response events.
96+
func WithHandlerTrace(ctx context.Context, trace HandlerTrace) context.Context {
97+
existing := contextHandlerTrace(ctx)
98+
return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{
99+
RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent),
100+
ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent),
101+
})
102+
}
103+
104+
func contextHandlerTrace(ctx context.Context) HandlerTrace {
105+
trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace)
106+
return trace
107+
}
108+
73109
// NewHandler creates a base lambda handler from the given handler function. The
74110
// returned Handler performs JSON deserialization and deserialization, and
75111
// delegates to the input handler function. The handler function parameter must
@@ -95,6 +131,9 @@ func NewHandler(handlerFunc interface{}) Handler {
95131
}
96132

97133
return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) {
134+
135+
trace := contextHandlerTrace(ctx)
136+
98137
// construct arguments
99138
var args []reflect.Value
100139
if takesContext {
@@ -107,7 +146,9 @@ func NewHandler(handlerFunc interface{}) Handler {
107146
if err := json.Unmarshal(payload, event.Interface()); err != nil {
108147
return nil, err
109148
}
110-
149+
if nil != trace.RequestEvent {
150+
trace.RequestEvent(ctx, event.Elem().Interface())
151+
}
111152
args = append(args, event.Elem())
112153
}
113154

@@ -123,6 +164,10 @@ func NewHandler(handlerFunc interface{}) Handler {
123164
var val interface{}
124165
if len(response) > 1 {
125166
val = response[0].Interface()
167+
168+
if nil != trace.ResponseEvent {
169+
trace.ResponseEvent(ctx, val)
170+
}
126171
}
127172

128173
return val, err

lambda/handler_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,64 @@ func TestInvalidJsonInput(t *testing.T) {
206206
assert.Equal(t, "unexpected end of JSON input", err.Error())
207207

208208
}
209+
210+
func TestHandlerTrace(t *testing.T) {
211+
handler := NewHandler(func(ctx context.Context, x int) (int, error) {
212+
if x != 123 {
213+
t.Error(x)
214+
}
215+
return 456, nil
216+
})
217+
requestHistory := ""
218+
responseHistory := ""
219+
checkInt := func(e interface{}, expected int) {
220+
nt, ok := e.(int)
221+
if !ok {
222+
t.Error("not int as expected", e)
223+
return
224+
}
225+
if nt != expected {
226+
t.Error("unexpected value", nt, expected)
227+
}
228+
}
229+
ctx := context.Background()
230+
ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace
231+
ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent
232+
RequestEvent: func(c context.Context, e interface{}) {
233+
requestHistory += "A"
234+
checkInt(e, 123)
235+
},
236+
})
237+
ctx = WithHandlerTrace(ctx, HandlerTrace{ // with ResponseEvent
238+
ResponseEvent: func(c context.Context, e interface{}) {
239+
responseHistory += "X"
240+
checkInt(e, 456)
241+
},
242+
})
243+
ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent and ResponseEvent
244+
RequestEvent: func(c context.Context, e interface{}) {
245+
requestHistory += "B"
246+
checkInt(e, 123)
247+
},
248+
ResponseEvent: func(c context.Context, e interface{}) {
249+
responseHistory += "Y"
250+
checkInt(e, 456)
251+
},
252+
})
253+
ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace
254+
255+
payload := []byte(`123`)
256+
js, err := handler.Invoke(ctx, payload)
257+
if err != nil {
258+
t.Error("unexpected handler error", err)
259+
}
260+
if string(js) != "456" {
261+
t.Error("unexpected handler output", string(js))
262+
}
263+
if requestHistory != "AB" {
264+
t.Error("request callbacks not called as expected", requestHistory)
265+
}
266+
if responseHistory != "XY" {
267+
t.Error("response callbacks not called as expected", responseHistory)
268+
}
269+
}

0 commit comments

Comments
 (0)