From d976902768ee320e323bf216897872c935c00a2f Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sat, 18 Jan 2020 10:13:44 -0500 Subject: [PATCH] Okay, I think that's how error handling should work --- examples/input/main.go | 64 ++++++++++++++++++++++++++---------------- tea.go | 18 +++++++++++- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/examples/input/main.go b/examples/input/main.go index 69023cc..f9d9806 100644 --- a/examples/input/main.go +++ b/examples/input/main.go @@ -3,6 +3,7 @@ package main // A simple program that counts down from 5 and then exits. import ( + "errors" "fmt" "log" "tea" @@ -10,8 +11,9 @@ import ( "tea/input" ) -type model struct { - input input.Model +type Model struct { + Input input.Model + Error error } type tickMsg struct{} @@ -20,19 +22,21 @@ func main() { tea.UseSysLog("tea") p := tea.NewProgram( - model{ - input: input.DefaultModel(), + Model{ + Input: input.DefaultModel(), + Error: nil, }, update, view, []tea.Sub{ - // Just hand off the subscription to the input component - func(m tea.Model) tea.Msg { - if m, ok := m.(model); ok { - return input.Blink(m.input) + // We just hand off the subscription to the input component, giving + // it the model it expects. + func(model tea.Model) tea.Msg { + m, ok := model.(Model) + if !ok { + return tea.NewErrMsg("could not perform assertion on model") } - // TODO: return error - return nil + return input.Blink(m.Input) }, }, ) @@ -42,9 +46,16 @@ func main() { } } -func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) { +func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) { var cmd tea.Cmd - m, _ := mdl.(model) + m, ok := model.(Model) + if !ok { + // When we encounter errors in Update we simply add the error to the + // model so we can handle it in the view. We could also return a command + // that does something else with the error, like logs it via IO. + m.Error = errors.New("could not perform assertion on model") + return m, nil + } switch msg := msg.(type) { case tea.KeyMsg: @@ -54,22 +65,27 @@ func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) { case "esc": return m, tea.Quit } + + // We handle errors just like any other message + case tea.ErrMsg: + m.Error = msg + return m, nil } - m.input, cmd = input.Update(msg, m.input) + m.Input, cmd = input.Update(msg, m.Input) return m, cmd } -func view(m tea.Model) string { - if m, ok := m.(model); ok { - help := "(esc to exit)" - - return fmt.Sprintf( - "What’s your favorite Pokémon?\n\n%s\n\n%s", - input.View(m.input), - help, - ) +func view(model tea.Model) string { + m, ok := model.(Model) + if !ok { + return "Oh no: could not perform assertion on model." + } else if m.Error != nil { + return fmt.Sprintf("Uh oh: %s", m.Error) } - // TODO: return error - return "" + return fmt.Sprintf( + "What’s your favorite Pokémon?\n\n%s\n\n%s", + input.View(m.Input), + "(esc to quit)", + ) } diff --git a/tea.go b/tea.go index 326c5a7..9ca665e 100644 --- a/tea.go +++ b/tea.go @@ -1,6 +1,7 @@ package tea import ( + "errors" "fmt" "io" "log" @@ -44,6 +45,22 @@ type Program struct { rw io.ReadWriter } +// ErrMsg is just a regular message containing an error. We handle it in Update +// just like a regular message by case switching. Of course, the developer +// could also define her own errors as well. +type ErrMsg struct { + error +} + +func (e ErrMsg) String() string { + return e.Error() +} + +// NewErrMsg is a convenience function for creating a generic ErrMsg +func NewErrMsg(s string) ErrMsg { + return ErrMsg{errors.New(s)} +} + // Quit is a command that tells the program to exit func Quit() Msg { return quitMsg{} @@ -63,7 +80,6 @@ func NewProgram(model Model, update Update, view View, subs []Sub) *Program { } // Start initializes the program -// TODO: error channel func (p *Program) Start() error { var ( model = p.model