Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/json.py: 33%

38 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +0000

1import ast 

2import json 

3import jsonschema 

4import typing as t 

5from viur.core.bones.base import ReadFromClientError, ReadFromClientErrorSeverity 

6from viur.core.bones.raw import RawBone 

7from viur.core import utils 

8 

9 

10class JsonBone(RawBone): 

11 """ 

12 This bone saves its content as a JSON-string, but unpacks its content to a dict or list when used. 

13 :param schema If provided we can control and verify which data to accept. 

14 

15 .. code-block:: python 

16 

17 # Example 

18 schema= {"type": "object", "properties" :{"price": {"type": "number"},"name": {"type": "string"}} 

19 # This will only accept the provided JSON when price is a number and name is a string. 

20 

21 """ 

22 

23 type = "raw.json" 

24 

25 def __init__( 

26 self, 

27 *, 

28 indexed: bool = False, 

29 multiple: bool = False, 

30 languages: bool = None, 

31 schema: t.Mapping = {}, 

32 **kwargs 

33 ): 

34 # JsonBone is bound to certain limits 

35 assert not multiple 

36 assert not languages 

37 assert not indexed 

38 

39 super().__init__(indexed=indexed, multiple=multiple, languages=languages, **kwargs) 

40 

41 # Validate the schema; if it's invalid a SchemaError will be raised 

42 jsonschema.validators.validator_for(False).check_schema(schema) 

43 self.schema = schema 

44 

45 def singleValueSerialize(self, value, skel: 'SkeletonInstance', name: str, parentIndexed: bool): 

46 return utils.json.dumps(skel.accessedValues[name]) 

47 

48 def singleValueUnserialize(self, val): 

49 return utils.json.loads(val) 

50 

51 def singleValueFromClient(self, value: str | list | dict, skel, bone_name, client_data): 

52 if value: 

53 if not isinstance(value, (list, dict)): 

54 value = str(value) 

55 

56 # Try to parse a JSON string 

57 try: 

58 value = utils.json.loads(value) 

59 

60 except json.decoder.JSONDecodeError as e: 

61 # Try to parse a Python dict as fallback 

62 try: 

63 value = ast.literal_eval(value) 

64 

65 except (SyntaxError, ValueError): 

66 # If this fails, report back the JSON parse error 

67 return self.getEmptyValue(), [ 

68 ReadFromClientError(ReadFromClientErrorSeverity.Invalid, f"Invalid JSON supplied: {e!s}") 

69 ] 

70 

71 try: 

72 jsonschema.validate(value, self.schema) 

73 except (jsonschema.exceptions.ValidationError, jsonschema.exceptions.SchemaError) as e: 

74 return self.getEmptyValue(), [ 

75 ReadFromClientError( 

76 ReadFromClientErrorSeverity.Invalid, 

77 f"Invalid JSON for schema supplied: {e!s}") 

78 ] 

79 

80 return super().singleValueFromClient(value, skel, bone_name, client_data) 

81 

82 def structure(self) -> dict: 

83 return super().structure() | { 

84 "schema": self.schema 

85 }