diff --git a/atlan/client/client.go b/atlan/client/client.go index 8ab0e11..0d6c44c 100644 --- a/atlan/client/client.go +++ b/atlan/client/client.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/atlanhq/atlan-go/atlan/logger" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -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) @@ -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) @@ -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. @@ -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") } @@ -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) { diff --git a/atlan/client/custom_metadata_cache.go b/atlan/client/custom_metadata_cache.go index e28c61a..faf9d0a 100644 --- a/atlan/client/custom_metadata_cache.go +++ b/atlan/client/custom_metadata_cache.go @@ -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 @@ -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() @@ -162,7 +162,7 @@ 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() @@ -170,7 +170,7 @@ func (c *CustomMetadataCache) GetIDForName(name string) (string, error) { 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) } } @@ -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() @@ -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) } } @@ -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 { @@ -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) } /* @@ -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 { @@ -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) } @@ -393,7 +393,7 @@ 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() @@ -401,6 +401,6 @@ func (c *CustomMetadataCache) GetAttributeDef(attrID string) (model.AttributeDef 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) } diff --git a/atlan/client/errors.go b/atlan/client/errors.go index 4475b21..78ce378 100644 --- a/atlan/client/errors.go +++ b/atlan/client/errors.go @@ -133,6 +133,7 @@ const ( RETRIES_INTERRUPTED RETRY_OVERRUN TYPEDEF_NOT_FOUND_BY_NAME + UNMARSHALLING_ERROR ) var errorCodes = map[ErrorCode]ErrorInfo{ @@ -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: { @@ -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", @@ -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 +} diff --git a/atlan/client/fluent_search.go b/atlan/client/fluent_search.go index e9ec2ea..805f23b 100644 --- a/atlan/client/fluent_search.go +++ b/atlan/client/fluent_search.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "github.com/atlanhq/atlan-go/atlan" "github.com/atlanhq/atlan-go/atlan/model" ) @@ -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 } diff --git a/atlan/client/index_search_client.go b/atlan/client/index_search_client.go index 4af7fd6..13f2973 100644 --- a/atlan/client/index_search_client.go +++ b/atlan/client/index_search_client.go @@ -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