Is it always safe to include err.Error() in the response? - go

Example:
err := Db.Find(&event, id).Error
if err != nil {
c.JSON(500, err.Error())
return
}
I'm worried that it might include sensitive information. Example: when connecting to a database and the db credentials are invalid, I'm worried that the error message might be something like: "invalid username: sample and password: xxx"

Effectively you answered your own question: you pointed out it may contain sensitive information which means it is not always safe to include them in responses visible to the users.
It may also contain information related to your implementation (e.g. package names, type names, call hierarchy), and also configuration data (e.g. server name, database name, user names etc.), potentially exposing private and sensitive architecture and business information.
Think about it: you're a package author and you create the error values (error messages) returned by your functions / methods. You create descriptive error messages describing why a requested function cannot complete normally, intended for the callers of that function/method (the developers), and not for the end users - who shouldn't know what's going on under the hood.
error.Error() messages are for the developers. They are also useful during testing. And they are indispensable for hunting down bugs. You should not show them to the users, instead log them to which you have access, and provide a more general or a user friendly error message to the users, ensuring them that the dev team has been notified and are looking into the problem. Showing original error messages may cause confusion in inexperienced users, and may raise security issues.

The best way is to write to the error log, And custom database error info for return.
like:
var (
ErrEventNotFound = "Event not found."
)
err := Db.Find(&event, id).Error
if err != nil {
// Log to file
c.JSON(500, ErrEventNotFound)
return
}

Related

How do I access custom fields in an error?

Objective
Add a command to dropbox's CLI tool to get the shared link for the given path (file or folder).
The changes are here: github fork.
Background
The dropbox-go-sdk has a function that takes a path, and returns a new shared link, or returns an error containing the existing shared link.
I don't know how to use the error to extract the existing shared link.
Code
on github, and snippet here:
dbx := sharing.New(config)
res, err := dbx.CreateSharedLinkWithSettings(arg)
if err != nil {
switch e := err.(type) {
case sharing.CreateSharedLinkWithSettingsAPIError:
fmt.Printf("%v", e.EndpointError)
default:
return err
}
}
This prints the following:
&{{shared_link_already_exists} <nil> <nil>}found unknown shared link typeError: shared_link_already_exists/...
tracing:
CreateSharedLinkWithSettings --> CreateSharedLinkWithSettingsAPIError --> CreateSharedLinkWithSettingsError --> SharedLinkAlreadyExistsMetadata --> IsSharedLinkMetadata
IsSharedLinkMetadata contains the Url that I'm looking for.
More Info
The API docs point to CreateSharedLinkWithSettings, which should pass back the information in the error including the existing Url.
I struggle to understand how to deal with the error and extract the url from it.
The dbxcli has some code doing a similar operation, but again, not sure how it's working enough to apply it to the code I'm working on. Is it a Struct? Map? I don't know what this thing is called. There's some weird magic err.(type) stuff happening in the code. How do I access the data?
dbx := sharing.New(config)
res, err := dbx.CreateSharedLinkWithSettings(arg)
if err != nil {
switch e := err.(type) {
case sharing.CreateSharedLinkWithSettingsAPIError:
fmt.Printf("%v", e.EndpointError)
// type cast to the specific error and access the field you want.
settingsError := err.(sharing.CreateSharedLinkWithSettingsAPIError)
fmt.Println(settingsError.EndpointError.SharedLinkAlreadyExists.Metadata.Url)
default:
return err
}
}
The question was answered in the comments by #jimb. The answer is you access the fields like any other golang data structure - nothing special.
The errors I got when trying to access the fields were because the fields were not there.
The problem with the code was dependency issues. The code depends on an older version of the go-sdk and I referenced the latest version.
This question serves as a good explanation for how real golang programmers handle errors in their code with examples. I wasn't able to find this online, so I won't close the question.

How to suppress stack trace after `panic` in Go?

I am developing a CLI tool and in cases where the things go wrong, I want to log the custom error and exit with panic. The issue with panic is that an exit by panic is followed by the stack trace which I don't want to show to the user. Is there a way to panic and have a ninja-like stealthy/quiet exit?
(Choosing panic over os.Exit() since that would handle any defer and also seems a lot cleaner.)
Well, a direct answer is yes, there is a way:
panic with an error of a custom, "sentinel", type, or with a custom, "sentinel", value, then
Have a defer-red call in main which recover()-s the panic and checks whether the returned value is of a sentinel type (or equals to a sentinel value—the exact approach is up to you).
If it detects a "known" error, it silently exits with a non-zero exit code;
otherwise it panics again.
But honestly I think your mindset is too affected by programming languages with exceptions: I have yet to see a CLI app which would have any difficulty in handling errors "the usual way" or would actually benefit from bailing out using panic.
A counter-argument to the approach you're craving for is this: bubbling up an error allows adding more context to it on each level of the call stack being unwound, where it makes sense,—producing as useful as possible error to display.
Basically, it works like this:
func main() {
...
err := DoStuff()
if err != nil {
log.Fatal("failed to do stuff: ", err)
}
...
}
func DoStuff() error {
foo, err := InitializeWhatever()
if err != nil {
return fmt.Errorf("failed to inialize whatever: %w", err)
}
...
return nil
}
func InitializeWhatever() (*Whatever, error) {
handle, err := OpenWhateverElse()
if err != nil {
return nil, fmt.Errorf("failed to open whatever else: %w", err)
}
...
return whatever, nil
}
…which would produce something like
failed to do stuff: failed to inialize whatever: failed to open whatever else: task failed successfully
…which makes it crystal clear which sequence of events led to the undesired outcome.
Sure, as usually, YMMV and no one except you knows your situation best, but still it's something to ponder.
And here's an assorted list of thoughts on what I've written above.
The approach with panicking with a known value / error of a known type is actually nothing new—for instance, see net/http.ErrAbortHandler.
The encoding/json package used to employ this approach for breaking out of multiple nested loops (has been reworked since then, though).
Still, some experts consider sentinel errors to be bad design and recommend instead asserting (using Go's type assertions) behaviour of the errors—for instance, you can look at how net.Error has Timeout and Temporary methods which allows to not have concrete exported types for temporary errors, and errors due to timeouts, and their combinations,—but instead have them all support a common set of methods which allow the callers to make sense of the error's nature.
Since 1.13, Go gained advanced support for "wrapping" errors—producing matryoshka-style error chains, and this allows an approach to error handling which sits somewhere between the two extremes above: one can create an error of a particular type at the place an error was first found, then wrap it in contexts as they bubble up the call stack, and then assert the type of the innermost error at the place which handles the error. A friendly explanation on how it works.
Well, and I'd recommend reading this for, I must admit, had you already done this, you'd probably not ask your question in the first place ;-)

How to get all Modifying Users of a specific Revision

Follow up on Google Drive Rest API : How to get all Modifying Users of a specific Revision
It has been approximately 3 years, so I am not sure what the status is, but I thought I would ask again on the status.
I see that Google Drive API # https://developers.google.com/drive/api/v3/reference/revisions/get
should actually do exactly what has been asked here, but when I make a call to the API it returns null for LastModifyingUser
I am not sure if this is a work in progress API or I am doing something wrong, so any help would be appreciated.
Just to provide some reference, I am posting some basic code that is an addition to what can be found here... https://developers.google.com/drive/api/v3/quickstart/go
revision, err := srv.Revisions.Get(fileId, revisionId).Do() //fieldId and revisionId are fatched using proper calls
if err != nil {
log.Fatalf("Unable to retrieve revision: %v", err)
}
fmt.Println("Revision:")
fmt.Printf("%+v\n", revision.LastModifyingUser)
You want to retrieve the value of lastModifyingUser from Revisions.Get() using Drive API v3.
If my understanding is correct, how about adding the fields? At the default, the fields are id,mimeType,modifiedTime. So when you want to retrieve only values of lastModifyingUser, please modify as follows.
From:
revision, err := srv.Revisions.Get(fileId, revisionId).Do()
To:
revision, err := srv.Revisions.Get(fileID, revisionID).Fields("lastModifyingUser").Do()
Note:
In this modified script, it is supposes that when you run your current script, no error occurs.
If you want to add lastModifyingUser to the default values of id,mimeType,modifiedTime, please set the fields to id,mimeType,modifiedTime,lastModifyingUser.
Reference:
Revisions
If I misunderstand your question, I'm sorry.

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.

Use package file to write to Cloud Storage?

Golang provides the file package to access Cloud Storage.
The package's Create function requires the io.WriteCloser interface. However, I have not found a single sample or documentation showing how to actually save a file to Cloud Storage.
Can anybody help? Is there a higher level implementation of io.WriteCloser that would allow us to store files in Cloud Storage? Any sample code?
We've obviously tried to Google it ourselves but found nothing and now hope for the community to help.
It's perhaps true than the behavior is not well defined in the documentation.
If you check the code: https://code.google.com/p/appengine-go/source/browse/appengine/file/write.go#133
In each call to Write the data is sent to the cloud (line 139). So you don't need to save. (You should close the file when you're done, through.)
Anyway, I'm confused with your wording: "The package's Create function requires the io.WriteCloser interface." That's not true. The package's Create functions returns a io.WriteCloser, that is, a thingy you can write to and close.
yourFile, _, err := Create(ctx, "filename", nil)
// Check err != nil here.
defer func() {
err := yourFile.Close()
// Check err != nil here.
}()
yourFile.Write([]byte("This will be sent to the file immediately."))
fmt.Fprintln(yourFile, "This too.")
io.Copy(yourFile, someReader)
This is how interfaces work in Go. They just provide you with a set of methods you can call, hiding the actual implementation from you; and, when you just depend on a particular interface instead of a particular implementation, you can combine in multiple ways, as fmt.Fprintln and io.Copy do.

Resources