Skip to content

Commit

Permalink
Merge pull request #30 from atlanhq/error-handling-cli
Browse files Browse the repository at this point in the history
DVX483: Enhancement - Improve error handling
  • Loading branch information
0xquark authored May 27, 2024
2 parents a2a028e + aa2d048 commit cd7e289
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 43 deletions.
26 changes: 11 additions & 15 deletions atlan/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"github.com/atlanhq/atlan-go/atlan/logger"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -206,11 +205,7 @@ func (ac *AtlanClient) CallAPI(api *API, queryParams interface{}, requestObj int
//logger.Log.Debugf("Params: %v", params)
response, err := ac.makeRequest(api.Method, path, params)
if err != nil {
if response != nil && response.Body != nil {
errorMessage, _ := ioutil.ReadAll(response.Body)
return nil, handleApiError(response, string(errorMessage))
}
return nil, err
return nil, handleApiError(response, err)
}

ac.logHTTPStatus(response)
Expand All @@ -222,7 +217,7 @@ func (ac *AtlanClient) CallAPI(api *API, queryParams interface{}, requestObj int
}

if response.StatusCode != api.Status {
return nil, fmt.Errorf("unexpected HTTP status: %s", response.Status)
return nil, handleApiError(response, err)
}

ac.logResponse(responseJSON)
Expand All @@ -237,12 +232,18 @@ func (ac *AtlanClient) makeRequest(method, path string, params map[string]interf
switch method {
case http.MethodGet:
req, err = http.NewRequest(method, path, nil)
if err != nil {
ThrowAtlanError(err, CONNECTION_ERROR, nil)
}
case http.MethodPost, http.MethodPut:
body, ok := params["data"].(io.Reader)
if !ok {
return nil, fmt.Errorf("missing or invalid 'data' parameter for POST/PUT/DELETE request")
}
req, err = http.NewRequest(method, path, body)
if err != nil {
ThrowAtlanError(err, CONNECTION_ERROR, nil)
}
req.Header.Set("Content-Type", "application/json")
case http.MethodDelete:
// DELETE requests may not always have a body.
Expand All @@ -254,7 +255,9 @@ func (ac *AtlanClient) makeRequest(method, path string, params map[string]interf
}
}
req, err = http.NewRequest(method, path, body)

if err != nil {
ThrowAtlanError(err, CONNECTION_ERROR, nil)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
Expand Down Expand Up @@ -313,13 +316,6 @@ func (ac *AtlanClient) logAPICall(method, path string) {

func (ac *AtlanClient) logHTTPStatus(response *http.Response) {
ac.logger.Debugf("HTTP Status: %s", response.Status)
if response.StatusCode < 200 || response.StatusCode >= 300 {
errorMessage, err := ioutil.ReadAll(response.Body)
if err != nil {
ac.logger.Errorf("Error reading response body: %v", err)
}
ac.logger.Errorf("Error: %s", handleApiError(response, string(errorMessage)))
}
}

func (ac *AtlanClient) logResponse(responseJSON []byte) {
Expand Down
28 changes: 14 additions & 14 deletions atlan/client/custom_metadata_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (c *CustomMetadataCache) RefreshCache() error {
}

if response == nil || len(response.CustomMetadataDefs) == 0 {
return AuthenticationError{AtlanError{ErrorCode: errorCodes[EXPIRED_API_TOKEN]}}
return ThrowAtlanError(err, EXPIRED_API_TOKEN, nil)
}

// Clear existing cache data
Expand Down Expand Up @@ -153,7 +153,7 @@ GetIDForName Translate the provided human-readable custom metadata set name to i
*/
func (c *CustomMetadataCache) GetIDForName(name string) (string, error) {
if name == "" || strings.TrimSpace(name) == "" {
return "", InvalidRequestError{AtlanError{ErrorCode: errorCodes[MISSING_CM_NAME]}}
return "", ThrowAtlanError(nil, MISSING_CM_NAME, nil)
}

c.mutex.RLock()
Expand All @@ -162,15 +162,15 @@ func (c *CustomMetadataCache) GetIDForName(name string) (string, error) {

if !exists {
if err := c.RefreshCache(); err != nil {
return "", ApiError{AtlanError{ErrorCode: errorCodes[CONNECTION_ERROR]}}
return "", ThrowAtlanError(err, CONNECTION_ERROR, nil)
}

c.mutex.RLock()
cmID, exists = c.MapNameToID[name]
c.mutex.RUnlock()

if !exists {
return "", NotFoundError{AtlanError{ErrorCode: errorCodes[CM_NOT_FOUND_BY_NAME], Args: []interface{}{name}}}
return "", ThrowAtlanError(nil, CM_NOT_FOUND_BY_NAME, nil, name)
}
}

Expand All @@ -190,7 +190,7 @@ GetNameForID Translate the provided Atlan-internal custom metadata ID string to
*/
func (c *CustomMetadataCache) GetNameForID(idstr string) (string, error) {
if idstr = strings.TrimSpace(idstr); idstr == "" {
return "", InvalidRequestError{AtlanError{ErrorCode: errorCodes[MISSING_CM_ID]}}
return "", ThrowAtlanError(nil, MISSING_CM_ID, nil)
}

c.mutex.RLock()
Expand All @@ -207,7 +207,7 @@ func (c *CustomMetadataCache) GetNameForID(idstr string) (string, error) {
c.mutex.RUnlock()

if !exists {
return "", NotFoundError{AtlanError{ErrorCode: errorCodes[CM_NOT_FOUND_BY_ID], Args: []interface{}{idstr}}}
return "", ThrowAtlanError(nil, CM_NOT_FOUND_BY_ID, nil, idstr)
}
}

Expand Down Expand Up @@ -235,7 +235,7 @@ func (c *CustomMetadataCache) GetAllCustomAttributes(includeDeleted, forceRefres
for typeID, cm := range c.CacheByID {
typeName, err := c.GetNameForID(typeID)
if err != nil {
return nil, NotFoundError{AtlanError{ErrorCode: errorCodes[CM_NOT_FOUND_BY_ID], Args: []interface{}{typeID}}}
return nil, ThrowAtlanError(err, CM_NOT_FOUND_BY_NAME, nil, typeID)
}
var toInclude []model.AttributeDef
if includeDeleted {
Expand Down Expand Up @@ -282,9 +282,9 @@ func (c *CustomMetadataCache) GetAttrIDForName(setName, attrName string) (string
// If found, return straight away
return attrID, nil
}
return "", NotFoundError{AtlanError{ErrorCode: errorCodes[CM_ATTR_NOT_FOUND_BY_NAME], Args: []interface{}{attrName, setName}}}
return "", ThrowAtlanError(nil, CM_ATTR_NOT_FOUND_BY_NAME, nil, attrName, setName)
}
return "", NotFoundError{AtlanError{ErrorCode: errorCodes[CM_ATTR_NOT_FOUND_BY_ID], Args: []interface{}{setID}}}
return "", ThrowAtlanError(nil, CM_ATTR_NOT_FOUND_BY_ID, nil, setID)
}

/*
Expand All @@ -309,7 +309,7 @@ func (c *CustomMetadataCache) GetAttrNameForID(setID, attrID string) (string, er
return attrName, nil
}
}
return "", NotFoundError{AtlanError{ErrorCode: errorCodes[CM_ATTR_NOT_FOUND_BY_ID], Args: []interface{}{setID}}}
return "", ThrowAtlanError(nil, CM_ATTR_NOT_FOUND_BY_ID, nil, setID)
}

func (c *CustomMetadataCache) GetAttributesForSearchResults(setID string) []string {
Expand Down Expand Up @@ -370,13 +370,13 @@ GetCustomMetadataDef Retrieve the full custom metadata structure definition.
func (c *CustomMetadataCache) GetCustomMetadataDef(name string) (model.CustomMetadataDef, error) {
baID, _ := c.GetIDForName(name)
if baID == "" {
return model.CustomMetadataDef{}, NotFoundError{AtlanError{ErrorCode: errorCodes[CM_NOT_FOUND_BY_NAME], Args: []interface{}{name}}}
return model.CustomMetadataDef{}, ThrowAtlanError(nil, CM_NOT_FOUND_BY_NAME, nil, name)

}
if typedef, ok := c.CacheByID[baID]; ok {
return typedef, nil
}
return model.CustomMetadataDef{}, NotFoundError{AtlanError{ErrorCode: errorCodes[CM_NOT_FOUND_BY_NAME], Args: []interface{}{name}}}
return model.CustomMetadataDef{}, ThrowAtlanError(nil, CM_NOT_FOUND_BY_NAME, nil, name)

}

Expand All @@ -393,14 +393,14 @@ GetAttributeDef Retrieve a specific custom metadata attribute definition by its
*/
func (c *CustomMetadataCache) GetAttributeDef(attrID string) (model.AttributeDef, error) {
if attrID == "" {
return model.AttributeDef{}, NotFoundError{AtlanError{ErrorCode: errorCodes[MISSING_CM_ATTR_ID]}}
return model.AttributeDef{}, ThrowAtlanError(nil, MISSING_CM_ATTR_ID, nil, attrID)
}
if c.AttrCacheByID == nil {
c.RefreshCache()
}
if attrDef, ok := c.AttrCacheByID[attrID]; ok {
return attrDef, nil
}
return model.AttributeDef{}, NotFoundError{AtlanError{ErrorCode: errorCodes[CM_ATTR_NOT_FOUND_BY_ID], Args: []interface{}{attrID}}}
return model.AttributeDef{}, ThrowAtlanError(nil, CM_ATTR_NOT_FOUND_BY_ID, nil, attrID)

}
53 changes: 43 additions & 10 deletions atlan/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const (
RETRIES_INTERRUPTED
RETRY_OVERRUN
TYPEDEF_NOT_FOUND_BY_NAME
UNMARSHALLING_ERROR
)

var errorCodes = map[ErrorCode]ErrorInfo{
Expand Down Expand Up @@ -403,7 +404,7 @@ var errorCodes = map[ErrorCode]ErrorInfo{
AUTHENTICATION_PASSTHROUGH: {
HTTPErrorCode: 401,
ErrorID: "ATLAN-GO-401-000",
ErrorMessage: "Server responded with %s: %s",
ErrorMessage: "Server responded with authentication error",
UserAction: "Check the details of the server's message to correct your request.",
},
NO_API_TOKEN: {
Expand All @@ -430,6 +431,12 @@ var errorCodes = map[ErrorCode]ErrorInfo{
ErrorMessage: "Your API token is no longer valid, it can no longer lookup base Atlan structures.",
UserAction: "You can double-check your API token from the Atlan Admin Center. See https://ask.atlan.com/hc/en-us/articles/8312649180049 for details or contact support at https://ask.atlan.com/hc/en-us/requests/new if you have any questions.",
},
UNMARSHALLING_ERROR: {
HTTPErrorCode: 401,
ErrorID: "ATLAN-GO-401-006",
ErrorMessage: "Failed to unmarshal response into json structure",
UserAction: "Please raise an issue on the Go SDK GitHub repository providing context in which this error occurred.",
},
PERMISSION_PASSTHROUGH: {
HTTPErrorCode: 403,
ErrorID: "ATLAN-GO-403-000",
Expand Down Expand Up @@ -678,27 +685,53 @@ var errorCodes = map[ErrorCode]ErrorInfo{
},
}

func handleApiError(response *http.Response, originalError string) error {
func handleApiError(response *http.Response, originalError error) error {
if response == nil {
return ApiConnectionError{AtlanError{ErrorCode: errorCodes[CONNECTION_ERROR]}}
return ThrowAtlanError(originalError, CONNECTION_ERROR, nil)
}
rc := response.StatusCode

switch rc {
case 400:
return InvalidRequestError{AtlanError{ErrorCode: errorCodes[INVALID_REQUEST_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, INVALID_REQUEST_PASSTHROUGH, nil)
case 404:
return NotFoundError{AtlanError{ErrorCode: errorCodes[NOT_FOUND_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, NOT_FOUND_PASSTHROUGH, nil)
case 401:
return AuthenticationError{AtlanError{ErrorCode: errorCodes[AUTHENTICATION_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, AUTHENTICATION_PASSTHROUGH, nil)
case 403:
return PermissionError{AtlanError{ErrorCode: errorCodes[PERMISSION_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, PERMISSION_PASSTHROUGH, nil)
case 409:
return ConflictError{AtlanError{ErrorCode: errorCodes[CONFLICT_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, CONFLICT_PASSTHROUGH, nil)
case 429:
return RateLimitError{AtlanError{ErrorCode: errorCodes[RATE_LIMIT_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, RATE_LIMIT_PASSTHROUGH, nil)
default:
return ApiError{AtlanError{ErrorCode: errorCodes[ERROR_PASSTHROUGH], OriginalError: originalError}}
return ThrowAtlanError(originalError, ERROR_PASSTHROUGH, nil)
}
return nil
}

func ThrowAtlanError(err error, sdkError ErrorCode, suggestion *string, args ...interface{}) *AtlanError {
atlanError := AtlanError{
ErrorCode: errorCodes[sdkError],
}

if err != nil {
atlanError.OriginalError = err.Error()
}

if args != nil {
atlanError.Args = args
}

if suggestion != nil {
atlanError.ErrorCode.UserAction = *suggestion
}

if len(args) != 0 {
atlanError.ErrorCode.ErrorMessage = fmt.Sprintf(
atlanError.ErrorCode.ErrorMessage, atlanError.Args...,
)
}

return &atlanError
}
3 changes: 1 addition & 2 deletions atlan/client/fluent_search.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package client

import (
"fmt"
"github.com/atlanhq/atlan-go/atlan"
"github.com/atlanhq/atlan-go/atlan/model"
)
Expand Down Expand Up @@ -150,7 +149,7 @@ func (fs *FluentSearch) Execute() ([]*model.IndexSearchResponse, error) {
{
response, err := iterator.NextPage()
if err != nil {
fmt.Printf("Error executing search: %v\n", err)
//fmt.Printf("Error executing search: %v\n", err)
return nil, err
}

Expand Down
4 changes: 2 additions & 2 deletions atlan/client/index_search_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ func Search(request model.IndexSearchRequest) (*model.IndexSearchResponse, error
// Call the API
responseBytes, err := DefaultAtlanClient.CallAPI(api, nil, &request)
if err != nil {
return nil, fmt.Errorf("error calling API: %v", err)
return nil, err
}

// Unmarshal the response
var response model.IndexSearchResponse
err = json.Unmarshal(responseBytes, &response)
if err != nil {
return nil, fmt.Errorf("error unmarshaling response: %v", err)
return nil, err
}

return &response, nil
Expand Down

0 comments on commit cd7e289

Please sign in to comment.