diff --git a/aries_cloudagent/core/error.py b/aries_cloudagent/core/error.py index 92db5a16cc..585de18a05 100644 --- a/aries_cloudagent/core/error.py +++ b/aries_cloudagent/core/error.py @@ -14,7 +14,7 @@ def __init__(self, *args, error_code: str = None, **kwargs): @property def message(self) -> str: """Accessor for the error message.""" - return self.args[0].strip() if self.args else "" + return str(self.args[0]).strip() if self.args else "" @property def roll_up(self) -> str: @@ -23,10 +23,22 @@ def roll_up(self) -> str: For display: aiohttp.web errors truncate after newline. """ - line = "{}{}".format( - "({}) ".format(self.error_code) if self.error_code else "", - re.sub(r"\n\s*", ". ", self.args[0]) if self.args else "", - ) + + def flatten(exc: Exception): + ret = ".".join( + ( + re.sub(r"\n\s*", ". ", str(exc.args[0]).strip()).strip() + if exc.args + else "" + ).rsplit(".", 1) + ) + return ret + + line = flatten(self) + err = self + while err.__cause__: + err = err.__cause__ + line += ". {}".format(flatten(err)) return line.strip() diff --git a/aries_cloudagent/core/tests/test_error.py b/aries_cloudagent/core/tests/test_error.py index d083743108..c0d50dc13a 100644 --- a/aries_cloudagent/core/tests/test_error.py +++ b/aries_cloudagent/core/tests/test_error.py @@ -17,4 +17,13 @@ async def test_base_error(self): err = BaseError(MESSAGE, error_code=CODE) assert err.error_code == CODE assert err.message == MESSAGE.strip() - assert err.roll_up == "(-1) Not enough space. Clear 10MB." + assert err.roll_up == "Not enough space. Clear 10MB" + + iox = IOError("hello") + keyx = KeyError("world") + osx = OSError("oh\nno") + osx.__cause__ = keyx + iox.__cause__ = osx + err.__cause__ = iox + + assert err.roll_up == "Not enough space. Clear 10MB. hello. oh. no. world" diff --git a/aries_cloudagent/protocols/actionmenu/v1_0/routes.py b/aries_cloudagent/protocols/actionmenu/v1_0/routes.py index c7882373fa..9a6b028d82 100644 --- a/aries_cloudagent/protocols/actionmenu/v1_0/routes.py +++ b/aries_cloudagent/protocols/actionmenu/v1_0/routes.py @@ -8,6 +8,7 @@ from marshmallow import fields, Schema from aries_cloudagent.connections.models.connection_record import ConnectionRecord +from aries_cloudagent.messaging.models.base import BaseModelError from aries_cloudagent.messaging.valid import UUIDFour from aries_cloudagent.storage.error import StorageNotFoundError @@ -185,8 +186,8 @@ async def actionmenu_send(request: web.BaseRequest): LOGGER.debug("Received send-menu request: %s %s", connection_id, menu_json) try: msg = Menu.deserialize(menu_json["menu"]) - except Exception: - LOGGER.exception("Exception deserializing menu") + except BaseModelError as err: + LOGGER.exception("Exception deserializing menu: %s", err.roll_up) raise try: diff --git a/aries_cloudagent/protocols/connections/v1_0/routes.py b/aries_cloudagent/protocols/connections/v1_0/routes.py index d58ace2889..22c7bffc82 100644 --- a/aries_cloudagent/protocols/connections/v1_0/routes.py +++ b/aries_cloudagent/protocols/connections/v1_0/routes.py @@ -17,6 +17,7 @@ ConnectionRecord, ConnectionRecordSchema, ) +from aries_cloudagent.messaging.models.base import BaseModelError from aries_cloudagent.messaging.valid import ( ENDPOINT, INDY_DID, @@ -334,7 +335,11 @@ async def connections_receive_invitation(request: web.BaseRequest): raise web.HTTPForbidden() connection_mgr = ConnectionManager(context) invitation_json = await request.json() - invitation = ConnectionInvitation.deserialize(invitation_json) + try: + invitation = ConnectionInvitation.deserialize(invitation_json) + except BaseModelError as x: + raise web.HTTPBadRequest(reason=x.roll_up) + auto_accept = json.loads(request.query.get("auto_accept", "null")) alias = request.query.get("alias") diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py index a2c7798e41..654e4660a0 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py @@ -200,6 +200,31 @@ async def test_connections_receive_invitation(self): await test_module.connections_receive_invitation(mock_req) mock_response.assert_called_once_with(mock_conn_rec.serialize.return_value) + async def test_connections_receive_invitation_bad(self): + context = RequestContext(base_context=InjectionContext(enforce_typing=False)) + mock_req = async_mock.MagicMock() + mock_req.app = { + "request_context": context, + } + mock_req.json = async_mock.CoroutineMock() + mock_req.query = { + "auto_accept": "true", + "alias": "alias", + } + + mock_conn_rec = async_mock.MagicMock() + mock_conn_rec.serialize = async_mock.MagicMock() + + with async_mock.patch.object( + test_module.ConnectionInvitation, "deserialize", autospec=True + ) as mock_inv_deser, async_mock.patch.object( + test_module, "ConnectionManager", autospec=True + ) as mock_conn_mgr: + mock_inv_deser.side_effect = test_module.BaseModelError() + + with self.assertRaises(test_module.web.HTTPBadRequest): + await test_module.connections_receive_invitation(mock_req) + async def test_connections_receive_invitation_forbidden(self): context = RequestContext(base_context=InjectionContext(enforce_typing=False)) context.update_settings({"admin.no_receive_invites": True}) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index 7e450cc534..fba4063406 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -16,6 +16,7 @@ from ....connections.models.connection_record import ConnectionRecord from ....holder.base import BaseHolder from ....messaging.decorators.attach_decorator import AttachDecorator +from ....messaging.models.base import BaseModelError from ....messaging.valid import ( INDY_CRED_DEF_ID, INDY_DID, @@ -374,8 +375,8 @@ async def presentation_exchange_retrieve(request: web.BaseRequest): record = await V10PresentationExchange.retrieve_by_id( context, presentation_exchange_id ) - except StorageNotFoundError: - raise web.HTTPNotFound() + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) return web.json_response(record.serialize()) @@ -408,8 +409,8 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): presentation_exchange_record = await V10PresentationExchange.retrieve_by_id( context, presentation_exchange_id ) - except StorageNotFoundError: - raise web.HTTPNotFound() + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) start = request.query.get("start") count = request.query.get("count") @@ -464,25 +465,25 @@ async def presentation_exchange_send_proposal(request: web.BaseRequest): outbound_handler = request.app["outbound_message_router"] body = await request.json() - + comment = body.get("comment") connection_id = body.get("connection_id") + + # Aries#0037 calls it a proposal in the proposal struct but it's of type preview + presentation_preview = body.get("presentation_proposal") try: connection_record = await ConnectionRecord.retrieve_by_id( context, connection_id ) - except StorageNotFoundError: - raise web.HTTPBadRequest() + presentation_proposal_message = PresentationProposal( + comment=comment, + presentation_proposal=PresentationPreview.deserialize(presentation_preview), + ) + except (BaseModelError, StorageNotFoundError) as err: + raise web.HTTPBadRequest(reason=err.roll_up) if not connection_record.is_ready: - raise web.HTTPForbidden() + raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready") - comment = body.get("comment") - # Aries#0037 calls it a proposal in the proposal struct but it's of type preview - presentation_preview = body.get("presentation_proposal") - presentation_proposal_message = PresentationProposal( - comment=comment, - presentation_proposal=PresentationPreview.deserialize(presentation_preview), - ) trace_msg = body.get("trace") presentation_proposal_message.assign_trace_decorator( context.settings, trace_msg, @@ -610,11 +611,11 @@ async def presentation_exchange_send_free_request(request: web.BaseRequest): connection_record = await ConnectionRecord.retrieve_by_id( context, connection_id ) - except StorageNotFoundError: - raise web.HTTPBadRequest() + except StorageNotFoundError as err: + raise web.HTTPBadRequest(reason=err.roll_up) if not connection_record.is_ready: - raise web.HTTPForbidden() + raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready") comment = body.get("comment") indy_proof_request = body.get("proof_request") @@ -693,11 +694,11 @@ async def presentation_exchange_send_bound_request(request: web.BaseRequest): connection_record = await ConnectionRecord.retrieve_by_id( context, connection_id ) - except StorageNotFoundError: - raise web.HTTPBadRequest() + except StorageNotFoundError as err: + raise web.HTTPBadRequest(reason=err.roll_up) if not connection_record.is_ready: - raise web.HTTPForbidden() + raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready") presentation_manager = PresentationManager(context) @@ -753,11 +754,11 @@ async def presentation_exchange_send_presentation(request: web.BaseRequest): connection_record = await ConnectionRecord.retrieve_by_id( context, connection_id ) - except StorageNotFoundError: - raise web.HTTPBadRequest() + except StorageNotFoundError as err: + raise web.HTTPBadRequest(reason=err.roll_up) if not connection_record.is_ready: - raise web.HTTPForbidden() + raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready") assert ( presentation_exchange_record.state @@ -822,11 +823,11 @@ async def presentation_exchange_verify_presentation(request: web.BaseRequest): connection_record = await ConnectionRecord.retrieve_by_id( context, connection_id ) - except StorageNotFoundError: - raise web.HTTPBadRequest() + except StorageNotFoundError as err: + raise web.HTTPBadRequest(reason=err.roll_up) if not connection_record.is_ready: - raise web.HTTPForbidden() + raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready") assert ( presentation_exchange_record.state @@ -864,8 +865,8 @@ async def presentation_exchange_remove(request: web.BaseRequest): presentation_exchange_record = await V10PresentationExchange.retrieve_by_id( context, presentation_exchange_id ) - except StorageNotFoundError: - raise web.HTTPNotFound() + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) await presentation_exchange_record.delete_record(context) return web.json_response({}) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index bf63b3a334..be6a7e9313 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -216,22 +216,13 @@ async def test_presentation_exchange_send_proposal_no_conn_record(self): with async_mock.patch.object( test_module, "ConnectionRecord", autospec=True - ) as mock_connection_record, async_mock.patch.object( - test_module, "PresentationManager", autospec=True - ) as mock_presentation_manager: + ) as mock_connection_record: # Emulate storage not found (bad connection id) mock_connection_record.retrieve_by_id = async_mock.CoroutineMock( side_effect=StorageNotFoundError ) - mock_presentation_manager.return_value.create_exchange_for_proposal = ( - async_mock.CoroutineMock() - ) - mock_presentation_manager.return_value.create_exchange_for_proposal.return_value = ( - async_mock.MagicMock() - ) - with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.presentation_exchange_send_proposal(mock) @@ -247,20 +238,15 @@ async def test_presentation_exchange_send_proposal_not_ready(self): with async_mock.patch.object( test_module, "ConnectionRecord", autospec=True ) as mock_connection_record, async_mock.patch.object( - test_module, "PresentationManager", autospec=True - ) as mock_presentation_manager: + test_module, "PresentationPreview", autospec=True + ) as mock_preview, async_mock.patch.object( + test_module, "PresentationProposal", autospec=True + ) as mock_proposal: + mock_preview.deserialize = async_mock.CoroutineMock() - # Emulate connection not ready mock_connection_record.retrieve_by_id = async_mock.CoroutineMock() mock_connection_record.retrieve_by_id.return_value.is_ready = False - mock_presentation_manager.return_value.create_exchange_for_proposal = ( - async_mock.CoroutineMock() - ) - mock_presentation_manager.return_value.create_exchange_for_proposal.return_value = ( - async_mock.MagicMock() - ) - with self.assertRaises(test_module.web.HTTPForbidden): await test_module.presentation_exchange_send_proposal(mock)