11"""Schema karşılaştırma modülü — iki schema arasındaki farkları bulur."""
22
3+ import json
34import logging
45from datetime import datetime , timezone
56from typing import Any
1011
1112logger = logging .getLogger (__name__ )
1213
14+ # Değişiklik tipi → okunabilir etiket ve açıklama eşlemesi
15+ _CHANGE_TYPE_LABELS : dict [str , str ] = {
16+ "field_removed" : "Field Removed" ,
17+ "field_added" : "Field Added" ,
18+ "type_changed" : "Type Changed" ,
19+ "nullable_changed_to_false" : "Nullable Changed" ,
20+ "array_item_schema_changed" : "Array Schema Changed" ,
21+ "auth_changed" : "Auth Changed" ,
22+ "format_changed" : "Format Changed" ,
23+ "enum_value_removed" : "Enum Value Removed" ,
24+ "enum_value_added" : "Enum Value Added" ,
25+ "required_field_added" : "Required Field Added" ,
26+ "depth_changed" : "Depth Changed" ,
27+ "status_code_changed_2xx" : "Status Code Changed" ,
28+ }
29+
30+ _CHANGE_TYPE_DESCRIPTIONS : dict [str , str ] = {
31+ "field_removed" : "Bu alan API response'undan tamamen kaldirildi. Bu alana bagli istemciler hata alabilir." ,
32+ "field_added" : "API response'una yeni bir alan eklendi. Mevcut istemciler etkilenmez." ,
33+ "type_changed" : "Bu alanin veri tipi degisti. Tip uyumsuzlugu istemcilerde parse hatasina yol acabilir." ,
34+ "nullable_changed_to_false" : "Bu alan artik null deger kabul etmiyor. null bekleyen istemciler etkilenebilir." ,
35+ "array_item_schema_changed" : "Array icindeki nesnelerin yapisi degisti. Listelemeye bagli istemciler etkilenebilir." ,
36+ "auth_changed" : "Kimlik dogrulama yapisi degisti." ,
37+ "format_changed" : "Alanin formati degisti (ornek: date -> datetime)." ,
38+ "enum_value_removed" : "Izin verilen degerlerden biri kaldirildi." ,
39+ "enum_value_added" : "Izin verilen degerlere yeni bir deger eklendi." ,
40+ "required_field_added" : "Yeni bir zorunlu alan eklendi." ,
41+ "depth_changed" : "Nesnelerin icleme derinligi degisti." ,
42+ "status_code_changed_2xx" : "HTTP basari durum kodu degisti." ,
43+ }
44+
45+
46+ def _field_summary (field_data : dict [str , Any ]) -> str :
47+ """Bir field'ın kısa yapısal özetini oluştur.
48+
49+ Args:
50+ field_data: Schema field verisi
51+
52+ Returns:
53+ Okunabilir field özeti
54+ """
55+ parts : list [str ] = []
56+
57+ field_type = field_data .get ("type" , "unknown" )
58+ parts .append (f"type: { field_type } " )
59+
60+ if field_data .get ("nullable" ):
61+ parts .append ("nullable: true" )
62+
63+ if field_type == "object" and "fields" in field_data :
64+ sub_fields = list (field_data ["fields" ].keys ())
65+ if sub_fields :
66+ if len (sub_fields ) <= 5 :
67+ parts .append (f"fields: [{ ', ' .join (sub_fields )} ]" )
68+ else :
69+ parts .append (f"fields: [{ ', ' .join (sub_fields [:5 ])} , ... +{ len (sub_fields ) - 5 } ]" )
70+
71+ if field_type == "array" and "items" in field_data :
72+ items = field_data ["items" ]
73+ items_type = items .get ("type" , "unknown" )
74+ if items_type == "object" and "fields" in items :
75+ item_fields = list (items ["fields" ].keys ())
76+ if len (item_fields ) <= 5 :
77+ parts .append (f"items: { items_type } [{ ', ' .join (item_fields )} ]" )
78+ else :
79+ parts .append (f"items: { items_type } [{ ', ' .join (item_fields [:5 ])} , ... +{ len (item_fields ) - 5 } ]" )
80+ else :
81+ parts .append (f"items: { items_type } " )
82+
83+ if "sample" in field_data :
84+ sample = str (field_data ["sample" ])
85+ if len (sample ) > 50 :
86+ sample = sample [:50 ] + "..."
87+ parts .append (f'sample: "{ sample } "' )
88+
89+ return "{ " + ", " .join (parts ) + " }"
90+
91+
92+ def _field_to_json_preview (field_data : dict [str , Any ], max_depth : int = 2 ) -> dict [str , Any ]:
93+ """Bir field'ın JSON benzeri önizlemesini oluştur.
94+
95+ Args:
96+ field_data: Schema field verisi
97+ max_depth: Maksimum derinlik
98+
99+ Returns:
100+ JSON preview dict
101+ """
102+ result : dict [str , Any ] = {"type" : field_data .get ("type" , "unknown" )}
103+
104+ if field_data .get ("nullable" ):
105+ result ["nullable" ] = True
106+
107+ if "sample" in field_data :
108+ result ["sample" ] = field_data ["sample" ]
109+
110+ if max_depth > 0 :
111+ if field_data .get ("type" ) == "object" and "fields" in field_data :
112+ sub_fields = {}
113+ for name , sub_data in field_data ["fields" ].items ():
114+ sub_fields [name ] = sub_data .get ("type" , "unknown" )
115+ result ["fields" ] = sub_fields
116+
117+ if field_data .get ("type" ) == "array" and "items" in field_data :
118+ items = field_data ["items" ]
119+ if items .get ("type" ) == "object" and "fields" in items :
120+ sub_fields = {}
121+ for name , sub_data in items ["fields" ].items ():
122+ sub_fields [name ] = sub_data .get ("type" , "unknown" )
123+ result ["items" ] = {"type" : "object" , "fields" : sub_fields }
124+ else :
125+ result ["items" ] = {"type" : items .get ("type" , "unknown" )}
126+
127+ return result
128+
13129
14130def _flatten_changes (old_schema : dict , new_schema : dict ) -> list [dict [str , Any ]]:
15131 """İki schema arasındaki değişiklikleri düzleştirilmiş liste olarak döndür.
@@ -29,28 +145,44 @@ def _flatten_changes(old_schema: dict, new_schema: dict) -> list[dict[str, Any]]
29145 # Kaldırılan alanlar
30146 for field_name in old_fields :
31147 if field_name not in new_fields :
32- old_type = old_fields [field_name ].get ("type" , "unknown" )
148+ old_field = old_fields [field_name ]
149+ old_type = old_field .get ("type" , "unknown" )
150+ change_type = "field_removed"
33151 change = {
34- "type" : "field_removed" ,
152+ "type" : change_type ,
153+ "change_type" : _CHANGE_TYPE_LABELS .get (change_type , change_type ),
35154 "path" : f"$.{ field_name } " ,
36155 "old_type" : old_type ,
37156 "new_type" : None ,
38- "severity" : get_severity ("field_removed" ),
39- "message" : f"Alan kaldırıldı: { field_name } ({ old_type } )" ,
157+ "old_value" : _field_summary (old_field ),
158+ "new_value" : None ,
159+ "old_preview" : json .dumps (_field_to_json_preview (old_field ), indent = 2 , ensure_ascii = False ),
160+ "new_preview" : None ,
161+ "severity" : get_severity (change_type ),
162+ "message" : f"Alan kaldirildi: { field_name } ({ old_type } )" ,
163+ "description" : _CHANGE_TYPE_DESCRIPTIONS .get (change_type , "" ),
40164 }
41165 changes .append (change )
42166
43167 # Eklenen alanlar
44168 for field_name in new_fields :
45169 if field_name not in old_fields :
46- new_type = new_fields [field_name ].get ("type" , "unknown" )
170+ new_field = new_fields [field_name ]
171+ new_type = new_field .get ("type" , "unknown" )
172+ change_type = "field_added"
47173 change = {
48- "type" : "field_added" ,
174+ "type" : change_type ,
175+ "change_type" : _CHANGE_TYPE_LABELS .get (change_type , change_type ),
49176 "path" : f"$.{ field_name } " ,
50177 "old_type" : None ,
51178 "new_type" : new_type ,
52- "severity" : get_severity ("field_added" ),
179+ "old_value" : None ,
180+ "new_value" : _field_summary (new_field ),
181+ "old_preview" : None ,
182+ "new_preview" : json .dumps (_field_to_json_preview (new_field ), indent = 2 , ensure_ascii = False ),
183+ "severity" : get_severity (change_type ),
53184 "message" : f"Yeni alan: { field_name } ({ new_type } )" ,
185+ "description" : _CHANGE_TYPE_DESCRIPTIONS .get (change_type , "" ),
54186 }
55187 changes .append (change )
56188
@@ -64,13 +196,20 @@ def _flatten_changes(old_schema: dict, new_schema: dict) -> list[dict[str, Any]]
64196 old_type = old_field .get ("type" , "unknown" )
65197 new_type = new_field .get ("type" , "unknown" )
66198 if old_type != new_type :
199+ change_type = "type_changed"
67200 change = {
68- "type" : "type_changed" ,
201+ "type" : change_type ,
202+ "change_type" : _CHANGE_TYPE_LABELS .get (change_type , change_type ),
69203 "path" : f"$.{ field_name } " ,
70204 "old_type" : old_type ,
71205 "new_type" : new_type ,
72- "severity" : get_severity ("type_changed" ),
73- "message" : f"Tip değişti: { field_name } ({ old_type } → { new_type } )" ,
206+ "old_value" : _field_summary (old_field ),
207+ "new_value" : _field_summary (new_field ),
208+ "old_preview" : json .dumps (_field_to_json_preview (old_field ), indent = 2 , ensure_ascii = False ),
209+ "new_preview" : json .dumps (_field_to_json_preview (new_field ), indent = 2 , ensure_ascii = False ),
210+ "severity" : get_severity (change_type ),
211+ "message" : f"Tip degisti: { field_name } ({ old_type } -> { new_type } )" ,
212+ "description" : _CHANGE_TYPE_DESCRIPTIONS .get (change_type , "" ),
74213 }
75214 changes .append (change )
76215
@@ -84,11 +223,17 @@ def _flatten_changes(old_schema: dict, new_schema: dict) -> list[dict[str, Any]]
84223 change_type = "field_added" # nullable olması INFO seviyesinde
85224 change = {
86225 "type" : change_type ,
226+ "change_type" : _CHANGE_TYPE_LABELS .get (change_type , change_type ),
87227 "path" : f"$.{ field_name } " ,
88228 "old_type" : f"nullable={ old_nullable } " ,
89229 "new_type" : f"nullable={ new_nullable } " ,
230+ "old_value" : f"nullable: { str (old_nullable ).lower ()} " ,
231+ "new_value" : f"nullable: { str (new_nullable ).lower ()} " ,
232+ "old_preview" : None ,
233+ "new_preview" : None ,
90234 "severity" : get_severity (change_type ),
91- "message" : f"Nullable değişti: { field_name } ({ old_nullable } → { new_nullable } )" ,
235+ "message" : f"Nullable degisti: { field_name } ({ old_nullable } -> { new_nullable } )" ,
236+ "description" : _CHANGE_TYPE_DESCRIPTIONS .get (change_type , "" ),
92237 }
93238 changes .append (change )
94239
@@ -117,13 +262,20 @@ def _flatten_changes(old_schema: dict, new_schema: dict) -> list[dict[str, Any]]
117262 nested_change ["severity" ] = get_severity ("array_item_schema_changed" )
118263 changes .append (nested_change )
119264 elif old_items .get ("type" ) != new_items .get ("type" ):
265+ change_type = "array_item_schema_changed"
120266 change = {
121- "type" : "array_item_schema_changed" ,
267+ "type" : change_type ,
268+ "change_type" : _CHANGE_TYPE_LABELS .get (change_type , change_type ),
122269 "path" : f"$.{ field_name } []" ,
123270 "old_type" : old_items .get ("type" , "unknown" ),
124271 "new_type" : new_items .get ("type" , "unknown" ),
125- "severity" : get_severity ("array_item_schema_changed" ),
126- "message" : f"Array item tipi değişti: { field_name } ({ old_items .get ('type' )} → { new_items .get ('type' )} )" ,
272+ "old_value" : _field_summary (old_items ),
273+ "new_value" : _field_summary (new_items ),
274+ "old_preview" : json .dumps (_field_to_json_preview (old_items ), indent = 2 , ensure_ascii = False ),
275+ "new_preview" : json .dumps (_field_to_json_preview (new_items ), indent = 2 , ensure_ascii = False ),
276+ "severity" : get_severity (change_type ),
277+ "message" : f"Array item tipi degisti: { field_name } ({ old_items .get ('type' )} -> { new_items .get ('type' )} )" ,
278+ "description" : _CHANGE_TYPE_DESCRIPTIONS .get (change_type , "" ),
127279 }
128280 changes .append (change )
129281
0 commit comments