godog/internal/models/stepdef.go
tfreville fbed999ad8 feat(step_definition): Allows to define step function without return.
Issue: It is not possible to use function without return when
       matching steps, resulting in a lot of Nil only error
       returns.

Fix: Allows to provide empty result function by correctly matching
     reflect Calls on step Handler.
     When nothing is returned by the Handler, it will return
     nil as if errors was nil.
2021-07-13 10:31:39 +02:00

190 строки
5,1 КиБ
Go

package models
import (
"fmt"
"reflect"
"strconv"
"github.com/cucumber/messages-go/v16"
"github.com/cucumber/godog/formatters"
)
var typeOfBytes = reflect.TypeOf([]byte(nil))
// StepDefinition ...
type StepDefinition struct {
formatters.StepDefinition
Args []interface{}
HandlerValue reflect.Value
// multistep related
Nested bool
Undefined []string
}
// Run a step with the matched arguments using reflect
func (sd *StepDefinition) Run() interface{} {
typ := sd.HandlerValue.Type()
if len(sd.Args) < typ.NumIn() {
return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.Args))
}
var values []reflect.Value
for i := 0; i < typ.NumIn(); i++ {
param := typ.In(i)
switch param.Kind() {
case reflect.Int:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int(v)))
case reflect.Int64:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int64: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int64(v)))
case reflect.Int32:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int32: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int32(v)))
case reflect.Int16:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 16)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int16: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int16(v)))
case reflect.Int8:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 8)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int8: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int8(v)))
case reflect.String:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
values = append(values, reflect.ValueOf(s))
case reflect.Float64:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to float64: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(v))
case reflect.Float32:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseFloat(s, 32)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to float32: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(float32(v)))
case reflect.Ptr:
arg := sd.Args[i]
switch param.Elem().String() {
case "messages.PickleDocString":
if v, ok := arg.(*messages.PickleStepArgument); ok {
values = append(values, reflect.ValueOf(v.DocString))
break
}
if v, ok := arg.(*messages.PickleDocString); ok {
values = append(values, reflect.ValueOf(v))
break
}
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleDocString`, i, arg, arg)
case "messages.PickleTable":
if v, ok := arg.(*messages.PickleStepArgument); ok {
values = append(values, reflect.ValueOf(v.DataTable))
break
}
if v, ok := arg.(*messages.PickleTable); ok {
values = append(values, reflect.ValueOf(v))
break
}
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleTable`, i, arg, arg)
default:
return fmt.Errorf("the argument %d type %T is not supported %s", i, arg, param.Elem().String())
}
case reflect.Slice:
switch param {
case typeOfBytes:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
values = append(values, reflect.ValueOf([]byte(s)))
default:
return fmt.Errorf("the slice argument %d type %s is not supported", i, param.Kind())
}
default:
return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind())
}
}
res := sd.HandlerValue.Call(values)
if len(res) == 0 {
return nil
}
return res[0].Interface()
}
func (sd *StepDefinition) shouldBeString(idx int) (string, error) {
arg := sd.Args[idx]
switch arg := arg.(type) {
case string:
return arg, nil
case *messages.PickleStepArgument:
if arg.DocString == nil {
return "", fmt.Errorf(`cannot convert DocString is not set`)
}
return arg.DocString.Content, nil
case *messages.PickleDocString:
return arg.Content, nil
default:
return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg)
}
}
// GetInternalStepDefinition ...
func (sd *StepDefinition) GetInternalStepDefinition() *formatters.StepDefinition {
if sd == nil {
return nil
}
return &sd.StepDefinition
}