error handling and logging for offchain condition evaluations

pull/3554/head
KPrasch 2024-06-20 17:28:21 +08:00 committed by derekpierre
parent 0fe3a9b5f1
commit ecc8b16602
No known key found for this signature in database
2 changed files with 56 additions and 22 deletions

View File

@ -7,9 +7,13 @@ from marshmallow import ValidationError, fields, post_load, validate
from marshmallow.fields import Field
from nucypher.policy.conditions.base import AccessControlCondition
from nucypher.policy.conditions.exceptions import InvalidCondition
from nucypher.policy.conditions.exceptions import (
ConditionEvaluationFailed,
InvalidCondition,
)
from nucypher.policy.conditions.lingo import ConditionType, ReturnValueTest
from nucypher.policy.conditions.utils import CamelCaseSchema
from nucypher.utilities.logging import Logger
class JSONPathField(Field):
@ -33,6 +37,7 @@ class OffchainCondition(AccessControlCondition):
"""
CONDITION_TYPE = ConditionType.OFFCHAIN.value
LOGGER = Logger("nucypher.policy.conditions.offchain")
class Schema(CamelCaseSchema):
@ -62,8 +67,6 @@ class OffchainCondition(AccessControlCondition):
parameters: Optional[dict] = None,
condition_type: str = ConditionType.OFFCHAIN.value,
):
# internal
if condition_type != self.CONDITION_TYPE:
raise InvalidCondition(
f"{self.__class__.__name__} must be instantiated with the {self.CONDITION_TYPE} type."
@ -74,33 +77,61 @@ class OffchainCondition(AccessControlCondition):
self.parameters = parameters
self.query = query
self.return_value_test = return_value_test
self.logger = self.LOGGER
def fetch(self):
def fetch(self) -> requests.Response:
"""Fetches data from the endpoint."""
try:
response = requests.get(
self.endpoint, params=self.parameters, headers=self.headers
)
except requests.exceptions.RequestException as e:
raise InvalidCondition(f"Failed to fetch endpoint {self.endpoint}: {e}")
response.raise_for_status()
except requests.exceptions.HTTPError as http_error:
self.logger.error(f"HTTP error occurred: {http_error}")
raise ConditionEvaluationFailed(
f"Failed to fetch endpoint {self.endpoint}: {http_error}"
)
except requests.exceptions.RequestException as request_error:
self.logger.error(f"Request exception occurred: {request_error}")
raise InvalidCondition(
f"Failed to fetch endpoint {self.endpoint}: {request_error}"
)
return response
def deserialize_response(self, response: requests.Response) -> Any:
"""Deserializes the JSON response from the endpoint."""
try:
data = response.json()
except requests.exceptions.RequestException as json_error:
self.logger.error(f"JSON parsing error occurred: {json_error}")
raise ConditionEvaluationFailed(
f"Failed to parse JSON response: {json_error}"
)
return data
def query_response(self, data: Any) -> Any:
try:
expression = parse(self.query)
matches = expression.find(data)
if not matches:
self.logger.info("No matches found for the JSONPath query.")
raise ConditionEvaluationFailed(
"No matches found for the JSONPath query."
)
result = matches[0].value
except (JsonPathLexerError, JsonPathParserError) as jsonpath_err:
self.logger.error(f"JSONPath error occurred: {jsonpath_err}")
raise ConditionEvaluationFailed(f"JSONPath error: {jsonpath_err}")
return result
def verify(self, **context) -> Tuple[bool, Any]:
"""
Verifies the offchain condition is met by performing a read operation on the endpoint
and evaluating the return value test with the result. Parses the endpoint's JSON response using
jsonpath.
and evaluating the return value test with the result. Parses the endpoint's JSON response using
JSONPath.
"""
response = self.fetch()
try:
data = response.json()
except requests.exceptions.RequestException as e:
raise InvalidCondition(f"Failed to parse JSON response: {e}")
expression = parse(self.query)
# TODO: Uses the first match, is it beneficial to support multiple matches?
result = expression.find(data)[0].value
data = self.deserialize_response(response)
result = self.query_response(data)
return self.return_value_test.eval(result), result

View File

@ -2,7 +2,10 @@ import pytest
import requests
from marshmallow import ValidationError
from nucypher.policy.conditions.exceptions import InvalidCondition
from nucypher.policy.conditions.exceptions import (
ConditionEvaluationFailed,
InvalidCondition,
)
from nucypher.policy.conditions.lingo import ReturnValueTest
from nucypher.policy.conditions.offchain import JSONPathField, OffchainCondition
@ -149,6 +152,6 @@ def test_offchain_condition_verify_invalid_json(mocker):
query="$.store.book[0].price",
return_value_test=ReturnValueTest("==", "2"),
)
with pytest.raises(InvalidCondition) as excinfo:
with pytest.raises(ConditionEvaluationFailed) as excinfo:
condition.verify()
assert "Failed to parse JSON response" in str(excinfo.value)