"""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 )