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

40 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-07 19:28 +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 if isinstance(val, (str, bytes, bytearray)): 

50 return utils.json.loads(val) 

51 return val 

52 

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

54 if value: 

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

56 value = str(value) 

57 

58 # Try to parse a JSON string 

59 try: 

60 value = utils.json.loads(value) 

61 

62 except json.decoder.JSONDecodeError as e: 

63 # Try to parse a Python dict as fallback 

64 try: 

65 value = ast.literal_eval(value) 

66 

67 except (SyntaxError, ValueError): 

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

69 return self.getEmptyValue(), [ 

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

71 ] 

72 

73 try: 

74 jsonschema.validate(value, self.schema) 

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

76 return self.getEmptyValue(), [ 

77 ReadFromClientError( 

78 ReadFromClientErrorSeverity.Invalid, 

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

80 ] 

81 

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

83 

84 def structure(self) -> dict: 

85 return super().structure() | { 

86 "schema": self.schema 

87 }