Skip to content
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

Add details to APIError #613

Merged
merged 1 commit into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions apierr/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ var (
}
)

const (
errorInfoType string = "type.googleapis.com/google.rpc.ErrorInfo"
)

// APIErrorBody maps "proper" databricks rest api errors to a struct
type APIErrorBody struct {
ErrorCode string `json:"error_code,omitempty"`
Message string `json:"message,omitempty"`
ErrorCode string `json:"error_code,omitempty"`
Message string `json:"message,omitempty"`
Details []ErrorDetail `json:"details,omitempty"`
// The following two are for scim api only
// for RFC 7644 Section 3.7.3 https://tools.ietf.org/html/rfc7644#section-3.7.3
ScimDetail string `json:"detail,omitempty"`
Expand All @@ -41,11 +46,19 @@ type APIErrorBody struct {
API12Error string `json:"error,omitempty"`
}

type ErrorDetail struct {
Type string `json:"@type,omitempty"`
Reason string `json:"reason,omitempty"`
Domain string `json:"domain,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}

// APIError is a generic struct for an api error on databricks
type APIError struct {
ErrorCode string
Message string
StatusCode int
Details []ErrorDetail
}

// Error returns error message string instead of
Expand All @@ -62,6 +75,25 @@ func IsMissing(err error) bool {
return false
}

// GetErrorInfo returns all entries in the list of error details of type `ErrorInfo`.
func GetErrorInfo(err error) []ErrorDetail {
return getDetailsByType(err, errorInfoType)
}

func getDetailsByType(err error, errorDetailType string) []ErrorDetail {
var apiError *APIError
if !errors.As(err, &apiError) {
return nil
}
filteredDetails := []ErrorDetail{}
for _, detail := range apiError.Details {
if errorDetailType == detail.Type {
filteredDetails = append(filteredDetails, detail)
}
}
return filteredDetails
}

// IsMissing tells if it is missing resource
func (apiError *APIError) IsMissing() bool {
return apiError.StatusCode == http.StatusNotFound
Expand Down Expand Up @@ -165,6 +197,7 @@ func parseErrorFromResponse(resp *http.Response, body []byte) *APIError {
Message: errorBody.Message,
ErrorCode: errorBody.ErrorCode,
StatusCode: resp.StatusCode,
Details: errorBody.Details,
}
}

Expand Down
54 changes: 54 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,60 @@ func TestSimpleRequestFailsAPIError(t *testing.T) {
assert.EqualError(t, err, "nope")
}

func TestETag(t *testing.T) {
reason := "some_reason"
domain := "a_domain"
eTag := "sample_etag"
c := &DatabricksClient{
Config: config.NewMockConfig(func(r *http.Request) error {
return nil
}),
httpClient: hc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 400,
Request: r,
Body: io.NopCloser(strings.NewReader(fmt.Sprintf(`{
"error_code": "RESOURCE_CONFLICT",
"message": "test_public_workspace_setting",
"stack_trace": "java.io.PrintWriter@329e4ed3",
"details": [
{
"@type": "%s",
"reason": "%s",
"domain": "%s",
"metadata": {
"etag": "%s"
}
},
{
"@type": "anotherType",
"reason": "",
"domain": "",
"metadata": {
"etag": "anotherTag"
}
}
]
}`, "type.googleapis.com/google.rpc.ErrorInfo", reason, domain, eTag))),
}, nil
}),
rateLimiter: rate.NewLimiter(rate.Inf, 1),
}
err := c.Do(context.Background(), "GET", "/a/b", map[string]string{
"e": "f",
}, map[string]string{
"c": "d",
}, nil)
details := apierr.GetErrorInfo(err)
assert.Equal(t, 1, len(details))
errorDetails := details[0]
assert.Equal(t, reason, errorDetails.Reason)
assert.Equal(t, domain, errorDetails.Domain)
assert.Equal(t, map[string]string{
"etag": eTag,
}, errorDetails.Metadata)
}

func TestSimpleRequestSucceeds(t *testing.T) {
type Dummy struct {
Foo int `json:"foo"`
Expand Down