Side-by-side service compare

When an API is versioned (v1, v2) or a server is mid-migration, the question is always the same: does the new one still behave like the old one? Before #182 the only answer was to invoke each side separately and eyeball the responses. The Compare surface does it for you.

Opening it

In the Discover rail, a Compare button sits in the toolbar whenever there is something to set against something else — at least two services, or at least two discovery URLs. Two URLs matter on their own because discovery de-dupes services by name: the same OrderService at two deployments collapses to a single row in the tree, so the button stays available to compare it against its other deployment.

Clicking it opens a full-pane compare surface. Pick a source (a discovery URL, or the embedded host) and then a service on each side — Baseline (A) on the left, Target (B) on the right. Each side is discovered independently, so two deployments of the same-named service are both reachable even though the tree only shows one.

What it diffs

Schema

Methods align by name, and version markers are matched — GetUser pairs with GetUser_v2, GET /v1/users with GET /v2/users — so a pure version bump is not mistaken for a change. The aligned methods split into:

Marker Meaning
+ (green) Added — present on the target, not the baseline.
(red) Removed — present on the baseline, gone on the target.
~ (yellow) Signature changed — same method, but the route, invocation type, request shape, response shape, or deprecated flag moved. The row names which facet.
= Unchanged (hidden by default; a show unchanged toggle reveals them).

The same AST-level diff powers Schema Watch (#185) — prose-only edits (a changed summary) are not counted as a breaking change.

Responses

For any aligned unary method, Diff response invokes it on both sides and compares the two response bodies field by field, type-aware — not a line diff:

  • $.total: type number → string — a field's type moved.
  • $.items.0.sku: added / removed — a field appeared or vanished.
  • $.status: "ok" → "shipped" — a leaf value changed.

Non-JSON responses fall back to a line diff. Invoke all & diff responses runs every aligned unary pair in sequence. (Streaming and duplex methods are schema-compared but not invoke-diffed — there is no single response body to set against another.)

Responses are sent with an empty {} body in this first version; for a method that needs a populated request, open it in the normal request builder to shape the call, then compare.

Export

Export markdown downloads a report — the schema summary, the added / removed / signature-changed method lists, and each response field diff — ready to paste into a pull-request comment. This is the same content the v2.5 PR bot will post automatically.

Notes

  • The compare surface is ephemeral: it holds no secrets on disk, and closing it (or switching rails, or opening a method) discards its state. Re-open it to start fresh.
  • Both sides discover live, so the comparison always reflects the servers as they are right now.