Repository: uber-go/fx (go)

Issue:
Errors should be wrapped not simple passed as value
**Describe the bug**
The `fx` project use `fmt.Errorf("%v", err)` instead of `fmt.Errorf("%w", err)` like [here](https://github.com/uber-go/fx/blob/v1.18.2/provide.go#L102). This prevent from using `errors.Unwrap()`, `errors.Is()` and `errors.As()` functions in custom logger. Also this hide true errors and make error message unclear.

**To Reproduce**

```go
type Error struct {
    Message string
    Err error
}

func (e *Error) Error() string {
    return e.Message 
}

func (e *Error) Unwrap() error {
    return e.Err
}

fx.New(
    fx.Invoke(
        func() error {
            return &Error{
                 Message: "Error A",
                 Err: &Error{
                     Message: "Error B wrapped in error A",
                 },
            },
        },
    )
).Run()
```

- ~~Error value provided by the fx in logger will contain only `fx bla bla: ble ble: Error A` without containing the message `Error B wrapped in error A`~~
- Error value provided by the fx in logger will contain only `fx bla bla: ble ble: Error A` (that is OK) but cannot get wrapped error with the `errors.Unwrap()` function and the wrapped error message is lost `Error B wrapped in error A` and their corresponding chain of wrapped errors
- It always returns single error value without any wrapped errors. Missing error context
- Using `errors.Unwrap()`, `errors.Is()` or `errors.As()` is worthless

**Expected behavior**

Error provided by the fx should support wrapped errors without loosing error context.

Pull request:
Use wrap errors and update tests
Use wrap error verb (`%w`) in some `fmt.Errorf` calls and update some test to avoid confusion about how to get errors and compared them. 

This resolves #988

Patch:
diff --git a/annotated.go b/annotated.go
index d07ba85..01fae63 100644
--- a/annotated.go
+++ b/annotated.go
@@ -134,6 +134,11 @@ func (e *annotationError) Error() string {
 	return e.err.Error()
 }
 
+// Unwrap the wrapped error.
+func (e *annotationError) Unwrap() error {
+	return e.err
+}
+
 type paramTagsAnnotation struct {
 	tags []string
 }
diff --git a/app.go b/app.go
index a5e2699..d2b8caf 100644
--- a/app.go
+++ b/app.go
@@ -762,7 +762,7 @@ type withTimeoutParams struct {
 }
 
 // errHookCallbackExited is returned when a hook callback does not finish executing
-var errHookCallbackExited = fmt.Errorf("goroutine exited without returning")
+var errHookCallbackExited = errors.New("goroutine exited without returning")
 
 func withTimeout(ctx context.Context, param *withTimeoutParams) error {
 	c := make(chan error, 1)
diff --git a/provide.go b/provide.go
index eb61ce9..999262c 100644
--- a/provide.go
+++ b/provide.go
@@ -131,18 +131,18 @@ func runProvide(c container, p provide, opts ...dig.ProvideOption) error {
 	case annotationError:
 		// fx.Annotate failed. Turn it into an Fx error.
 		return fmt.Errorf(
-			"encountered error while applying annotation using fx.Annotate to %s: %+v",
+			"encountered error while applying annotation using fx.Annotate to %s: %w",
 			fxreflect.FuncName(constructor.target), constructor.err)
 
 	case annotated:
 		ctor, err := constructor.Build()
 		if err != nil {
-			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err)
+			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", constructor, p.Stack, err)
 		}
 
 		opts = append(opts, dig.LocationForPC(constructor.FuncPtr))
 		if err := c.Provide(ctor, opts...); err != nil {
-			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err)
+			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", constructor, p.Stack, err)
 		}
 
 	case Annotated:
@@ -159,7 +159,7 @@ func runProvide(c container, p provide, opts ...dig.ProvideOption) error {
 		}
 
 		if err := c.Provide(ann.Target, opts...); err != nil {
-			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", ann, p.Stack, err)
+			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", ann, p.Stack, err)
 		}
 
 	default:
@@ -180,7 +180,7 @@ func runProvide(c container, p provide, opts ...dig.ProvideOption) error {
 		}
 
 		if err := c.Provide(constructor, opts...); err != nil {
-			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", fxreflect.FuncName(constructor), p.Stack, err)
+			return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", fxreflect.FuncName(constructor), p.Stack, err)
 		}
 	}
 	return nil


Test patch:
diff --git a/app_internal_test.go b/app_internal_test.go
index 82de85e..6e54f86 100644
--- a/app_internal_test.go
+++ b/app_internal_test.go
@@ -21,6 +21,7 @@
 package fx
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"sync"
@@ -105,3 +106,13 @@ func (o withClockOption) apply(m *module) {
 func (o withClockOption) String() string {
 	return fmt.Sprintf("WithClock(%v)", o.clock)
 }
+
+func TestAnnotationError(t *testing.T) {
+	wantErr := errors.New("want error")
+	err := &annotationError{
+		err: wantErr,
+	}
+	require.Error(t, err)
+	assert.ErrorIs(t, err, wantErr)
+	assert.Contains(t, err.Error(), wantErr.Error())
+}
diff --git a/app_test.go b/app_test.go
index 9f70583..d9bc543 100644
--- a/app_test.go
+++ b/app_test.go
@@ -654,6 +654,18 @@ type errHandlerFunc func(error)
 
 func (f errHandlerFunc) HandleError(err error) { f(err) }
 
+type customError struct {
+	err error
+}
+
+func (e *customError) Error() string {
+	return fmt.Sprintf("custom error: %v", e.err)
+}
+
+func (e *customError) Unwrap() error {
+	return e.err
+}
+
 func TestInvokes(t *testing.T) {
 	t.Parallel()
 
@@ -673,16 +685,18 @@ func TestInvokes(t *testing.T) {
 	t.Run("Failure event", func(t *testing.T) {
 		t.Parallel()
 
+		wantErr := errors.New("great sadness")
 		app, spy := NewSpied(
 			Invoke(func() error {
-				return errors.New("great sadness")
+				return wantErr
 			}),
 		)
 		require.Error(t, app.Err())
 
 		invoked := spy.Events().SelectByTypeName("Invoked")
 		require.Len(t, invoked, 1)
-		assert.Error(t, invoked[0].(*fxevent.Invoked).Err)
+		require.Error(t, invoked[0].(*fxevent.Invoked).Err)
+		require.ErrorIs(t, invoked[0].(*fxevent.Invoked).Err, wantErr)
 	})
 
 	t.Run("ErrorsAreNotOverriden", func(t *testing.T) {
@@ -716,6 +730,23 @@ func TestInvokes(t *testing.T) {
 		)
 		assert.Equal(t, 1, count)
 	})
+
+	t.Run("ErrorsAreWrapped", func(t *testing.T) {
+		t.Parallel()
+		wantErr := errors.New("err")
+
+		app := NewForTest(t,
+			Invoke(func() error {
+				return &customError{err: wantErr}
+			}),
+		)
+
+		err := app.Err()
+		require.Error(t, err)
+		assert.ErrorIs(t, err, wantErr)
+		var ce *customError
+		assert.ErrorAs(t, err, &ce)
+	})
 }
 
 func TestError(t *testing.T) {
@@ -738,12 +769,15 @@ func TestError(t *testing.T) {
 	t.Run("SingleErrorOption", func(t *testing.T) {
 		t.Parallel()
 
+		wantErr := errors.New("module failure")
 		app := NewForTest(t,
-			Error(errors.New("module failure")),
+			Error(wantErr),
 			Invoke(func() { t.Errorf("Invoke should not be called") }),
 		)
 		err := app.Err()
+		require.Error(t, err)
 		assert.EqualError(t, err, "module failure")
+		assert.ErrorIs(t, err, wantErr)
 	})
 
 	t.Run("MultipleErrorOption", func(t *testing.T) {
@@ -751,6 +785,8 @@ func TestError(t *testing.T) {
 
 		type A struct{}
 
+		errA := errors.New("module A failure")
+		errB := errors.New("module B failure")
 		app := NewForTest(t,
 			Provide(func() A {
 				t.Errorf("Provide should not be called")
@@ -759,14 +795,14 @@ func TestError(t *testing.T) {
 			),
 			Invoke(func(A) { t.Errorf("Invoke should not be called") }),
 			Error(
-				errors.New("module A failure"),
-				errors.New("module B failure"),
+				errA,
+				errB,
 			),
 		)
 		err := app.Err()
 		require.Error(t, err)
-		assert.Contains(t, err.Error(), "module A failure")
-		assert.Contains(t, err.Error(), "module B failure")
+		assert.ErrorIs(t, err, errA)
+		assert.ErrorIs(t, err, errB)
 		assert.NotContains(t, err.Error(), "not in the container")
 	})
 
@@ -1759,7 +1795,7 @@ func TestHookConstructors(t *testing.T) {
 			}))
 		}))
 		require.NoError(t, app.Start(ctx))
-		require.ErrorContains(t, app.Stop(ctx), wantErr.Error())
+		require.ErrorIs(t, app.Stop(ctx), wantErr)
 	})
 
 	t.Run("stop deadline", func(t *testing.T) {