Using an error variable as package-level vs global variable - go

Using the Go language I am writing a function that uses a custom error variable, e.g.
func readEnv() (map[string]string, error) {
var ErrConfig = errors.New("Configuration Error: Variables starting with the right prefix are not even")
if this {
return nil, ErrConfig
}
I declare this as a local var to avoid declaring it as a package-level var which is not recommended (and marked as issue from the linter if I am not mistaken)
The problem arises when I want to unit test this fxn and want to also test the error path (in which case the fxn should return the above error which however has not access to.). Now, the only way I can think of to address this is to re-declare this variable in my table test.
Which is the right way? Declaring ErrConfig as a package-level variable or just re-declaring it in the unit test fxn?

Does it matter to the caller of readEnv() what error you return?
If it doesn't matter, your test shouldn't care either, just check if the returned error is nil or not.
If it matters, in your current solution callers can't do any better than you in your tests. If it matters and clients should be able to tell, you have to export some kind of mechanism to test / examine the returned error.
One solution is to move ErrConfig to a package level variable. This is accepted, used numerous places in the standard lib too, e.g. io.EOF, io.ErrClosedPipe.
This is not the only solution though. You could make Errconfig unexported (e.g. errConfig), and provide an exported function to test an error, e.g.:
var errConfig = errors.New("Configuration Error: Variables starting...")
func IsErrConfig(err error ) bool {
return err == errConfig
}
This is also used in many places in the standard lib, e.g. os.IsExist(), os.IsNotExist()
You could also make the returned error implement an exported interface, and callers can check using a type assertion if the returned error implements that, providing extra functionality for the returned error.

Related

How to know variable name adhering to go error

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.

How can I get the AWS Lambda Alias in my Go function?

I want to run some Lambda alias specific code before my main function starts executing. It currently looks like this
func init() {
// Trying to get Lambda function alias here
}
func main() {
adapter = chiproxy.New(r)
lambda.start(lambdaHandler)
}
func lambdaHandler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
c, err := adapter.ProxyWithContext(ctx, req)
return c, err
}
req in lambdaHandler has the info I need but I don't know how to get it in init()?
I don't think you can get the alias before the function has been passed a request. lambda automatically provides a number of environment variables but alias is not one of them.
That's probably because the alias is just a pointer to the version; you could have many aliases pointing to the same version, but the same function runtime could be initialized for all of them. So it wouldn't make sense to provide any particular alias at initialization time.
I want to run some Lambda alias specific code before my main function starts executing
But the function's runtime isn't specific to an alias, it's specific to a version. In fact you could create a new alias after the version had been instantiated, and the same cached runtime could be used.
I think I can see why you might want to have invokers call the same function with different aliases, but I'm not sure it's going to be a low friction path to achieve what you want with aliases. Consider whether you might instead want to create bunch of different functions - which could have the same codebase, and have init choose a proper handler or do other init based on the function name instead of its alias.

How to handle errors for values declared outside of funcs in Go source file

I have this:
var wd, _ = os.Getwd()
var advanceAgreementTemplateBytes, _ = ioutil.ReadFile(path.Join(wd, "/docs/templates/advance-agreement.template"))
var advanceAgreementTemplate = string(advanceAgreementTemplateBytes)
var t, _ = template.New("advance-agreement").Parse(string(advanceAgreementTemplate))
func sendAdvanceEmail(user *common.User, amount *apd.Decimal, fee *apd.Decimal, created time.Time) {
// use variables `t`, `advanceAgreementTemplate` etc here
}
I want to be able to handle the errors in the cached variables instead of using _ instead of err. Is the only way to do this is to use func init()?
If you want to error-check these, use func init or just initialize them in main.
Not all calls have Must variants, and such variants don't make sense for all calls. Don't forget that the Go way is to do proper error checking. Having Must wrappers for all possible failing calls would encourage a bad programming style, where instead of handling and propagating errors, every error would crash by default.
It's very easy to write your own Must wrappers if all you need is to crash the program on error.
The benefit of writing your own Must wrapper is that you could provide a much better error message - another thing Go encourages. ioutil.ReadFile will just say "open /docs/templates/advance-agreement.template: No such file or directory".

Checking (value or type) against errors returned by fmt.Errorf

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.

Mapping external errors to domain errors in golang

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.

Resources