mirror of https://github.com/nucypher/nucypher.git
Add generic catch-all field for any data received from JSON (`taco-web`).
We need to account for the case when `taco-web` provides large numbers (bigints) as strings. Add tests.pull/3585/head
parent
1cb40795d0
commit
1b13c50b02
|
@ -33,6 +33,7 @@ from nucypher.policy.conditions.exceptions import (
|
|||
RPCExecutionFailed,
|
||||
)
|
||||
from nucypher.policy.conditions.lingo import (
|
||||
AnyField,
|
||||
ConditionType,
|
||||
ExecutionCallAccessControlCondition,
|
||||
ReturnValueTest,
|
||||
|
@ -71,7 +72,7 @@ class RPCCall(ExecutionCall):
|
|||
"null": "Undefined method name",
|
||||
},
|
||||
)
|
||||
parameters = fields.List(fields.Field, required=False, allow_none=True)
|
||||
parameters = fields.List(AnyField, required=False, allow_none=True)
|
||||
|
||||
@validates("method")
|
||||
def validate_method(self, value):
|
||||
|
|
|
@ -17,6 +17,7 @@ from nucypher.policy.conditions.json.base import (
|
|||
JsonRequestCall,
|
||||
)
|
||||
from nucypher.policy.conditions.lingo import (
|
||||
AnyField,
|
||||
ConditionType,
|
||||
ExecutionCallAccessControlCondition,
|
||||
ReturnValueTest,
|
||||
|
@ -26,7 +27,7 @@ from nucypher.policy.conditions.lingo import (
|
|||
class BaseJsonRPCCall(JsonRequestCall, ABC):
|
||||
class Schema(JsonRequestCall.Schema):
|
||||
method = fields.Str(required=True)
|
||||
params = fields.Field(required=False, allow_none=True)
|
||||
params = AnyField(required=False, allow_none=True)
|
||||
query = JSONPathField(required=False, allow_none=True)
|
||||
authorization_token = fields.Str(required=False, allow_none=True)
|
||||
|
||||
|
|
|
@ -39,6 +39,39 @@ from nucypher.policy.conditions.types import ConditionDict, Lingo
|
|||
from nucypher.policy.conditions.utils import CamelCaseSchema, ConditionProviderManager
|
||||
|
||||
|
||||
class AnyField(fields.Field):
|
||||
"""
|
||||
Catch all field for all data types received in JSON.
|
||||
However, `taco-web` will provide bigints as strings since typescript can't handle large
|
||||
numbers as integers, so those need converting to integers.
|
||||
"""
|
||||
|
||||
def _convert_any_large_integers_from_string(self, value):
|
||||
if isinstance(value, list):
|
||||
return [
|
||||
self._convert_any_large_integers_from_string(item) for item in value
|
||||
]
|
||||
elif isinstance(value, dict):
|
||||
return {
|
||||
k: self._convert_any_large_integers_from_string(v)
|
||||
for k, v in value.items()
|
||||
}
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
result = int(value)
|
||||
return result
|
||||
except ValueError:
|
||||
# ignore
|
||||
pass
|
||||
|
||||
return value
|
||||
|
||||
def _serialize(self, value, attr, obj, **kwargs):
|
||||
return value
|
||||
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
return self._convert_any_large_integers_from_string(value)
|
||||
|
||||
class _ConditionField(fields.Dict):
|
||||
"""Serializes/Deserializes Conditions to/from dictionaries"""
|
||||
|
||||
|
@ -511,7 +544,7 @@ class ReturnValueTest:
|
|||
class ReturnValueTestSchema(CamelCaseSchema):
|
||||
SKIP_VALUES = (None,)
|
||||
comparator = fields.Str(required=True, validate=OneOf(_COMPARATOR_FUNCTIONS))
|
||||
value = fields.Raw(
|
||||
value = AnyField(
|
||||
allow_none=False, required=True
|
||||
) # any valid type (excludes None)
|
||||
index = fields.Int(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
@ -9,7 +10,7 @@ from nucypher.policy.conditions.context import USER_ADDRESS_CONTEXT
|
|||
from nucypher.policy.conditions.exceptions import (
|
||||
InvalidConditionLingo,
|
||||
)
|
||||
from nucypher.policy.conditions.lingo import ConditionLingo, ConditionType
|
||||
from nucypher.policy.conditions.lingo import AnyField, ConditionLingo, ConditionType
|
||||
from tests.constants import TESTERCHAIN_CHAIN_ID
|
||||
|
||||
|
||||
|
@ -376,3 +377,85 @@ def test_lingo_data(conditions_test_data):
|
|||
for name, condition_dict in conditions_test_data.items():
|
||||
condition_class = ConditionLingo.resolve_condition_class(condition_dict)
|
||||
_ = condition_class.from_dict(condition_dict)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
1231323123132,
|
||||
2121.23211,
|
||||
False,
|
||||
'"foo"', # string
|
||||
":userAddress", # context variable
|
||||
"0xaDD9D957170dF6F33982001E4c22eCCdd5539118", # string
|
||||
"0x1234", # hex string
|
||||
125, # int
|
||||
-123456789, # negative int
|
||||
1.223, # float
|
||||
True, # bool
|
||||
[1, 1.2314, False, "love"], # list of different types
|
||||
["a", "b", "c"], # list
|
||||
[True, False], # list of bools
|
||||
{"name": "John", "age": 22}, # dict
|
||||
namedtuple("MyStruct", ["field1", "field2"])(1, "a"),
|
||||
[True, 2, 6.5, "0x123"],
|
||||
],
|
||||
)
|
||||
def test_any_field_various_types(value):
|
||||
field = AnyField()
|
||||
|
||||
deserialized_value = field._deserialize(value, attr=None, data=None)
|
||||
serialized_value = field._serialize(deserialized_value, attr=None, obj=None)
|
||||
|
||||
assert deserialized_value == serialized_value
|
||||
assert deserialized_value == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"integer_value",
|
||||
[
|
||||
2**256 - 1, # uint256 max
|
||||
-(2**255), # int256 min
|
||||
123132312, # safe int
|
||||
-1231231, # safe negative int
|
||||
],
|
||||
)
|
||||
def test_any_field_integer_str_and_no_str_conversion(integer_value):
|
||||
field = AnyField()
|
||||
|
||||
deserialized_raw_integer = field._deserialize(
|
||||
value=integer_value, attr=None, data=None
|
||||
)
|
||||
deserialized_string_integer = field._deserialize(
|
||||
value=str(integer_value), attr=None, data=None
|
||||
)
|
||||
assert deserialized_raw_integer == deserialized_string_integer
|
||||
|
||||
assert (
|
||||
field._serialize(deserialized_raw_integer, attr=None, obj=None) == integer_value
|
||||
)
|
||||
assert (
|
||||
field._serialize(deserialized_string_integer, attr=None, obj=None)
|
||||
== integer_value
|
||||
)
|
||||
|
||||
|
||||
def test_any_field_nested_integer():
|
||||
field = AnyField()
|
||||
|
||||
uint256_max = 2**256 - 1
|
||||
int256_min = -(2**255)
|
||||
regular_number = 12341231
|
||||
|
||||
parameters = [
|
||||
f"{uint256_max}",
|
||||
{"a": [f"{int256_min}", "my_string_value", "0xdeadbeef"], "b": regular_number},
|
||||
]
|
||||
# quoted numbers get unquoted after deserialization
|
||||
expected_parameters = [
|
||||
uint256_max,
|
||||
{"a": [int256_min, "my_string_value", "0xdeadbeef"], "b": regular_number},
|
||||
]
|
||||
|
||||
deserialized_parameters = field._deserialize(value=parameters, attr=None, data=None)
|
||||
assert deserialized_parameters == expected_parameters
|
||||
|
|
Loading…
Reference in New Issue