func (funcCtx *funcContext) getArgParserAndTyp(sb *schemaBuilder, in []reflect.Type) (*argParser, graphql.Type, []reflect.Type, error) {
	var argParser *argParser
	var argType graphql.Type
	if len(in) > 0 && in[0] != selectionSetType {
		var err error
		if argParser, argType, err = sb.makeStructParser(in[0]); err != nil {
			return nil, nil, in, fmt.Errorf("attempted to parse %s as arguments struct, but failed: %s", in[0].Name(), err.Error())
		}
		in = in[1:]
	}
	return argParser, argType, in, nil
}
getArgParserAndTyp reads a list of input parameters, and, if we have a set
of custom parameters for the field func (at this point any input type other
than the selectionSet is considered the args input), we will return the
argParser for that type and pop that field from the returned input parameters.
package schemabuilder
import (
	"context"
	"fmt"
	"reflect"
	"github.com/samsarahq/thunder/graphql"
)
func (sb *schemaBuilder) buildFunction(typ reflect.Type, m *method) (*graphql.Field, error) {
	funcCtx := &funcContext{typ: typ}
	if typ.Kind() == reflect.Ptr {
		return nil, fmt.Errorf("source-type of buildFunction cannot be a pointer (got: %v)", typ)
	}
	callableFunc, err := funcCtx.getFuncVal(m)
	if err != nil {
		return nil, err
	}
	in := funcCtx.getFuncInputTypes()
	in = funcCtx.consumeContextAndSource(in)
	argParser, argType, in, err := funcCtx.getArgParserAndTyp(sb, in)
	if err != nil {
		return nil, err
	}
	funcCtx.hasArgs = argParser != nil
	in = funcCtx.consumeSelectionSet(in)
	if len(in) != 0 {
		return nil, fmt.Errorf("%s arguments should be [context][, [*]%s][, args][, selectionSet]", funcCtx.funcType, typ)
	}
	err = funcCtx.parseReturnSignature(m)
	if err != nil {
		return nil, err
	}
	retType, err := funcCtx.getReturnType(sb, m)
	if err != nil {
		return nil, err
	}
	args, err := funcCtx.argsTypeMap(argType)
	if err != nil {
		return nil, err
	}
	return &graphql.Field{
		Resolve: func(ctx context.Context, source, funcRawArgs interface{}, selectionSet *graphql.SelectionSet) (interface{}, error) {
			funcInputArgs := funcCtx.prepareResolveArgs(source, funcRawArgs, ctx)
			funcOutputArgs := callableFunc.Call(funcInputArgs)
			return funcCtx.extractResultAndErr(funcOutputArgs, retType)
		},
		Args:           args,
		Type:           retType,
		ParseArguments: argParser.Parse,
		Expensive:      funcCtx.hasContext,
	}, nil
}
type funcContext struct {
	hasContext      bool
	hasSource       bool
	hasArgs         bool
	hasSelectionSet bool
	hasRet          bool
	hasError        bool
	funcType     reflect.Type
	isPtrFunc    bool
	typ          reflect.Type
	selectionSet *graphql.SelectionSet
}
func (funcCtx *funcContext) getFuncVal(m *method) (reflect.Value, error) {
	fun := reflect.ValueOf(m.Fn)
	if fun.Kind() != reflect.Func {
		return fun, fmt.Errorf("fun must be func, not %s", fun)
	}
	funcCtx.funcType = fun.Type()
	return fun, nil
}
func (funcCtx *funcContext) getFuncInputTypes() []reflect.Type {
	in := make([]reflect.Type, 0, funcCtx.funcType.NumIn())
	for i := 0; i < funcCtx.funcType.NumIn(); i++ {
		in = append(in, funcCtx.funcType.In(i))
	}
	return in
}
func (funcCtx *funcContext) consumeContextAndSource(in []reflect.Type) []reflect.Type {
	ptr := reflect.PtrTo(funcCtx.typ)
	if len(in) > 0 && in[0] == contextType {
		funcCtx.hasContext = true
		in = in[1:]
	}
	if len(in) > 0 && (in[0] == funcCtx.typ || in[0] == ptr) {
		funcCtx.hasSource = true
		funcCtx.isPtrFunc = in[0] == ptr
		in = in[1:]
	}
	return in
}