mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
interact: let function evaluator returns state, inject nil if object is not found
This commit is contained in:
parent
14eea34394
commit
086127e8f7
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/pquerna/otp"
|
"github.com/pquerna/otp"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthMode string
|
type AuthMode string
|
||||||
|
@ -23,25 +24,26 @@ type AuthInteract struct {
|
||||||
OneTimePasswordKey *otp.Key `json:"otpKey,omitempty"`
|
OneTimePasswordKey *otp.Key `json:"otpKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *AuthInteract) Commands(interact *Interact) {
|
func (it *AuthInteract) Commands(interact *Interact) {
|
||||||
interact.Command("/auth", func(reply Reply) error {
|
interact.Command("/auth", func(reply Reply) error {
|
||||||
reply.Message("Enter your authentication code")
|
reply.Message("Enter your authentication code")
|
||||||
return nil
|
return nil
|
||||||
}).NamedNext(StateAuthenticated, func(reply Reply, code string) error {
|
}).NamedNext(StateAuthenticated, func(reply Reply, code string) error {
|
||||||
switch i.Mode {
|
switch it.Mode {
|
||||||
case AuthModeToken:
|
case AuthModeToken:
|
||||||
if code == i.Token {
|
if code == it.Token {
|
||||||
reply.Message("Great! You're authenticated!")
|
reply.Message("Great! You're authenticated!")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
reply.Message("Incorrect authentication code")
|
|
||||||
return ErrAuthenticationFailed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case AuthModeOTP:
|
case AuthModeOTP:
|
||||||
|
if totp.Validate(code, it.OneTimePasswordKey.Secret()) {
|
||||||
|
reply.Message("Great! You're authenticated!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
reply.Message("Incorrect authentication code")
|
||||||
|
return ErrAuthenticationFailed
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,33 +70,33 @@ func New() *Interact {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) SetOriginState(s State) {
|
func (it *Interact) SetOriginState(s State) {
|
||||||
i.originState = s
|
it.originState = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) AddCustomInteraction(custom CustomInteraction) {
|
func (it *Interact) AddCustomInteraction(custom CustomInteraction) {
|
||||||
custom.Commands(i)
|
custom.Commands(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) PrivateCommand(command string, f interface{}) *Command {
|
func (it *Interact) PrivateCommand(command string, f interface{}) *Command {
|
||||||
cmd := NewCommand(command, f)
|
cmd := NewCommand(command, f)
|
||||||
i.privateCommands[command] = cmd
|
it.privateCommands[command] = cmd
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) Command(command string, f interface{}) *Command {
|
func (it *Interact) Command(command string, f interface{}) *Command {
|
||||||
cmd := NewCommand(command, f)
|
cmd := NewCommand(command, f)
|
||||||
i.commands[command] = cmd
|
it.commands[command] = cmd
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) getNextState(currentState State) (nextState State, final bool) {
|
func (it *Interact) getNextState(currentState State) (nextState State, final bool) {
|
||||||
var ok bool
|
var ok bool
|
||||||
final = false
|
final = false
|
||||||
nextState, ok = i.states[currentState]
|
nextState, ok = it.states[currentState]
|
||||||
if ok {
|
if ok {
|
||||||
// check if it's the final state
|
// check if it's the final state
|
||||||
if _, hasTransition := i.statesFunc[nextState]; !hasTransition {
|
if _, hasTransition := it.statesFunc[nextState]; !hasTransition {
|
||||||
final = true
|
final = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,90 +104,90 @@ func (i *Interact) getNextState(currentState State) (nextState State, final bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// state not found, return to the origin state
|
// state not found, return to the origin state
|
||||||
return i.originState, final
|
return it.originState, final
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) setState(s State) {
|
func (it *Interact) setState(s State) {
|
||||||
log.Infof("[interact]: transiting state from %s -> %s", i.currentState, s)
|
log.Infof("[interact]: transiting state from %s -> %s", it.currentState, s)
|
||||||
i.currentState = s
|
it.currentState = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) handleResponse(text string, ctxObjects ...interface{}) error {
|
func (it *Interact) handleResponse(text string, ctxObjects ...interface{}) error {
|
||||||
args := parseCommand(text)
|
args := parseCommand(text)
|
||||||
|
|
||||||
f, ok := i.statesFunc[i.currentState]
|
f, ok := it.statesFunc[it.currentState]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("state function of %s is not defined", i.currentState)
|
return fmt.Errorf("state function of %s is not defined", it.currentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := parseFuncArgsAndCall(f, args, ctxObjects...)
|
_, err := parseFuncArgsAndCall(f, args, ctxObjects...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nextState, end := i.getNextState(i.currentState)
|
nextState, end := it.getNextState(it.currentState)
|
||||||
if end {
|
if end {
|
||||||
i.setState(i.originState)
|
it.setState(it.originState)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
i.setState(nextState)
|
it.setState(nextState)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) getCommand(command string) (*Command, error) {
|
func (it *Interact) getCommand(command string) (*Command, error) {
|
||||||
switch i.currentState {
|
switch it.currentState {
|
||||||
case StateAuthenticated:
|
case StateAuthenticated:
|
||||||
if cmd, ok := i.privateCommands[command]; ok {
|
if cmd, ok := it.privateCommands[command]; ok {
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case StatePublic:
|
case StatePublic:
|
||||||
if _, ok := i.privateCommands[command]; ok {
|
if _, ok := it.privateCommands[command]; ok {
|
||||||
return nil, fmt.Errorf("private command can not be executed in the public mode")
|
return nil, fmt.Errorf("private command can not be executed in the public mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd, ok := i.commands[command]; ok {
|
if cmd, ok := it.commands[command]; ok {
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("command %s not found", command)
|
return nil, fmt.Errorf("command %s not found", command)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) runCommand(command string, args []string, ctxObjects ...interface{}) error {
|
func (it *Interact) runCommand(command string, args []string, ctxObjects ...interface{}) error {
|
||||||
cmd, err := i.getCommand(command)
|
cmd, err := it.getCommand(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.setState(cmd.initState)
|
it.setState(cmd.initState)
|
||||||
if err := parseFuncArgsAndCall(cmd.F, args, ctxObjects...); err != nil {
|
if _, err := parseFuncArgsAndCall(cmd.F, args, ctxObjects...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we can successfully execute the command, then we can go to the next state.
|
// if we can successfully execute the command, then we can go to the next state.
|
||||||
nextState, end := i.getNextState(i.currentState)
|
nextState, end := it.getNextState(it.currentState)
|
||||||
if end {
|
if end {
|
||||||
i.setState(i.originState)
|
it.setState(it.originState)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
i.setState(nextState)
|
it.setState(nextState)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) SetMessenger(messenger Messenger) {
|
func (it *Interact) SetMessenger(messenger Messenger) {
|
||||||
messenger.SetTextMessageResponder(func(reply Reply, response string) error {
|
messenger.SetTextMessageResponder(func(reply Reply, response string) error {
|
||||||
return i.handleResponse(response, reply)
|
return it.handleResponse(response, reply)
|
||||||
})
|
})
|
||||||
i.messenger = messenger
|
it.messenger = messenger
|
||||||
}
|
}
|
||||||
|
|
||||||
// builtin initializes the built-in commands
|
// builtin initializes the built-in commands
|
||||||
func (i *Interact) builtin() error {
|
func (it *Interact) builtin() error {
|
||||||
i.Command("/uptime", func(reply Reply) error {
|
it.Command("/uptime", func(reply Reply) error {
|
||||||
reply.Message("uptime")
|
reply.Message("uptime")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -195,45 +195,45 @@ func (i *Interact) builtin() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) init() error {
|
func (it *Interact) init() error {
|
||||||
if err := i.builtin(); err != nil {
|
if err := it.builtin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, cmd := range i.commands {
|
for n, cmd := range it.commands {
|
||||||
for s1, s2 := range cmd.states {
|
for s1, s2 := range cmd.states {
|
||||||
if _, exist := i.states[s1]; exist {
|
if _, exist := it.states[s1]; exist {
|
||||||
return fmt.Errorf("state %s already exists", s1)
|
return fmt.Errorf("state %s already exists", s1)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.states[s1] = s2
|
it.states[s1] = s2
|
||||||
}
|
}
|
||||||
for s, f := range cmd.statesFunc {
|
for s, f := range cmd.statesFunc {
|
||||||
i.statesFunc[s] = f
|
it.statesFunc[s] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// register commands to the service
|
// register commands to the service
|
||||||
if i.messenger == nil {
|
if it.messenger == nil {
|
||||||
return fmt.Errorf("messenger is not set")
|
return fmt.Errorf("messenger is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
commandName := n
|
commandName := n
|
||||||
i.messenger.AddCommand(commandName, func(reply Reply, response string) error {
|
it.messenger.AddCommand(commandName, func(reply Reply, response string) error {
|
||||||
args := parseCommand(response)
|
args := parseCommand(response)
|
||||||
return i.runCommand(commandName, args, reply)
|
return it.runCommand(commandName, args, reply)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interact) Start(ctx context.Context) error {
|
func (it *Interact) Start(ctx context.Context) error {
|
||||||
if err := i.init(); err != nil {
|
if err := it.init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use go routine and context
|
// TODO: use go routine and context
|
||||||
i.messenger.Start()
|
it.messenger.Start()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ func parseCommand(src string) (args []string) {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{}) error {
|
func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{}) (State, error) {
|
||||||
fv := reflect.ValueOf(f)
|
fv := reflect.ValueOf(f)
|
||||||
ft := reflect.TypeOf(f)
|
ft := reflect.TypeOf(f)
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
if objectIndex >= len(objects) {
|
if objectIndex >= len(objects) {
|
||||||
return fmt.Errorf("found interface type %s, but object args are empty", at)
|
return "", fmt.Errorf("found interface type %s, but object args are empty", at)
|
||||||
}
|
}
|
||||||
|
|
||||||
for oi := objectIndex; oi < len(objects); oi++ {
|
for oi := objectIndex; oi < len(objects); oi++ {
|
||||||
|
@ -290,8 +290,10 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("can not find object implements %s", at)
|
v := reflect.Zero(at)
|
||||||
|
rArgs = append(rArgs, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
@ -302,7 +304,7 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
bv, err := strconv.ParseBool(args[argIndex])
|
bv, err := strconv.ParseBool(args[argIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
av := reflect.ValueOf(bv)
|
av := reflect.ValueOf(bv)
|
||||||
rArgs = append(rArgs, av)
|
rArgs = append(rArgs, av)
|
||||||
|
@ -311,7 +313,7 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
nf, err := strconv.ParseInt(args[argIndex], 10, 64)
|
nf, err := strconv.ParseInt(args[argIndex], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
av := reflect.ValueOf(nf)
|
av := reflect.ValueOf(nf)
|
||||||
|
@ -321,7 +323,7 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
nf, err := strconv.ParseFloat(args[argIndex], 64)
|
nf, err := strconv.ParseFloat(args[argIndex], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
av := reflect.ValueOf(nf)
|
av := reflect.ValueOf(nf)
|
||||||
|
@ -332,22 +334,29 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
|
|
||||||
out := fv.Call(rArgs)
|
out := fv.Call(rArgs)
|
||||||
if ft.NumOut() == 0 {
|
if ft.NumOut() == 0 {
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to get the error object from the return value
|
// try to get the error object from the return value
|
||||||
|
var state State
|
||||||
|
var err error
|
||||||
for i := 0; i < ft.NumOut(); i++ {
|
for i := 0; i < ft.NumOut(); i++ {
|
||||||
outType := ft.Out(i)
|
outType := ft.Out(i)
|
||||||
switch outType.Kind() {
|
switch outType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if outType.Name() == "State" {
|
||||||
|
state = State(out[i].String())
|
||||||
|
}
|
||||||
|
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
o := out[0].Interface()
|
o := out[i].Interface()
|
||||||
switch ov := o.(type) {
|
switch ov := o.(type) {
|
||||||
case error:
|
case error:
|
||||||
return ov
|
err = ov
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return state, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func Test_parseFuncArgsAndCall_NoErrorFunction(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := parseFuncArgsAndCall(noErrorFunc, []string{"BTCUSDT", "0.123", "true"})
|
_, err := parseFuncArgsAndCall(noErrorFunc, []string{"BTCUSDT", "0.123", "true"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func Test_parseFuncArgsAndCall_ErrorFunction(t *testing.T) {
|
||||||
return errors.New("error")
|
return errors.New("error")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := parseFuncArgsAndCall(errorFunc, []string{"BTCUSDT", "0.123"})
|
_, err := parseFuncArgsAndCall(errorFunc, []string{"BTCUSDT", "0.123"})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func Test_parseFuncArgsAndCall_InterfaceInjection(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
err := parseFuncArgsAndCall(f, []string{"BTCUSDT", "0.123"}, buf)
|
_, err := parseFuncArgsAndCall(f, []string{"BTCUSDT", "0.123"}, buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "123", buf.String())
|
assert.Equal(t, "123", buf.String())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user