-
Notifications
You must be signed in to change notification settings - Fork 40.9k
Nicer value rendering in API errors #132314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This PR may require API review. If so, when the changes are ready, complete the pre-review checklist and request an API review. Status of requested reviews is tracked in the API Review project. |
1 similar comment
This PR may require API review. If so, when the changes are ready, complete the pre-review checklist and request an API review. Status of requested reviews is tracked in the API Review project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/triage accepted
staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go
Outdated
Show resolved
Hide resolved
96a6584
to
6fcb038
Compare
Open questions:
|
6fcb038
to
5ac9af4
Compare
Today, if the value passed is a struct, map, or list, we get Go's vative rendering which is clunky. This uses JSON (could be kyaml when that is ready) instead. I hear it already: "But JSON is slow!". I benchmarked it -- for an simple int or string field, JSON is only a little slower (~20%) than a type assertion, but it IS slower, so I left the type assertion in. Remember that this is only called when an API error has occurred. The type assertions do not handle typedefs-to{string, int64, etc} so those will fall back on JSON. Almost all of our errors go thru standard functions which demand string or int64 anyway, so mostly pointless. I also benchmarked using reflect to check `CanInt()` and that is almost exactly as fast as type-switch but handles more cases, so we COULD switch to that instead, if we wanted. I thought it wasn't worth the complexity. JSON is really there to handle composite types.
Notes: * For types that define String() - should we prefer that or JSON? * metav1.Time has a MarshalJSON() and inhereits a String() and they are different * Since validation runs on internal types, we still get some GoNames instead of goNames.
5ac9af4
to
e68d601
Compare
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: thockin The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
(triage): |
At first glance, this seems like a situation where a user-visible representation of a value is needed, which is what
As we use error-wrapping style (i.e. |
Except that there is more text after the value:
That invalidates my argument and quoting becomes necessary. Or can we shuffle things around?
|
Apropos error rendering: should or shouldn't validation tests check for expected errors by comparing against full strings, check for more or less complete sub-strings, or against errors produced by calling the same error method as in the validation code? For DRA, I chose the latter because I didn't want the test to depend on the implementation of those methods. As seen in this PR, some other validation tests use strings which then need to be updated when changing the implementation. Strings have the advantage that one can check the readability of the user-visible error message and more easily spot when the wrong method is used when the result makes no sense. |
The Genesis of this PR (other than being a long-annoying thing) is declarative validation. We added support to auto-check listmap types for duplicates and throw errors. Testing those exposed the fact that we produce a "duplicate value" error, where the value is (for example) a whole container. It's clearly NOT a duplicate value, but the key is buried in there. I thought "perhaps we can return a As for readability, I don't think we intend the final error to be machine parseable or splittable, but I am eager to make it more useful to humans. E.g. is something like "Invalid value ("the value"): the details" better? I think the quotes are useful to distinguish As for unit tests, I prefer they operate with the new Matcher logic, so they are less dependent on exact strings. For example, I want to make the error message better for dns-label, and it breaks hundreds of tests. This is why we are adding "origin". |
So that's exactly the case where ignoring the One can also argue that the API errors are meant to provide a data dump of the values, not just a user visible rendering, because one may have to inspect the entire value.
Agreed, that's why I thought it would be okay to not use quoting. That can make strings less readable and works fine as long as humans can "spot" where the value starts.
If we keep quoting the value, then
So produce expected errors and compare against the actual errors, which is what DRA does - except that it doesn't use the ErrorMatcher helper yet. Let me look into changing that now... |
The advantage of matcher is that you can decide which criteria to match, often the field path + error type + detail substring is OK, but as we add more origin, the details string matters less and becomes the opposite of useful. |
I want my error matching to be pretty complete, but the "origin instead of detail string" is nice. I converted pkg/apis/resource/validation, which included making some changes elsewhere - see #132577 |
AFAIK this is OK to review now. |
Today, if the value passed is a struct, map, or list, we get Go's native rendering which is clunky.
This uses JSON (could be kyaml when that is ready) instead.
I hear it already: "But JSON is slow!". I benchmarked it -- for a simple int or string field, JSON is only a little slower (~20%) than a type assertion, but it IS slower, so I left the type assertion in. Remember that this is only called when an API error has occurred.
The type assertions do not handle typedefs-to-{string, int64, etc} so those will fall back on JSON. Almost all of our errors go thru standard functions which demand string or int64 anyway, so mostly pointless.
I also benchmarked using reflect to check
CanInt()
and that is almost exactly as fast as type-switch but handles more cases, so we COULD switch to that instead, if we wanted. I thought it wasn't worth the complexity.JSON is really there to handle composite types.
/kind bug
/kind cleanup