-
Notifications
You must be signed in to change notification settings - Fork 81
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
Satisfy errors.Is even if formatting is incorrect #61
base: v1
Are you sure you want to change the base?
Conversation
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.
Appreciate the regression tests done here.
Overall I think the implementation is a bit of a duck tape solution instead of patching the function with the problem.
Could we instead patch the problem with something like this to solve it?
func makeWrappedConstError(err error, format string, args ...interface{}) error {
separator := " "
if err.Error() == "" || errors.Is(err, &fmtNoop{}) {
separator = ""
}
args = append(args, err)
errPlacement := fmt.Sprintf("%%[%d]w", len(args))
return fmt.Errorf(strings.Join([]string{format, errPlacement}, separator), args...)
}
.gitignore
Outdated
@@ -21,3 +21,6 @@ _testmain.go | |||
|
|||
*.exe | |||
*.test | |||
|
|||
# IDE files | |||
/.idea/ |
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.
Can we not include this in the PR? Project git ignore shouldn't be for ignoring your systems files made. There is a global git ignore for that. Also don't want to introduce different concepts in the PR.
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.
global git ignore
Wow, first time I'm hearing about this
I think we can just simplify this too: func makeWrappedConstError(err error, format string, args ...interface{}) error {
separator := " "
if err.Error() == "" || errors.Is(err, &fmtNoop{}) {
separator = ""
}
args = append(args, err)
combinedFormat := fmt.Sprintf("%s%s%%[%d]w", format, separator, len(args))
return fmt.Errorf(combinedFormat, args...)
} |
|
@@ -116,7 +116,7 @@ func WithType(err error, errType ConstError) error { | |||
// interface. | |||
func Timeoutf(format string, args ...interface{}) error { | |||
return newLocationError( |
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.
Based on the docs from go tool vet
we should do this:
func Timeoutf(format string, args ...interface{}) error {
if false {
_ = fmt.Sprintf(format, args...) // enable printf checking
}
return newLocationError(
makeWrappedConstError(Timeout, format, args...),
1,
)
}
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.
We can do one better, and put this check inside makeWrappedConstError
.
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.
only reason I put it at the exported level was to save the linter from recursing further than it needs.
@hpidcock I agree, the vet check is a really nice thing to add in. However, we still have the same problem if the format string is not a constant. Even a case as simple as this still fails: fstring := "extra arg %v"
err := errors.NotValidf(fstring, 3, 2)
errors.Is(err, errors.NotValid) // false @tlm you have a fair point about this being a "duct-tape" solution. However, I personally would feel much more comfortable if these I don't think we should be relying on I'll come back with a more well-thought-out and well-designed solution. |
I honestly don't think these are hacky solutions, they are just not readable. That is ok. Needs a comment and its a sufficient and well formed solution. |
I agree with @hpidcock that the go vet check should be added. It's a static analysis check so it will only ever be performed on const strings as you just have no way of performing the check on what is dynamic runtime data. If you take the following code:
This suffers the same problem. It's just the fact of life that we live with. Taking this on as a runtime check would be both expensive and introduce a new error handling path into the code. I did some quick number counting over the code base to see how many of our format calls to the error library don't contain a constant string and it's bugger all. Was a bit hard to tell fully as newline's make it hard but we don't have a lot compared to how many of these calls we do have. As for the other solution being too complicated to read I am not sure I agree with that. But there are methods to make it more composed so it's build on pieces that are easier to digest and we can also add comments to the function to describe the intent. Your original PR still failed to address on the functions that needs to be maintained. Most of these *f() funcs are used because they offer very specific error formatting with the error type. For example "hazza not found". We have a ton of tests in Juju that rely on very specific error message formatting still. The solution I have proposed maintains this contract which is part of the reason we left these functions in place the first time.
How are they not doing that now. My advice. We have identified a very small problem here that has affected us a grand total of once thus far and hasn't made it to committed code. Both solutions proposed here fix the issue well. Keep it stupid simple and don't overcomplicate what is a simple problem and simple fix. |
The issue is that we're breaking the contract of these methods:
This is incorrect for the current code, and a more correct version would read:
The problem I have is that we're feeding things into this black box called
Except it doesn't, because
This PR has maintained that contract. You can run the unit tests locally to verify that none of the error messages have changed.
Huuuge caveat: IF the format string is correct. Otherwise it will not have the correct Unwrap behaviour. That is the whole point here.
Does |
5384712
to
4cc5e15
Compare
@hpidcock @tlm just pushed a new version of this which patches the
|
If you create an error, but provide the wrong number of format arguments:
then, the resulting error doesn't satisfy
errors.Is
as you'd expect:Obviously this is a user error in this case, but nonetheless, we should still fulfill the contract of
NotValidf
and ensure the returned error satisfieserrors.Is(_, errors.NotValid)
.The issue was that the format string was being passed directly to
fmt.Errorf
, and we were relying on the%w
functionality to correctly wrap the error type. If the error string is malformed, then theConstError
won't get wrapped properly - thus the returned error will fail to satisfyerrors.Is
.This PR fixes the problem by explicitly wrapping the error using
WithType
before returning. This way, we can ensure the returned error always satisfieserrors.Is
, no matter what is returned frommakeWrappedConstError
.I've also added regression tests for this bug.