I'm implementing server using GIN Framework.
The Gin framework has a handler for each route.
Each function handler has it's own controller that returns some result (basically error)
package controller
var (
ErrTooManyAttempts = errors.New("too many attempts")
ErrNoPermission = errors.New("no permission")
ErrNotAvailable = errors.New("not available")
)
This errors are created for developers and logging, so we need to beautify and translate these before sending them to the client.
To be able to get translation we should know the key of the message.
But the problem is I need to bind these errors in some map[error]string (key) to get translation key by error.
It's quite complex because I have to bind all the errors with the corresponding keys.
To improve it, I'd like to use reflection:
Get all variables in the package
Walk through and find Err prefix
Generate translation key based on package name and error name, ex: controller-ErrTooManyAttempts
Merge translation file, so it should look like this:
`
{
"controller-ErrTooManyAttempts": "Too many attempts. Please try again later",
"controller-ErrNoPermission": "Permission to perform this action is denied",
"controller-ErrNotAvailable": "Service not available. Please try again later"
}
`
What is the correct way to translate errors from the package? Is it possible to provide me with some example?
Related
When working with go there is a pattern used to define errors and handle them in a (to me) very peculiar way. Often errors are declared like ErrorSomethingWentWrong = errors.New("Just an example!"). You can use errors.Is(err, ErrorSomethingWentWrong) to catch that specific error. The Is function can do this by comparing the pointers. But in order to make the comparison I need to know which variable name is used to define the errorString so I can use errors.Is to catch it.
For example:
ErrorSomethingWentWrong = errors.New("Just an example!")
func DoSomething() (*Something, error) {
return nil, ErrorSomethingWentWrong
}
I know a error is returned with the string "Just an example!" But I don't know it has the variable name ErrorSomethingWentWrong:
func checkError() {
if errors.Is(err, ErrorSomethingWentWrong){ // how to know this???
//handle error
}
}
When I use errors.Is(err, ErrorSomethingWentWrong) I can catch this error and handle it. When using debugging I can't see that the errorString represents the ErrorSomethingWentWrong variable. But when I don't know that the variable name was ErrorSomethingWentWrong I need to reverse engineer the code or read the docs to know which error is returned.
So how can you know using debugging or reflection to retrieve the error variable name?
If I understand correctly, this is your scenario:
You are receiving an error from a package, and there is no documentation to provide you with the errors you should be handling. You are debugging, and see an errors.errorString, but don't know if there is a global and public error value that you can compare to.
Unfortunately, this is a value and the debugger can't tell you if this is a global variable or if so which one, because you don't receive a variable, only a value.
You can:
read through the source code of the package you are calling and see which errors are being returned and how to handle them
search the source code for the text in the error string, this could help you pinpoint where the error is defined or created.
If it turns out the specific error you wish to handle is not defined globally (and re-writing the package is not feasible for your case), so you can't handle it. This can happen if somebody uses errors.New inside a function (bad practice). Then you can also try these:
func handleError(err error) {
if err.Error() == "full error string" {}
if strings.Contains(err.Error(), "partial error string") {}
}
But those are both ugly imho.
TL;DR It's not possible, you have to read the source code.
If a piece of code has its own error type as in
var ErrSomethingWentWrong = errors.New("Something went wrong"
I believe in my code I can do this
import github.com/therepo/theproject/thepackage/thatexportstheaboveerror
// code that might return the above error
if err == thatexportstheaboveerror.ErrSomethingWentWrong {
// handle the particular case
}
What happens when the error returned is via fmt.Errorf as in this case?
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
How can value (or type or whatever) check/assertion should be performed go-idiomatically?
Is this the only way around it?
if err != nil {
if strings.Contains(err.Error(), "unable to authenticate") {
// handle the particular error
}
}
Since Go version 1.13, the errors package has included the concept of "wrapping" errors. A routine returning a "wrapped" error calls fmt.Errorf using the %w verb in the format, to wrap an inner error into an outer error. An outer error can then be tested to see if it contains a particular inner error.
Prior to that, xerrors provided the same general concept.
Both of these require that whoever produces the error value use the provided wrapping interface. The routine you are asking about—part of https://godoc.org/golang.org/x/crypto/ssh—does not do this. Some older code that doesn't wrap errors provides certain kind of error-testing functions. For instance, when os.Open returns an error, os.IsNotExist will tell you if that error is because the file does not exist.
Unfortunately this particular package has no such test, so you're pretty much stuck with what you've suggested (direct string inspection) if you really want to know, programmatically, that this error came from this particular source.
I'm working on a custom controller for a custom resource using kubebuilder (version 1.0.8). I have a scenario where I need to get a list of all the instances of my custom resource so I can sync up with an external database.
All the examples I've seen for kubernetes controllers use either client-go or just call the api server directly over http. However, kubebuilder has also given me this client.Client object to get and list resources. So I'm trying to use that.
After creating a client instance by using the passed in Manager instance (i.e. do mgr.GetClient()), I then tried to write some code to get the list of all the Environment resources I created.
func syncClusterWithDatabase(c client.Client, db *dynamodb.DynamoDB) {
// Sync environments
// Step 1 - read all the environments the cluster knows about
clusterEnvironments := &cdsv1alpha1.EnvironmentList{}
c.List(context.Background(), /* what do I put here? */, clusterEnvironments)
}
The example in the documentation for the List method shows:
c.List(context.Background, &result);
which doesn't even compile.
I saw a few method in the client package to limit the search to particular labels, or for a specific field with a specific value, but nothing to limit the result to a specific resource kind.
Is there a way to do this via the Client object? Should I do something else entirely?
So figured it out - the answer is to pass nil for the second parameter. The type of the output pointer determines which sort of resource it actually retrieves.
According to the latest documentation, the List method is defined as follows,
List(ctx context.Context, list ObjectList, opts ...ListOption) error
If the List method you are calling has the same definition as above, your code should compile. As it has variadic options to set the namespace and field match, the mandatory arguments are Context and objectList.
Ref: KubeBuilder Book
I have a service type called ComputeService which implements certain domain logic. The service itself depends on implementation of an interface called Computer which has a method Computer.Compute(args...) (value, error). As shown, Compute itself might return certain errors.
ComputeService needs to send appropriate errors from a set of domain-errors with proper domain-error code so that translations can be done and also clients can handle errors appropriately.
My question is, should the Computer implementations be wrapping their failure in domain-errors or should ComputeService do this. If ComputeService is the one doing it, then it will have to know about different errors returned by different implementations of Computer interface which in my opinion breaks the abstraction. Both ways are demonstrated below:
package arithmetic
type Computer struct {
}
func (ac Computer) Compute(args ....) (value, error) {
// errors is a domain-errors package defined in compute service project
return errors.NewDivideByZero()
}
OR
package compute
type Service struct {
}
func (svc Service) Process(args...) error {
computer := findComputerImplementation(args...)
val, err := computer.Compute(args...)
if err != nil {
if err == arith.ErrDivideByZero {
// converting an arithmetic computer implementation
// specific error to domain error
return errors.NewDivideByZero()
} else if err == algebra.ErrInvalidCoEfficient {
// converting an algebraic computer implementation
// specific error to domain error
return errors.NewBadInput()
}
// some new implementation was used and we have no idea
// what errors it could be returning. so we have to send
// a internal server error equivalent here
return errors.NewInternalError()
}
}
Implementors of Computer should respond with the domain errors, since they're the closest ones to the action and best able to determine what an error is. Like you said, having that logic in ComputeService breaks the abstraction. If you need mapping code from specific Computer errors to domain errors, create wrapper structs that separate the main logic from that error wrapping code.
To keep internal error context, just embed the original error in the domain error and make IsSpecificDomainError helpers.
type MyDomainError struct {
Err error
}
func NewMyDomainErr(err error) error {
return &MyDomainError{err}
}
func IsMyDomainError(e error) bool {
_, ok := err.(*MyDomainError)
return ok
}
To keep internal error context, just embed the original error in the domain error
This can use Wrapping errors, which are on their way for Go 1.13 (Q4 2019), from issue 29934, as detailed here.
err.Is():
As Russ Cox mentions:
I think we all agree that strings.Contains(err.Error(), "not found") is fragile code.
I hope we also agree that we'd prefer to see code like errors.Is(err, os.ErrNotExist).
But the point is that in many cases, it is essential to future evolution of a package to keep callers from depending on a particular error result satisfying errors.Is(err, os.ErrNotExist), even if that is the underlying cause in today's result.
It's like looking at an unexported field or comparing error text - it's a detail that might change.
And while strings.Contains looks and is fragile, errors.Is does not look nor should be considered fragile.
If we are to avoid it being fragile, then we need to provide a way for packages to report detail without letting clients test for it. That way is errors that can't be unwrapped.
err.As():
var pe *os.PathError
if errors.As(err, &pe) {
use(pe)
}
%w:
func inner() error { return errors.New("inner error") }
func outer() error { return fmt.Errorf("outer error: %w", inner()) }
fmt.Fprintf("%+v", outer())
// outer error:
// /path/to/file.go:123
// - inner error:
// /path/to/file.go:122
The current status for Go 1.13:
Just stating what I see as the compromise solution offered by the team:
fmt.Errorf is currently being used extensively to wrap errors and return a new (opaque) error (as in you cannot access the underlying error).
'%w' can now be used to explicitly opt-in to return an error that can be unwrapped.
errors is designed as a base package with no dependencies so that every package can depend on it.
the team agrees to punt on the areas that there is broad disagreement, and want to release just enough (errors.Is, errors.As, extension to way most folks wrap errors) so folks can achieve things.
Generics is not here yet, and we do not know when it will come: the heated discussion on that will make this one on "error 2 values" look like child's play.
errors.Is and errors.As are clean and concise enough to be comfortable for a long time.
Most of the contentious things have been punted to go 1.14.
Wrapf cannot live in errors as it is a base package.
Wrapf means team MUST decide on what happens when a nil error is passed: Punt on it.
Wrap may conflict with ideas being considered for localization, internationalization, etc.
ErrorFormatter and ErrorPrinter have not yet gotten much deeper usage and have warts. Punt.
I have an Go app (web service) which returns errors in json like this:
{errorString: "internal server error"}
It's not ok because "internal server error" is just some programmer error string not usefull for client. The solution is to add error codes:
{errCode: 1, errorString: "internal server error"}
Now, client known that 1 means "internal server error" and can process it as want. For example, show for user message "Internal Server Error" or (in my case) the same in russian lang.
Ok.
So, obviously i need some file where all error constants will be described.
For ex. errors.go
const (
ErrNo = iota
// Common Errors
ErrNotFound
ErrInternalServerError
**// More specified errors**
)
The problem is in More specified errors section.
I have 2 ways:
places all errors definiton in one file errors.go
try to place specific errors to each related file:
my controller is devided on several files in package server:
clienthandler.go -- for client requests,
orderhandler.go -- for orders requests and so on.
specific client errors must be places in clienthandler.go, order errors in orderhandler.go
But how it can be realized?
I know one simple solution:
Take some max count of errors for each controller, for example 1000.
clienthandler.go
package server
const (
ErrCheckIdCity = 1000*1 + iota
ErrCheckName
)
that is 1000 errors (from 1000 to 1999) reserved for this file
orderhandler.go
package server
const (
ErrCheckIdCity = 1000*2 + iota
ErrCheckItem
)
that is 1000 errors (from 2000 to 2999) reserved for this file
But disadvantge is that we limit myself by 1000 errors per controller
May be thers is some better way?
Or i need just use one global errros.go file ) ?
Place each error where it's originated and export it.
See the link from my comment.
var ErrInvalidParam = fmt.Errorf(“invalid parameter [%s]”, param)
If you want to add an error code, create a new type satisfying the error interface and add the appropriate members like errorCode or related data.
If you want, create a build method as helper, similar as errors.New does