diff --git a/googleapi/googleapi.go b/googleapi/googleapi.go index 75248fd16ec..a520579e12d 100644 --- a/googleapi/googleapi.go +++ b/googleapi/googleapi.go @@ -79,6 +79,8 @@ type Error struct { Header http.Header Errors []ErrorItem + // Err is a wrapped apierror.APIError, typically derived from this Error. + Err error } // ErrorItem is a detailed error code & message from the Google API frontend. @@ -122,6 +124,10 @@ func (e *Error) Error() string { return buf.String() } +func (e *Error) Unwrap() error { + return e.Err +} + type errorReply struct { Error *Error `json:"error"` } diff --git a/internal/gensupport/error.go b/internal/gensupport/error.go new file mode 100644 index 00000000000..5919e9976b9 --- /dev/null +++ b/internal/gensupport/error.go @@ -0,0 +1,22 @@ +// Copyright 2020 Google LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gensupport + +import ( + "github.com/googleapis/gax-go/v2/apierror" + "google.golang.org/api/googleapi" +) + +// WrapError creates an apierror.APIError from err (one that +// does not wrap err), wraps it in err, and returns err. If +// err is not a googleapi.Error (or a gRPC Status), it returns +// err without modification. +func WrapError(err *googleapi.Error) *googleapi.Error { + // TODO: Update this call to apierror.FromWrappingError once it is available. + if apiError, ok := apierror.FromError(err); ok { + err.Err = apiError + } + return err +} diff --git a/internal/gensupport/error_test.go b/internal/gensupport/error_test.go new file mode 100644 index 00000000000..18b590db7af --- /dev/null +++ b/internal/gensupport/error_test.go @@ -0,0 +1,48 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gensupport + +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/googleapis/gax-go/v2/apierror" + "google.golang.org/api/googleapi" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/protobuf/proto" +) + +func TestWrapError(t *testing.T) { + // The error format v2 for Google JSON REST APIs, per https://cloud.google.com/apis/design/errors#http_mapping. + jsonErrStr := "{\"error\":{\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\", \"reason\":\"just because\", \"domain\":\"tests\"}]}}" + hae := &googleapi.Error{ + Body: jsonErrStr, + } + hae = WrapError(hae) + + var aerr *apierror.APIError + if ok := errors.As(hae, &aerr); !ok { + t.Errorf("got false, want true") + } + + httpErrInfo := &errdetails.ErrorInfo{Reason: "just because", Domain: "tests"} + details := apierror.ErrDetails{ErrorInfo: httpErrInfo} + if diff := cmp.Diff(aerr.Details(), details, cmp.Comparer(proto.Equal)); diff != "" { + t.Errorf("got(-), want(+),: \n%s", diff) + } + if s := aerr.Reason(); s != "just because" { + t.Errorf("Reason() got %s, want 'just because'", s) + } + if s := aerr.Domain(); s != "tests" { + t.Errorf("Domain() got %s, want nil", s) + } + if err := aerr.Unwrap(); err != nil { + t.Errorf("Unwrap() got %T, want nil", err) + } + if m := aerr.Metadata(); m != nil { + t.Errorf("Metadata() got %v, want nil", m) + } +}