Skip to content

Commit

Permalink
Supply storage as functional option
Browse files Browse the repository at this point in the history
  • Loading branch information
oklahomer committed Apr 9, 2017
1 parent d32cd3b commit 505977d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 11 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ var Task = sarah.NewScheduledTaskBuilder().
return []*sarah.ScheduledTaskResult{
{
Content: "Howdy!!",
Destination: &rtmapi.Channel{Name: "XXXX"},
Destination: rtmapi.ChannelID("XXX"),
},
}, nil
}).
Expand Down Expand Up @@ -171,7 +171,8 @@ func main() {
configBuf, _ := ioutil.ReadFile("/path/to/adapter/config.yaml")
slackConfig := slack.NewConfig()
yaml.Unmarshal(configBuf, slackConfig)
slackBot := sarah.NewBot(slack.NewAdapter(slackConfig), sarah.NewCacheConfig())
storage := sarah.NewUserContextStorage(sarah.NewCacheConfig())
slackBot := sarah.NewBot(slack.NewAdapter(slackConfig), storage)

// Register desired command(s)
slackBot.AppendCommand(hello.SlackCommand)
Expand Down
48 changes: 42 additions & 6 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,45 @@ type defaultBot struct {
// - call Adapter.SendMessage to send output
// The aim of defaultBot is to lessen the tasks of Adapter developer by providing some common tasks' implementations, and achieve easier creation of Bot implementation.
// Hence this method returns Bot interface instead of any concrete instance so this can be ONLY treated as Bot implementation to be fed to Runner.RegisterBot.
func NewBot(adapter Adapter, cacheConfig *CacheConfig) Bot {
return &defaultBot{
//
// Some optional settings can be supplied by passing sarah.WithStorage and others that return DefaultBotOption.
//
// // Use pre-defined storage.
// storage := sarah.NewUserContextStorage(sarah.NewCacheConfig())
// bot, err := sarah.NewBot(myAdapter, sarah.WithStorage(sarah.NewUserContextStorage(sarah.NewCacheConfig())))
func NewBot(adapter Adapter, options ...DefaultBotOption) (Bot, error) {
bot := &defaultBot{
botType: adapter.BotType(),
runFunc: adapter.Run,
sendMessageFunc: adapter.SendMessage,
commands: NewCommands(),
userContextStorage: NewUserContextStorage(cacheConfig),
userContextStorage: nil,
}

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

return bot, nil
}

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

// BotWithStorage creates and returns DefaultBotOption to set preferred UserContextStorage implementation.
// Below example utilizes pre-defined in-memory storage.
//
// config := sarah.NewCacheConfig()
// configBuf, _ := ioutil.ReadFile("/path/to/storage/config.yaml")
// yaml.Unmarshal(configBuf, config)
// bot, err := sarah.NewBot(myAdapter, storage)
func BotWithStorage(storage UserContextStorage) DefaultBotOption {
return func(bot *defaultBot) error {
bot.userContextStorage = storage
return nil
}
}

Expand All @@ -69,9 +101,13 @@ func (bot *defaultBot) Respond(ctx context.Context, input Input) error {
senderKey := input.SenderKey()

// See if any conversational context is stored.
nextFunc, storageErr := bot.userContextStorage.Get(senderKey)
if storageErr != nil {
return storageErr
var nextFunc ContextualFunc
if bot.userContextStorage != nil {
var storageErr error
nextFunc, storageErr = bot.userContextStorage.Get(senderKey)
if storageErr != nil {
return storageErr
}
}

var res *CommandResponse
Expand Down
53 changes: 50 additions & 3 deletions bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,58 @@ func (bot *DummyBot) Run(ctx context.Context, enqueueInput func(Input) error, no
bot.RunFunc(ctx, enqueueInput, notifyErr)
}

func Test_NewBot(t *testing.T) {
func TestNewBot_WithoutFunctionalOption(t *testing.T) {
adapter := &DummyAdapter{}
myBot := NewBot(adapter, NewCacheConfig())
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)
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
},
)

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

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

if myBot != nil {
t.Fatalf("Bot should not be returned: %#v.", myBot)
}
}

func TestBotWithStorage(t *testing.T) {
storage := &DummyUserContextStorage{}
option := BotWithStorage(storage)

bot := &defaultBot{}
option(bot)

if bot.userContextStorage == nil {
t.Fatal("UserContextStorage is not set")
}

if bot.userContextStorage != storage {
t.Fatalf("Expected UserContextStorage implementation is not set: %#v", bot.userContextStorage)
}
}

Expand Down

0 comments on commit 505977d

Please sign in to comment.