Skip to content

Commit

Permalink
Remove return value from functional option
Browse files Browse the repository at this point in the history
  • Loading branch information
oklahomer committed Jun 20, 2019
1 parent 97e10fa commit 6e4185e
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 225 deletions.
10 changes: 3 additions & 7 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,14 @@ func NewBot(adapter Adapter, options ...DefaultBotOption) (Bot, error) {
}

for _, opt := range options {
err := opt(bot)
if err != nil {
return nil, err
}
opt(bot)
}

return bot, nil
}

// DefaultBotOption defines function that defaultBot's functional option must satisfy.
type DefaultBotOption func(bot *defaultBot) error
type DefaultBotOption func(bot *defaultBot)

// BotWithStorage creates and returns DefaultBotOption to set preferred UserContextStorage implementation.
// Below example utilizes pre-defined in-memory storage.
Expand All @@ -99,9 +96,8 @@ type DefaultBotOption func(bot *defaultBot) error
// yaml.Unmarshal(configBuf, config)
// bot, err := sarah.NewBot(myAdapter, storage)
func BotWithStorage(storage UserContextStorage) DefaultBotOption {
return func(bot *defaultBot) error {
return func(bot *defaultBot) {
bot.userContextStorage = storage
return nil
}
}

Expand Down
37 changes: 5 additions & 32 deletions bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,17 @@ func (bot *DummyBot) Run(ctx context.Context, enqueueInput func(Input) error, no

func TestNewBot_WithoutFunctionalOption(t *testing.T) {
adapter := &DummyAdapter{}
myBot, err := NewBot(adapter)

if err != nil {
t.Fatalf("Unexpected error is returned: %#v.", err)
}

if _, ok := myBot.(*defaultBot); !ok {
t.Errorf("NewBot did not return bot instance: %#v.", myBot)
}
}

func TestNewBot_WithFunctionalOption(t *testing.T) {
adapter := &DummyAdapter{}
expectedErr := errors.New("this is expected")
myBot, err := NewBot(
adapter,
func(bot *defaultBot) error {
return nil
},
func(bot *defaultBot) error {
return expectedErr
},
func(_ *defaultBot) {},
)

if err == nil {
t.Fatal("Expected error is not returned")
}

if err != expectedErr {
if err != nil {
t.Fatalf("Unexpected error is returned: %#v.", err)
}

if myBot != nil {
t.Fatalf("Bot should not be returned: %#v.", myBot)
if _, ok := myBot.(*defaultBot); !ok {
t.Errorf("NewBot did not return bot instance: %#v.", myBot)
}
}

Expand All @@ -80,11 +57,7 @@ func TestBotWithStorage(t *testing.T) {
option := BotWithStorage(storage)

bot := &defaultBot{}
err := option(bot)

if err != nil {
t.Fatalf("Unexpected error is returned: %s", err.Error())
}
option(bot)

if bot.userContextStorage == nil {
t.Fatal("UserContextStorage is not set")
Expand Down
7 changes: 2 additions & 5 deletions gitter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
)

// AdapterOption defines function signature that Adapter's functional option must satisfy.
type AdapterOption func(adapter *Adapter) error
type AdapterOption func(adapter *Adapter)

// Adapter stores REST/Streaming API clients' instances to let users interact with gitter.
type Adapter struct {
Expand All @@ -32,10 +32,7 @@ func NewAdapter(config *Config, options ...AdapterOption) (*Adapter, error) {
}

for _, opt := range options {
err := opt(adapter)
if err != nil {
return nil, xerrors.Errorf("failed to apply options: %w", err)
}
opt(adapter)
}

return adapter, nil
Expand Down
18 changes: 1 addition & 17 deletions gitter/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/oklahomer/go-sarah"
"github.com/oklahomer/go-sarah/log"
"github.com/oklahomer/go-sarah/retry"
"golang.org/x/xerrors"
"io/ioutil"
stdLogger "log"
"os"
Expand Down Expand Up @@ -64,7 +63,7 @@ func (c *DummyConnection) Close() error {

func TestNewAdapter(t *testing.T) {
config := NewConfig()
adapter, err := NewAdapter(config)
adapter, err := NewAdapter(config, func(_ *Adapter) {})
if err != nil {
t.Fatalf("Unexpected error returned: %s.", err.Error())
}
Expand All @@ -74,21 +73,6 @@ func TestNewAdapter(t *testing.T) {
}
}

func TestNewAdapter_WithOptionError(t *testing.T) {
expectedErr := errors.New("dummy")
adapter, err := NewAdapter(NewConfig(), func(_ *Adapter) error {
return expectedErr
})

if !xerrors.Is(err, expectedErr) {
t.Errorf("Expected error is not returned: %#v.", err)
}

if adapter != nil {
t.Error("Adapter instance should not be returned on error.")
}
}

func TestAdapter_BotType(t *testing.T) {
adapter := &Adapter{}

Expand Down
45 changes: 14 additions & 31 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,115 +32,102 @@ func NewConfig() *Config {
// Calls to its methods are thread-safe.
type optionHolder struct {
mutex sync.RWMutex
stashed []func(*runner) error
stashed []func(*runner)
}

func (o *optionHolder) register(opt func(*runner) error) {
func (o *optionHolder) register(opt func(*runner)) {
o.mutex.Lock()
defer o.mutex.Unlock()

o.stashed = append(o.stashed, opt)
}

func (o *optionHolder) apply(r *runner) error {
func (o *optionHolder) apply(r *runner) {
o.mutex.Lock()
defer o.mutex.Unlock()

for _, v := range o.stashed {
e := v(r)
if e != nil {
return e
}
v(r)
}

return nil
}

// RegisterAlerter registers given sarah.Alerter implementation.
// When registered sarah.Bot implementation encounters critical state, given alerter is called to notify such state.
func RegisterAlerter(alerter Alerter) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
r.alerters.appendAlerter(alerter)
return nil
})
}

// RegisterBot registers given sarah.Bot implementation to be run on sarah.Run().
// This may be called multiple times to register as many bot instances as wanted.
// When a Bot with same sarah.BotType is already registered, this returns error on sarah.Run().
func RegisterBot(bot Bot) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
r.bots = append(r.bots, bot)
return nil
})
}

// RegisterCommand registers given sarah.Command.
// On sarah.Run(), Commands are registered to corresponding bot via Bot.AppendCommand().
func RegisterCommand(botType BotType, command Command) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
commands, ok := r.commands[botType]
if !ok {
commands = []Command{}
}
r.commands[botType] = append(commands, command)
return nil
})
}

// RegisterCommandProps registers given sarah.CommandProps to build sarah.Command on sarah.Run().
// This props is re-used when configuration file is updated and a corresponding sarah.Command needs to be re-built.
func RegisterCommandProps(props *CommandProps) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
stashed, ok := r.commandProps[props.botType]
if !ok {
stashed = []*CommandProps{}
}
r.commandProps[props.botType] = append(stashed, props)
return nil
})
}

// RegisterScheduledTask registers given sarah.ScheduledTask.
// On sarah.Run(), schedule is set for this task.
func RegisterScheduledTask(botType BotType, task ScheduledTask) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
tasks, ok := r.scheduledTasks[botType]
if !ok {
tasks = []ScheduledTask{}
}
r.scheduledTasks[botType] = append(tasks, task)
return nil
})
}

// RegisterScheduledTaskProps registers given sarah.ScheduledTaskProps to build sarah.ScheduledTask on sarah.Run().
// This props is re-used when configuration file is updated and a corresponding sarah.ScheduledTask needs to be re-built.
func RegisterScheduledTaskProps(props *ScheduledTaskProps) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
stashed, ok := r.scheduledTaskProps[props.botType]
if !ok {
stashed = []*ScheduledTaskProps{}
}
r.scheduledTaskProps[props.botType] = append(stashed, props)
return nil
})
}

// RegisterConfigWatcher registers given ConfigWatcher implementation.
func RegisterConfigWatcher(watcher ConfigWatcher) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
r.configWatcher = watcher
return nil
})
}

// RegisterWorker registers given workers.Worker implementation.
// When this is not called, a worker instance with default setting is used.
func RegisterWorker(worker workers.Worker) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
r.worker = worker
return nil
})
}

Expand All @@ -166,9 +153,8 @@ func RegisterWorker(worker workers.Worker) {
// Each Bot/Adapter's implementation can be kept simple in this way.
// go-sarah's core should always supervise and control its belonging Bots.
func RegisterBotErrorSupervisor(fnc func(BotType, error) *SupervisionDirective) {
options.register(func(r *runner) error {
options.register(func(r *runner) {
r.superviseError = fnc
return nil
})
}

Expand Down Expand Up @@ -217,10 +203,7 @@ func newRunner(ctx context.Context, config *Config) (*runner, error) {
superviseError: nil,
}

err = options.apply(r)
if err != nil {
return nil, xerrors.Errorf("failed to apply option: %w", err)
}
options.apply(r)

if r.worker == nil {
w, e := workers.Run(ctx, workers.NewConfig())
Expand Down

0 comments on commit 6e4185e

Please sign in to comment.