Repository: knative/pkg
Pull request #2618 (2022-10-26 22:41:35)

Add FieldError#WrappedErrors to allow dependent code to have access (readonly)
# Changes

- :gift: Add FieldError#GetErrors to allow dependent code to have access (readonly)

/kind enhancement

<!-- Please include the 'why' behind your changes if no issue exists -->

**Release Note**

```release-note
NONE
```

**Docs**
```docs
NONE
```


linux-foundation-easycla[bot]:  <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/25767896/142331174/2618/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-not-signed.svg" alt="CLA Not Signed" align="left" height="28" width="328"></a><br/><br /><ul><li><a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/25767896/142331174/2618/#/?version=2' target='_blank'>:x:</a> - login: @tcnghia / name: Nghia Tran . The commit (d52594856822be4db9bca21219679a6d3e7fd1d9) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/25767896/142331174/2618/#/?version=2' target='_blank'>Please click here to be authorized</a>. For further assistance with EasyCLA, <a href='https://jira.linuxfoundation.org/servicedesk/customer/portal/4' target='_blank'>please submit a support request ticket</a>.</li></ul>
knative-prow[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2618#" title="Author self-approved">tcnghia</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[apis/OWNERS](https://github.com/knative/pkg/blob/main/apis/OWNERS)~~ [tcnghia]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2618?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
Base: **81.37**% // Head: **81.35**% // Decreases project coverage by **`-0.01%`** :warning:
> Coverage data is based on head [(`d525948`)](https://codecov.io/gh/knative/pkg/pull/2618?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) compared to base [(`6ce976c`)](https://codecov.io/gh/knative/pkg/commit/6ce976ce9255359fab375aacf31e6fe628722c57?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2618      +/-   ##
==========================================
- Coverage   81.37%   81.35%   -0.02%     
==========================================
  Files         162      162              
  Lines        9831     9838       +7     
==========================================
+ Hits         8000     8004       +4     
- Misses       1591     1593       +2     
- Partials      240      241       +1     
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2618?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [apis/field\_error.go](https://codecov.io/gh/knative/pkg/pull/2618/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-YXBpcy9maWVsZF9lcnJvci5nbw==) | `95.70% <100.00%> (+0.12%)` | :arrow_up: |
| [controller/controller.go](https://codecov.io/gh/knative/pkg/pull/2618/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29udHJvbGxlci9jb250cm9sbGVyLmdv) | `88.06% <0.00%> (-0.72%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/knative/pkg/pull/2618?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).

knative-prow[bot]: @tcnghia: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
unit-tests_pkg_main | c4fe8d5f68b6ad290e4e7fb602c55d041dc490cc | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2618/unit-tests_pkg_main/1585480121299505152) | true | `/test unit-tests`

[Your PR dashboard](https://prow.knative.dev/pr).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow[bot]: @tcnghia: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
unit-tests_pkg_main | c4fe8d5f68b6ad290e4e7fb602c55d041dc490cc | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2618/unit-tests_pkg_main/1585480121299505152) | true | `/test unit-tests`
build-tests_pkg_main | c4fe8d5f68b6ad290e4e7fb602c55d041dc490cc | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2618/build-tests_pkg_main/1585480121244979200) | true | `/test build-tests`

[Your PR dashboard](https://prow.knative.dev/pr).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2618#pullrequestreview-1158400773" title="Approved">mattmoor</a>*, *<a href="https://github.com/knative/pkg/pull/2618#" title="Author self-approved">tcnghia</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[apis/OWNERS](https://github.com/knative/pkg/blob/main/apis/OWNERS)~~ [mattmoor,tcnghia]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/apis/field_error.go b/apis/field_error.go
index ddd4838af..cacea2f3c 100644
--- a/apis/field_error.go
+++ b/apis/field_error.go
@@ -83,11 +83,12 @@ var _ error = (*FieldError)(nil)
 
 // ViaField is used to propagate a validation error along a field access.
 // For example, if a type recursively validates its "spec" via:
-//   if err := foo.Spec.Validate(); err != nil {
-//     // Augment any field paths with the context that they were accessed
-//     // via "spec".
-//     return err.ViaField("spec")
-//   }
+//
+//	if err := foo.Spec.Validate(); err != nil {
+//	  // Augment any field paths with the context that they were accessed
+//	  // via "spec".
+//	  return err.ViaField("spec")
+//	}
 func (fe *FieldError) ViaField(prefix ...string) *FieldError {
 	if fe == nil {
 		return nil
@@ -114,11 +115,12 @@ func (fe *FieldError) ViaField(prefix ...string) *FieldError {
 
 // ViaIndex is used to attach an index to the next ViaField provided.
 // For example, if a type recursively validates a parameter that has a collection:
-//  for i, c := range spec.Collection {
-//    if err := doValidation(c); err != nil {
-//      return err.ViaIndex(i).ViaField("collection")
-//    }
-//  }
+//
+//	for i, c := range spec.Collection {
+//	  if err := doValidation(c); err != nil {
+//	    return err.ViaIndex(i).ViaField("collection")
+//	  }
+//	}
 func (fe *FieldError) ViaIndex(index int) *FieldError {
 	return fe.ViaField(asIndex(index))
 }
@@ -130,11 +132,12 @@ func (fe *FieldError) ViaFieldIndex(field string, index int) *FieldError {
 
 // ViaKey is used to attach a key to the next ViaField provided.
 // For example, if a type recursively validates a parameter that has a collection:
-//  for k, v := range spec.Bag {
-//    if err := doValidation(v); err != nil {
-//      return err.ViaKey(k).ViaField("bag")
-//    }
-//  }
+//
+//	for k, v := range spec.Bag {
+//	  if err := doValidation(v); err != nil {
+//	    return err.ViaKey(k).ViaField("bag")
+//	  }
+//	}
 func (fe *FieldError) ViaKey(key string) *FieldError {
 	return fe.ViaField(asKey(key))
 }
@@ -145,7 +148,8 @@ func (fe *FieldError) ViaFieldKey(field, key string) *FieldError {
 }
 
 // At is a way to alter the level of the diagnostics held in this FieldError.
-//    ErrMissingField("foo").At(WarningLevel)
+//
+//	ErrMissingField("foo").At(WarningLevel)
 func (fe *FieldError) At(l DiagnosticLevel) *FieldError {
 	if fe == nil {
 		return nil
@@ -166,9 +170,10 @@ func (fe *FieldError) At(l DiagnosticLevel) *FieldError {
 }
 
 // Filter is a way to access the set of diagnostics having a particular level.
-//    if err := x.Validate(ctx).Filter(ErrorLevel); err != nil {
-//       return err
-//    }
+//
+//	if err := x.Validate(ctx).Filter(ErrorLevel); err != nil {
+//	   return err
+//	}
 func (fe *FieldError) Filter(l DiagnosticLevel) *FieldError {
 	if fe == nil {
 		return nil
@@ -251,10 +256,15 @@ func (fe *FieldError) normalized() []*FieldError {
 	return errors
 }
 
+// WrappedErrors returns the value of the errors after normalizing and deduping using merge().
+func (fe *FieldError) WrappedErrors() []*FieldError {
+	return merge(fe.normalized())
+}
+
 // Error implements error
 func (fe *FieldError) Error() string {
 	// Get the list of errors as a flat merged list.
-	normedErrors := merge(fe.normalized())
+	normedErrors := fe.WrappedErrors()
 	errs := make([]string, 0, len(normedErrors))
 	for _, e := range normedErrors {
 		if e.Details == "" {
@@ -282,10 +292,11 @@ func asKey(key string) string {
 
 // flatten takes in a array of path components and looks for chances to flatten
 // objects that have index prefixes, examples:
-//   err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0]
-//   err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar
-//   err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar
-//   err(bar).ViaIndex(0).ViaIndex(1).ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar
+//
+//	err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0]
+//	err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar
+//	err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar
+//	err(bar).ViaIndex(0).ViaIndex(1).ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar
 func flatten(path []string) string {
 	var newPath []string
 	for _, part := range path {


Test patch:
diff --git a/apis/field_error_test.go b/apis/field_error_test.go
index da376d2f1..a52fb9e6a 100644
--- a/apis/field_error_test.go
+++ b/apis/field_error_test.go
@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
 type testStruct struct {
@@ -361,6 +362,38 @@ missing field(s): bar`,
 	}
 }
 
+func TestWrappedErrors(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		err  *FieldError
+		want []*FieldError
+	}{{
+		name: "nil",
+		err:  nil,
+		want: nil,
+	}, {
+		name: "empty",
+		err:  &FieldError{},
+		want: nil,
+	}, {
+		name: "has errors",
+		err: (&FieldError{}).Also(
+			ErrInvalidValue("42", "answer"),
+			ErrGeneric("generic", "error"),
+		),
+		want: []*FieldError{
+			ErrGeneric("generic", "error"),
+			ErrInvalidValue("42", "answer"),
+		},
+	}} {
+		t.Run(test.name, func(t *testing.T) {
+			if diff := cmp.Diff(test.want, test.err.WrappedErrors(), cmp.AllowUnexported(FieldError{}), cmpopts.EquateEmpty()); diff != "" {
+				t.Errorf("unexpected GetErrors() -want,+got: %s", diff)
+			}
+		})
+	}
+}
+
 func TestViaIndexOrKeyFieldError(t *testing.T) {
 	tests := []struct {
 		name     string