func FromFunction()

in wstl1/mapping_engine/projector/projector.go [61:158]


func FromFunction(fn interface{}, name string) (types.Projector, error) {
	tokenType := reflect.TypeOf((*jsonutil.JSONToken)(nil)).Elem()

	f := reflect.ValueOf(fn)
	if f.Kind() != reflect.Func {
		return nil, fmt.Errorf("projector must be a function")
	}

	// Check args are our JSON types.
	ft := reflect.TypeOf(fn)
	for i := 0; i < ft.NumIn(); i++ {
		isObj := ft.In(i).AssignableTo(tokenType)
		isSliceOfObj := ft.In(i).Kind() == reflect.Slice && ft.In(i).Elem().AssignableTo(tokenType)
		if !isObj && !isSliceOfObj {
			return nil, fmt.Errorf("parameter %d is of type %v which is not supported", i, ft.In(i))
		}
	}

	// Check return type is what we expect.
	if ft.NumOut() != 2 {
		return nil, fmt.Errorf("incorrect return type, expected (jsonutil.JSONToken, error) got %d return values", ft.NumOut())
	}
	if !ft.Out(0).AssignableTo(tokenType) || !ft.Out(1).AssignableTo(reflect.TypeOf((*error)(nil)).Elem()) {
		return nil, fmt.Errorf("incorrect return type, expected (jsonutil.JSONToken, error) got (%v, %v)", ft.Out(0), ft.Out(1))
	}

	// Build wrapper closure.
	return func(metaArgs []jsonutil.JSONMetaNode, pctx *types.Context) (jsonutil.JSONToken, error) {
		errLocation := errors.FnLocationf("Native Function Preamble %q", name)

		if err := pctx.PushProjectorToStack(name); err != nil {
			return nil, errors.Wrap(errLocation, err)
		}

		// Lose the meta.
		args := make([]jsonutil.JSONToken, len(metaArgs))
		for i, metaArg := range metaArgs {
			node, err := jsonutil.NodeToToken(metaArg)
			if err != nil {
				return nil, errors.Wrap(errLocation, fmt.Errorf("error converting args: %v", err))
			}
			args[i] = node
		}

		if ft.IsVariadic() && len(args) < ft.NumIn()-1 {
			return nil, errors.Wrap(errLocation, fmt.Errorf("expected at least %d parameters (could be more, function is variadic), got %d", ft.NumIn()-1, len(args)))
		}
		if !ft.IsVariadic() && len(args) != ft.NumIn() {
			return nil, errors.Wrap(errLocation, fmt.Errorf("expected %d parameters, got %d", ft.NumIn(), len(args)))
		}
		argvs := make([]reflect.Value, 0, len(args))
		for i, arg := range args {
			if ft.IsVariadic() && i == ft.NumIn()-1 {
				a, err := extractVariadic(ft.In(i).Elem(), args[i:])
				if err != nil {
					return nil, errors.Wrap(errLocation, fmt.Errorf("error extracting variadic argument %d: %v", i, err))
				}

				argvs = append(argvs, a...)
				break
			}
			if ft.In(i).Kind() == reflect.Slice {
				a, err := extractSlice(ft.In(i).Elem(), arg)
				if err != nil {
					return nil, errors.Wrap(errLocation, fmt.Errorf("error extracting slice argument %d: %v", i, err))
				}
				argvs = append(argvs, a)
				continue
			}

			a, err := extractSimple(ft.In(i), arg)
			if err != nil {
				return nil, errors.Wrap(errLocation, fmt.Errorf("error extracting argument %d: %v", i, err))
			}
			argvs = append(argvs, a)
		}

		result := f.Call(argvs)

		var r jsonutil.JSONToken
		var err error

		if ri := result[0].Interface(); ri != nil {
			r = ri.(jsonutil.JSONToken)
		}
		if ri := result[1].Interface(); ri != nil {
			err = ri.(error)
		}

		pctx.PopProjectorFromStack(name)

		if err != nil {
			return nil, errors.Wrap(errors.FnLocationf("Native Function %q", name), err)
		}

		return r, nil
	}, nil
}