"""Format error responses.""" from switchboard_consumer.connectors.sentry import sentry_client from switchboard_consumer.constants import entity_type from switchboard_consumer.constants.error import ( ERROR_CODE_TRACK_PROCESSING, ERROR_MESSAGE_TRACK_INSERT_FAILED, ERROR_MESSAGE_TRACK_INSERT_FAILED_PARSING, ERROR_MESSAGE_TRACK_INSERT_FAILED_WITH_REASON, ERROR_MESSAGE_TRACK_INSERT_RAW_ERROR, UNKNOWN_ERROR_CODE, UNKNOWN_ERROR_MESSAGE ) from switchboard_consumer.utils.common import flatten_json from switchboard_consumer.utils.message import create_processing_result def format_graphql_errors(errors): """Parse graphql errors into swb acceptable format.""" formatted_errors = [] for error in errors: formatted_error = format_graphql_error(error) formatted_errors.append(formatted_error) return formatted_errors def get_microservice_error_details(error): """Try and get microservice error code and error message.""" error_code = None error_message = None try: body = error['extensions']['response']['body'] try: error_code = body['code'] except (KeyError, TypeError): pass try: error_message = body['message'] except (KeyError, TypeError): response = error['extensions']['response'] status = str(response.get('status', '')) status_text = response.get('statusText', '') url = response.get('url', '') error_message = ' '.join([status, status_text, url]).strip() except KeyError: pass return error_code, error_message def format_graphql_error(error): """Parse graphql error into SWB format.""" extensions = error.get('extensions', {}) # Get error code and message from graphql payload. code = extensions.get('code', UNKNOWN_ERROR_CODE) message = error.get('message', UNKNOWN_ERROR_MESSAGE) # Try and get a specific error from the microservice response body. service_code, service_message = get_microservice_error_details(error) if service_code: code = service_code if service_message: message = service_message return { 'code': code, 'message': str(message) } def format_generic_error(message, code='error'): """Format a message into an acceptable error format.""" return { 'code': code, 'message': message } def format_validation_error(validation_result): """Format a message into an acceptable error format.""" return [ { 'code': 'validation', 'message': ','.join(validation_result['messages']) } ] def format_exception(exception): """Parse exceptions into swb acceptable format.""" return { 'code': 'daemon_exception', 'message': '{exception_type}: {exception}'.format( exception_type=type(exception).__name__, exception=exception ) } def format_create_track_errors(tracks, kafka_message, errors, mapping, logger): """Format errors in create track to processing results.""" error_processing_result = [] for error in errors: if _is_bulk_track_insert_error(error): return format_bulk_create_track_error( tracks, kafka_message, error, mapping, logger ) error_processing_result.append( format_partial_track_error( tracks, kafka_message, error, mapping, logger ) ) return error_processing_result def format_partial_track_error(tracks, kafka_message, error, mapping, logger): """Format a partial track error into a processing result.""" track = tracks[error['path'][-1]] error_message = parse_graphql_error(error) if not error_message: # An error was not found in the graphql body error_message = ERROR_MESSAGE_TRACK_INSERT_FAILED_PARSING if sentry_client: sentry_client.captureMessage(error_message, stack=True) return _format_track_processing_result( track, mapping.get(track['isrc']), kafka_message, [format_generic_error( ERROR_MESSAGE_TRACK_INSERT_RAW_ERROR.format(str(error_message)), ERROR_CODE_TRACK_PROCESSING )] ) def format_bulk_create_track_error(tracks, kafka_message, error, mapping, logger): """Format bulk create track error into processing results.""" error_processing_results = [] track_error = parse_graphql_error(error, 'tracks') if track_error == UNKNOWN_ERROR_MESSAGE: # We failed to parse any errors out of graphql error_message = 'Failed to parse track errors from graphql response.' logger.error(error_message) if sentry_client: sentry_client.captureMessage(error_message, stack=True) formatted_errors = [ format_generic_error( ERROR_MESSAGE_TRACK_INSERT_RAW_ERROR.format( ERROR_MESSAGE_TRACK_INSERT_FAILED_PARSING), ERROR_CODE_TRACK_PROCESSING) ] for track in tracks: error_processing_results.append( _format_track_processing_result( track, mapping.get(track['isrc']), kafka_message, formatted_errors ) ) return error_processing_results for index, track in enumerate(tracks): # Look for an associated track error in the graphql errors block formatted_errors = [] associated_error = track_error.get(str(index)) if associated_error: for key, value in flatten_json(associated_error).items(): formatted_errors.append( format_generic_error( ERROR_MESSAGE_TRACK_INSERT_FAILED_WITH_REASON.format( key, value ), ERROR_CODE_TRACK_PROCESSING )) else: # No associated error in the graphql response formatted_errors.append(format_generic_error( ERROR_MESSAGE_TRACK_INSERT_FAILED, ERROR_CODE_TRACK_PROCESSING )) error_processing_results.append( _format_track_processing_result( track, mapping.get(track['isrc']), kafka_message, formatted_errors )) return error_processing_results def _format_track_processing_result(track, source_id_mapping, kafka_message, errors): source_id_mapping.update({ 'businessKey': track['isrc'], 'businessKeyType': 'ISRC' }) return create_processing_result( [source_id_mapping], kafka_message, entity_type=entity_type.TRACK, errors=errors ) def parse_graphql_error(error, key=None): """Parse GQL error.""" error_message = UNKNOWN_ERROR_MESSAGE extensions = error.get('extensions', {}) if extensions: body = extensions.get('response', {}).get('body', {}) if key: try: error_message = body.get( 'message', {}).get( key, UNKNOWN_ERROR_MESSAGE ) except AttributeError: if isinstance(body, str): return body else: error_message = body.get('message') return error_message def _is_bulk_track_insert_error(error): """Check if we've hit a bulk track insert error.""" return len(error['path']) == 1