Files
schema2mock/http_log_explorer/analyzers/diff_engine.py

186 lines
5.2 KiB
Python

"""Diff engine for comparing HTTP entries."""
import difflib
from http_log_explorer.models import DiffResult, HTTPEntry
class DiffEngine:
"""Engine for comparing HTTP request/response pairs."""
def diff(self, entry1: HTTPEntry, entry2: HTTPEntry) -> DiffResult:
"""Compare two HTTP entries.
Args:
entry1: First HTTPEntry
entry2: Second HTTPEntry
Returns:
DiffResult with differences
"""
result = DiffResult(
entry1_id=entry1.id,
entry2_id=entry2.id,
)
result.url_changed = entry1.request.url != entry2.request.url
result.status_changed = entry1.response.status != entry2.response.status
result.status1 = entry1.response.status
result.status2 = entry2.response.status
result.request_headers_diff = self.headers_diff(
entry1.request.headers,
entry2.request.headers,
)
result.response_headers_diff = self.headers_diff(
entry1.response.headers,
entry2.response.headers,
)
result.request_body_diff = self.body_diff(
entry1.request.body,
entry2.request.body,
)
result.response_body_diff = self.body_diff(
entry1.response.body,
entry2.response.body,
)
return result
def headers_diff(
self, headers1: dict[str, str], headers2: dict[str, str]
) -> list[str]:
"""Compare two header dictionaries.
Args:
headers1: First headers dict
headers2: Second headers dict
Returns:
List of diff lines
"""
all_keys = set(headers1.keys()) | set(headers2.keys())
diff_lines: list[str] = []
for key in sorted(all_keys):
val1 = headers1.get(key)
val2 = headers2.get(key)
if val1 != val2:
if val1 is None:
diff_lines.append(f"+ {key}: {val2}")
elif val2 is None:
diff_lines.append(f"- {key}: {val1}")
else:
diff_lines.append(f"- {key}: {val1}")
diff_lines.append(f"+ {key}: {val2}")
return diff_lines
def body_diff(
self, body1: str | None, body2: str | None
) -> list[str]:
"""Compare two body strings.
Args:
body1: First body
body2: Second body
Returns:
List of diff lines (unified format)
"""
if body1 == body2:
return []
b1 = body1 or ""
b2 = body2 or ""
lines1 = b1.splitlines(keepends=True)
lines2 = b2.splitlines(keepends=True)
if not lines1 and not lines2:
return []
diff = list(difflib.unified_diff(
lines1,
lines2,
fromfile="before",
tofile="after",
lineterm="",
))
return diff
def unified_diff_output(self, diff_result: DiffResult) -> str:
"""Generate a human-readable unified diff output.
Args:
diff_result: The diff result
Returns:
Formatted string with all differences
"""
lines: list[str] = []
lines.append(f"=== Diff: {diff_result.entry1_id} vs {diff_result.entry2_id} ===")
lines.append("")
if diff_result.url_changed:
lines.append(f"URL changed: {diff_result.url_changed}")
if diff_result.status_changed:
lines.append(f"Status: {diff_result.status1} -> {diff_result.status2}")
if diff_result.request_headers_diff:
lines.append("")
lines.append("--- Request Headers ---")
lines.extend(diff_result.request_headers_diff)
if diff_result.request_body_diff:
lines.append("")
lines.append("--- Request Body ---")
lines.extend(diff_result.request_body_diff)
if diff_result.response_headers_diff:
lines.append("")
lines.append("--- Response Headers ---")
lines.extend(diff_result.response_headers_diff)
if diff_result.response_body_diff:
lines.append("")
lines.append("--- Response Body ---")
lines.extend(diff_result.response_body_diff)
if not any([
diff_result.url_changed,
diff_result.status_changed,
diff_result.request_headers_diff,
diff_result.request_body_diff,
diff_result.response_headers_diff,
diff_result.response_body_diff,
]):
lines.append("No differences found.")
return "\n".join(lines)
def has_differences(self, diff_result: DiffResult) -> bool:
"""Check if there are any differences.
Args:
diff_result: The diff result
Returns:
True if there are any differences
"""
return bool(
diff_result.url_changed
or diff_result.status_changed
or diff_result.request_headers_diff
or diff_result.request_body_diff
or diff_result.response_headers_diff
or diff_result.response_body_diff
)