Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

include root cause chain in error roll-up; integrate with deserializa… #534

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions aries_cloudagent/core/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()


Expand Down
11 changes: 10 additions & 1 deletion aries_cloudagent/core/tests/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
5 changes: 3 additions & 2 deletions aries_cloudagent/protocols/actionmenu/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion aries_cloudagent/protocols/connections/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ConnectionRecord,
ConnectionRecordSchema,
)
from aries_cloudagent.messaging.models.base import BaseModelError
from aries_cloudagent.messaging.valid import (
ENDPOINT,
INDY_DID,
Expand Down Expand Up @@ -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")

Expand Down
25 changes: 25 additions & 0 deletions aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
59 changes: 30 additions & 29 deletions aries_cloudagent/protocols/present_proof/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())


Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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({})
Expand Down
26 changes: 6 additions & 20 deletions aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down