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
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
@@ -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()


11 changes: 10 additions & 1 deletion aries_cloudagent/core/tests/test_error.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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:
7 changes: 6 additions & 1 deletion aries_cloudagent/protocols/connections/v1_0/routes.py
Original file line number Diff line number Diff line change
@@ -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")

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
@@ -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})
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
@@ -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({})
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
@@ -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)