Skip to content

Commit

Permalink
app: Add fx.Error option (#625)
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney authored Jun 12, 2018
1 parent 98d2848 commit c639d33
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
12 changes: 12 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ func (io invokeOption) String() string {
return fmt.Sprintf("fx.Invoke(%s)", strings.Join(items, ", "))
}

// Error registers any number of errors with the application to short-circuit
// startup. If more than one error is given, the errors are combined into a
// single error.
//
// Similar to invocations, errors are applied in order. All Provide and Invoke
// options registered before or after an Error option will not be applied.
func Error(errs ...error) Option {
return optionFunc(func(app *App) {
app.err = multierr.Append(app.err, multierr.Combine(errs...))
})
}

// Options converts a collection of Options into a single Option. This allows
// packages to bundle sophisticated functionality into easy-to-use Fx modules.
// For example, a logging package might export a simple option like this:
Expand Down
62 changes: 62 additions & 0 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,68 @@ func TestInvokes(t *testing.T) {
})
}

func TestError(t *testing.T) {
t.Run("NilErrorOption", func(t *testing.T) {
var invoked bool

app := fxtest.New(t,
Error(nil),
Invoke(func() { invoked = true }),
)
err := app.Err()
require.NoError(t, err)
assert.True(t, invoked)
})

t.Run("SingleErrorOption", func(t *testing.T) {
app := fxtest.New(t,
Error(errors.New("module failure")),
Invoke(func() { t.Errorf("Invoke should not be called") }),
)
err := app.Err()
assert.EqualError(t, err, "module failure")
})

t.Run("MultipleErrorOption", func(t *testing.T) {
type A struct{}

app := fxtest.New(t,
Provide(func() A {
t.Errorf("Provide should not be called")
return A{}
},
),
Invoke(func(A) { t.Errorf("Invoke should not be called") }),
Error(
errors.New("module A failure"),
errors.New("module B failure"),
),
)
err := app.Err()
require.Error(t, err)
assert.Contains(t, err.Error(), "module A failure")
assert.Contains(t, err.Error(), "module B failure")
assert.NotContains(t, err.Error(), "not in the container")
})

t.Run("ProvideAndInvokeErrorsAreIgnored", func(t *testing.T) {
type A struct{}
type B struct{}

app := fxtest.New(t,
Provide(func(b B) A {
t.Errorf("B is missing from the container; Provide should not be called")
return A{}
},
),
Error(errors.New("module failure")),
Invoke(func(A) { t.Errorf("A was not provided; Invoke should not be called") }),
)
err := app.Err()
assert.EqualError(t, err, "module failure")
})
}

func TestOptions(t *testing.T) {
t.Run("OptionsComposition", func(t *testing.T) {
var n int
Expand Down
55 changes: 55 additions & 0 deletions error_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fx_test

import (
"errors"
"fmt"
"net/http"
"os"

"go.uber.org/fx"
)

func ExampleError() {
// A module that provides a HTTP server depends on
// the $PORT environment variable. If the variable
// is unset, the module returns an fx.Error option.
newHTTPServer := func() fx.Option {
port := os.Getenv("PORT")
if port == "" {
return fx.Error(errors.New("$PORT is not set"))
}
return fx.Provide(&http.Server{
Addr: fmt.Sprintf(":%s", port),
})
}

app := fx.New(
newHTTPServer(),
fx.Invoke(func(s *http.Server) error { return s.ListenAndServe() }),
)

fmt.Println(app.Err())

// Output:
// $PORT is not set
}

0 comments on commit c639d33

Please sign in to comment.