~emersion/soju

b920facdff276092505f6eb69c2875f8af7ea022 — delthas 1 year, 9 months ago f05bd84
service: Return the error rather than printing it

This enables callers to make the difference between a successful
service call and a failed one.
3 files changed, 36 insertions(+), 35 deletions(-)

M downstream.go
M service.go
M user.go
M downstream.go => downstream.go +4 -2
@@ 2428,7 2428,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
					})
				}
				if msg.Command == "PRIVMSG" {
					handleServicePRIVMSG(&serviceContext{
					if err := handleServicePRIVMSG(&serviceContext{
						Context: ctx,
						nick:    dc.nick,
						network: dc.network,


@@ 2438,7 2438,9 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
						print: func(text string) {
							sendServicePRIVMSG(dc, text)
						},
					}, text)
					}, text); err != nil {
						sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err))
					}
				}
				continue
			}

M service.go => service.go +26 -31
@@ 125,28 125,24 @@ func splitWords(s string) ([]string, error) {
	return words, nil
}

func handleServicePRIVMSG(ctx *serviceContext, text string) {
func handleServicePRIVMSG(ctx *serviceContext, text string) error {
	words, err := splitWords(text)
	if err != nil {
		ctx.print(fmt.Sprintf(`error: failed to parse command: %v`, err))
		return
		return fmt.Errorf(`failed to parse command: %v`, err)
	}
	handleServiceCommand(ctx, words)
	return handleServiceCommand(ctx, words)
}

func handleServiceCommand(ctx *serviceContext, words []string) {
func handleServiceCommand(ctx *serviceContext, words []string) error {
	cmd, params, err := serviceCommands.Get(words)
	if err != nil {
		ctx.print(fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
		return
		return fmt.Errorf(`%v (type "help" for a list of commands)`, err)
	}
	if cmd.admin && !ctx.admin {
		ctx.print("error: you must be an admin to use this command")
		return
		return fmt.Errorf("you must be an admin to use this command")
	}
	if !cmd.global && ctx.user == nil {
		ctx.print("error: this command must be run as a user (try running with user run)")
		return
		return fmt.Errorf("this command must be run as a user (try running with user run)")
	}

	if cmd.handle == nil {


@@ 154,24 150,21 @@ func handleServiceCommand(ctx *serviceContext, words []string) {
			var l []string
			appendServiceCommandSetHelp(cmd.children, words, ctx.admin, ctx.user == nil, &l)
			ctx.print("available commands: " + strings.Join(l, ", "))
			return nil
		}
		// Pretend the command does not exist if it has neither children nor handler.
		// This is obviously a bug but it is better to not die anyway.
		var logger Logger
		if ctx.user != nil {
			logger = ctx.user.logger
		} else {
			// Pretend the command does not exist if it has neither children nor handler.
			// This is obviously a bug but it is better to not die anyway.
			var logger Logger
			if ctx.user != nil {
				logger = ctx.user.logger
			} else {
				logger = ctx.srv.Logger
			}
			logger.Printf("command without handler and subcommands invoked:", words[0])
			ctx.print(fmt.Sprintf("command %q not found", words[0]))
			logger = ctx.srv.Logger
		}
		return
		logger.Printf("command without handler and subcommands invoked:", words[0])
		return fmt.Errorf("command %q not found", words[0])
	}

	if err := cmd.handle(ctx, params); err != nil {
		ctx.print(fmt.Sprintf("error: %v", err))
	}
	return cmd.handle(ctx, params)
}

func (cmds serviceCommandSet) Get(params []string) (*serviceCommand, []string, error) {


@@ 1169,8 1162,7 @@ func handleUserRun(ctx *serviceContext, params []string) error {
	username := params[0]
	params = params[1:]
	if ctx.user != nil && username == ctx.user.Username {
		handleServiceCommand(ctx, params)
		return nil
		return handleServiceCommand(ctx, params)
	}

	u := ctx.srv.getUser(username)


@@ 1179,9 1171,11 @@ func handleUserRun(ctx *serviceContext, params []string) error {
	}

	printCh := make(chan string, 1)
	retCh := make(chan error, 1)
	ev := eventUserRun{
		params: params,
		print:  printCh,
		ret:    retCh,
	}
	select {
	case <-ctx.Done():


@@ 1199,12 1193,13 @@ func handleUserRun(ctx *serviceContext, params []string) error {
			// in case the event is never processed.
			// TODO: Properly fix this condition by flushing the u.events queue
			//       and running close(ev.print) in a defer
			return nil
			return fmt.Errorf("timeout executing command")
		case text, ok := <-printCh:
			if !ok {
				return nil
			if ok {
				ctx.print(text)
			}
			ctx.print(text)
		case ret := <-retCh:
			return ret
		}
	}
}

M user.go => user.go +6 -2
@@ 87,6 87,7 @@ type eventTryRegainNick struct {
type eventUserRun struct {
	params []string
	print  chan string
	ret    chan error
}

type deliveredClientMap map[string]string // client name -> msg ID


@@ 808,7 809,7 @@ func (u *user) run() {
			e.uc.tryRegainNick(e.nick)
		case eventUserRun:
			ctx := context.TODO()
			handleServiceCommand(&serviceContext{
			err := handleServiceCommand(&serviceContext{
				Context: ctx,
				user:    u,
				srv:     u.srv,


@@ 823,7 824,10 @@ func (u *user) run() {
					}
				},
			}, e.params)
			close(e.print)
			select {
			case <-ctx.Done():
			case e.ret <- err:
			}
		case eventStop:
			for _, dc := range u.downstreamConns {
				dc.Close()