diff --git a/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py b/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py index 45716768f..910951c8d 100644 --- a/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +++ b/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py @@ -30,8 +30,8 @@ ), 400: ErrorResolution( response_action=ResponseAction.FAIL, - failure_type=FailureType.system_error, - error_message="HTTP Status Code: 400. Error: Bad request. Please check your request parameters.", + failure_type=FailureType.config_error, + error_message=None, ), 401: ErrorResolution( response_action=ResponseAction.FAIL, diff --git a/airbyte_cdk/sources/streams/http/http_client.py b/airbyte_cdk/sources/streams/http/http_client.py index 3a0a62739..208f7814e 100644 --- a/airbyte_cdk/sources/streams/http/http_client.py +++ b/airbyte_cdk/sources/streams/http/http_client.py @@ -466,21 +466,34 @@ def _handle_error_resolution( if error_resolution.response_action == ResponseAction.FAIL: if response is not None: + parsed_api_error = self._error_message_parser.parse_response_error_message(response) filtered_response_message = filter_secrets( f"Request (body): '{str(request.body)}'. Response (body): '{self._get_response_body(response)}'. Response (headers): '{response.headers}'." ) - error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{self._error_message_parser.parse_response_error_message(response)}'. {filtered_response_message}" + error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{parsed_api_error}'. {filtered_response_message}" + + # Build user-facing message: prefer the error handler's custom message, + # fall back to a concise message with the parsed API error body + if error_resolution.error_message: + user_facing_message = error_resolution.error_message + elif parsed_api_error: + user_facing_message = filter_secrets( + f"API responded with HTTP {response.status_code}: {parsed_api_error}" + ) + else: + user_facing_message = f"API responded with HTTP {response.status_code}." else: error_message = ( f"'{request.method}' request to '{request.url}' failed with exception: '{exc}'" ) + user_facing_message = error_resolution.error_message or error_message # ensure the exception message is emitted before raised self._logger.error(error_message) raise AirbyteTracedException( internal_message=error_message, - message=error_resolution.error_message or error_message, + message=user_facing_message, failure_type=error_resolution.failure_type, ) diff --git a/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py b/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py index bf4e1e321..0f5e73541 100644 --- a/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +++ b/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py @@ -91,8 +91,8 @@ def test_default_error_handler_with_default_response_filter( parameters={}, ), ResponseAction.RETRY, - FailureType.system_error, - "HTTP Status Code: 400. Error: Bad request. Please check your request parameters.", + FailureType.config_error, + None, ), ( "_with_http_response_status_402_fail_with_default_failure_type", diff --git a/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py b/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py index 04e232083..2b881fcd6 100644 --- a/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py +++ b/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py @@ -370,6 +370,6 @@ def test_resumable_full_refresh_failure(self, http_mocker): page=2 ) - assert actual_messages.errors[0].trace.error.failure_type == FailureType.system_error + assert actual_messages.errors[0].trace.error.failure_type == FailureType.config_error assert actual_messages.errors[0].trace.error.stream_descriptor.name == "justice_songs" - assert "Bad request" in actual_messages.errors[0].trace.error.message + assert "400" in actual_messages.errors[0].trace.error.message