mirror of https://github.com/knative/docs.git
190 lines
5.2 KiB
Go
190 lines
5.2 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/cloudevents/sdk-go/v2/event"
|
|
"github.com/cloudevents/sdk-go/v2/protocol"
|
|
)
|
|
|
|
// Receive is the signature of a fn to be invoked for incoming cloudevents.
|
|
type ReceiveFull func(context.Context, event.Event) protocol.Result
|
|
|
|
type receiverFn struct {
|
|
numIn int
|
|
numOut int
|
|
fnValue reflect.Value
|
|
|
|
hasContextIn bool
|
|
hasEventIn bool
|
|
|
|
hasEventOut bool
|
|
hasResultOut bool
|
|
}
|
|
|
|
const (
|
|
inParamUsage = "expected a function taking either no parameters, one or more of (context.Context, event.Event) ordered"
|
|
outParamUsage = "expected a function returning one or mode of (*event.Event, protocol.Result) ordered"
|
|
)
|
|
|
|
var (
|
|
contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
|
eventType = reflect.TypeOf((*event.Event)(nil)).Elem()
|
|
eventPtrType = reflect.TypeOf((*event.Event)(nil)) // want the ptr type
|
|
resultType = reflect.TypeOf((*protocol.Result)(nil)).Elem()
|
|
)
|
|
|
|
// receiver creates a receiverFn wrapper class that is used by the client to
|
|
// validate and invoke the provided function.
|
|
// Valid fn signatures are:
|
|
// * func()
|
|
// * func() error
|
|
// * func(context.Context)
|
|
// * func(context.Context) transport.Result
|
|
// * func(event.Event)
|
|
// * func(event.Event) transport.Result
|
|
// * func(context.Context, event.Event)
|
|
// * func(context.Context, event.Event) transport.Result
|
|
// * func(event.Event) *event.Event
|
|
// * func(event.Event) (*event.Event, transport.Result)
|
|
// * func(context.Context, event.Event, *event.Event
|
|
// * func(context.Context, event.Event) (*event.Event, transport.Result)
|
|
//
|
|
func receiver(fn interface{}) (*receiverFn, error) {
|
|
fnType := reflect.TypeOf(fn)
|
|
if fnType.Kind() != reflect.Func {
|
|
return nil, errors.New("must pass a function to handle events")
|
|
}
|
|
|
|
r := &receiverFn{
|
|
fnValue: reflect.ValueOf(fn),
|
|
numIn: fnType.NumIn(),
|
|
numOut: fnType.NumOut(),
|
|
}
|
|
|
|
if err := r.validate(fnType); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (r *receiverFn) invoke(ctx context.Context, e event.Event) (*event.Event, protocol.Result) {
|
|
args := make([]reflect.Value, 0, r.numIn)
|
|
|
|
if r.numIn > 0 {
|
|
if r.hasContextIn {
|
|
args = append(args, reflect.ValueOf(ctx))
|
|
}
|
|
if r.hasEventIn {
|
|
args = append(args, reflect.ValueOf(e))
|
|
}
|
|
}
|
|
v := r.fnValue.Call(args)
|
|
var respOut protocol.Result
|
|
var eOut *event.Event
|
|
if r.numOut > 0 {
|
|
i := 0
|
|
if r.hasEventOut {
|
|
if eo, ok := v[i].Interface().(*event.Event); ok {
|
|
eOut = eo
|
|
}
|
|
i++ // <-- note, need to inc i.
|
|
}
|
|
if r.hasResultOut {
|
|
if resp, ok := v[i].Interface().(protocol.Result); ok {
|
|
respOut = resp
|
|
}
|
|
}
|
|
}
|
|
return eOut, respOut
|
|
}
|
|
|
|
// Verifies that the inputs to a function have a valid signature
|
|
// Valid input is to be [0, all] of
|
|
// context.Context, event.Event in this order.
|
|
func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error {
|
|
r.hasContextIn = false
|
|
r.hasEventIn = false
|
|
|
|
switch fnType.NumIn() {
|
|
case 2:
|
|
// has to be (context.Context, event.Event)
|
|
if !fnType.In(1).ConvertibleTo(eventType) {
|
|
return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Event", inParamUsage, fnType.In(1))
|
|
} else {
|
|
r.hasEventIn = true
|
|
}
|
|
fallthrough
|
|
case 1:
|
|
if !fnType.In(0).ConvertibleTo(contextType) {
|
|
if !fnType.In(0).ConvertibleTo(eventType) {
|
|
return fmt.Errorf("%s; cannot convert parameter 1 from %s to context.Context or event.Event", inParamUsage, fnType.In(0))
|
|
} else if r.hasEventIn {
|
|
return fmt.Errorf("%s; duplicate parameter of type event.Event", inParamUsage)
|
|
} else {
|
|
r.hasEventIn = true
|
|
}
|
|
} else {
|
|
r.hasContextIn = true
|
|
}
|
|
fallthrough
|
|
case 0:
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("%s; function has too many parameters (%d)", inParamUsage, fnType.NumIn())
|
|
}
|
|
}
|
|
|
|
// Verifies that the outputs of a function have a valid signature
|
|
// Valid output signatures to be [0, all] of
|
|
// *event.Event, transport.Result in this order
|
|
func (r *receiverFn) validateOutParamSignature(fnType reflect.Type) error {
|
|
r.hasEventOut = false
|
|
r.hasResultOut = false
|
|
|
|
switch fnType.NumOut() {
|
|
case 2:
|
|
// has to be (*event.Event, transport.Result)
|
|
if !fnType.Out(1).ConvertibleTo(resultType) {
|
|
return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Response", outParamUsage, fnType.Out(1))
|
|
} else {
|
|
r.hasResultOut = true
|
|
}
|
|
fallthrough
|
|
case 1:
|
|
if !fnType.Out(0).ConvertibleTo(resultType) {
|
|
if !fnType.Out(0).ConvertibleTo(eventPtrType) {
|
|
return fmt.Errorf("%s; cannot convert parameter 1 from %s to *event.Event or transport.Result", outParamUsage, fnType.Out(0))
|
|
} else {
|
|
r.hasEventOut = true
|
|
}
|
|
} else if r.hasResultOut {
|
|
return fmt.Errorf("%s; duplicate parameter of type event.Response", outParamUsage)
|
|
} else {
|
|
r.hasResultOut = true
|
|
}
|
|
fallthrough
|
|
case 0:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("%s; function has too many return types (%d)", outParamUsage, fnType.NumOut())
|
|
}
|
|
}
|
|
|
|
// validateReceiverFn validates that a function has the right number of in and
|
|
// out params and that they are of allowed types.
|
|
func (r *receiverFn) validate(fnType reflect.Type) error {
|
|
if err := r.validateInParamSignature(fnType); err != nil {
|
|
return err
|
|
}
|
|
if err := r.validateOutParamSignature(fnType); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|