Coverage for jsonschema_diff/core/tools/render.py: 100%

29 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-25 07:00 +0000

1from typing import TYPE_CHECKING, Any, List, Sequence, TypeAlias 

2 

3if TYPE_CHECKING: 

4 from ..abstraction import Statuses 

5 from ..config import Config 

6 

7PATH_MAKER_IGNORE_RULES_TYPE: TypeAlias = Sequence[str] 

8 

9 

10class RenderTool: 

11 """ 

12 Small helper utilities used by the rendering subsystem. 

13 """ 

14 

15 # --------------------------------------------------------------------- # 

16 # Basic helpers 

17 # --------------------------------------------------------------------- # 

18 

19 @staticmethod 

20 def make_tab(config: "Config", tab_level: int) -> str: 

21 """ 

22 Return indentation string. 

23 

24 Parameters 

25 ---------- 

26 config : Config 

27 Application config that owns the ``TAB`` constant. 

28 tab_level : int 

29 Indentation depth. 

30 

31 Returns 

32 ------- 

33 str 

34 ``config.TAB`` repeated *tab_level* times. 

35 """ 

36 return config.TAB * tab_level 

37 

38 @staticmethod 

39 def make_prefix(status: "Statuses") -> str: 

40 """ 

41 Convert a ``Statuses`` enum value to its printable form. 

42 

43 Parameters 

44 ---------- 

45 status : Statuses 

46 Validation status. 

47 

48 Returns 

49 ------- 

50 str 

51 ``status.value`` as plain text. 

52 """ 

53 return f"{status.value}" 

54 

55 # --------------------------------------------------------------------- # 

56 # Path builder 

57 # --------------------------------------------------------------------- # 

58 

59 @staticmethod 

60 def make_path( 

61 schema_path: Sequence[Any], 

62 json_path: Sequence[Any], 

63 ignore: PATH_MAKER_IGNORE_RULES_TYPE = ("properties",), 

64 ) -> str: 

65 """ 

66 Compose a human‑readable path by synchronising two parallel paths. 

67 

68 The function walks through *schema_path* (tokens from JSON Schema) and 

69 *json_path* (real path in the JSON instance) and emits a short textual 

70 representation such as ``["items"][0].extra``. 

71 

72 Parameters 

73 ---------- 

74 schema_path : Sequence[Any] 

75 Tokens encountered while traversing the schema. 

76 json_path : Sequence[Any] 

77 Path tokens from the actual JSON document. 

78 ignore : Sequence[str], default (``"properties"``,) 

79 Schema‑only service tokens to skip. 

80 

81 Returns 

82 ------- 

83 str 

84 Compact path string. 

85 

86 Algorithm 

87 --------- 

88 i — index in *schema_path*, j — index in *json_path*. 

89 

90 1. If *schema_path[i]* is in *ignore* and **differs** from 

91 *json_path[j]* → skip it. 

92 2. If tokens are equal → emit as property/index and advance both. 

93 3. Otherwise the token exists only in schema → emit as ``.token`` and 

94 advance *i*. 

95 4. After the schema is exhausted, append remaining elements of 

96 *json_path*. 

97 

98 Integer‑like tokens are rendered as ``[n]``; everything else as 

99 ``["key"]``. 

100 """ 

101 parts: List[str] = [] 

102 i = j = 0 

103 

104 while i < len(schema_path): 

105 s_tok = schema_path[i] 

106 

107 # 1. Ignore schema-only service tokens 

108 if s_tok in ignore and (j >= len(json_path) or str(s_tok) != str(json_path[j])): 

109 i += 1 

110 continue 

111 

112 # 2. Token is present in both paths 

113 if j < len(json_path) and str(s_tok) == str(json_path[j]): 

114 tok = json_path[j] 

115 parts.append(f"[{tok}]" if isinstance(tok, int) else f'["{tok}"]') 

116 i += 1 

117 j += 1 

118 continue 

119 

120 # 3. Token is schema-only – treat as extra property 

121 parts.append(f".{s_tok}") 

122 i += 1 

123 

124 # 4. Append the rest of json_path 

125 for tok in json_path[j:]: 

126 parts.append(f"[{tok}]" if isinstance(tok, int) else f'["{tok}"]') 

127 

128 return "".join(parts)