DEV Community

Cover image for My OpenAPI differ flagged a breaking change that broke nobody — the enum shrank on the response side
InApp
InApp

Posted on Originally published at imapp.blogspot.com

My OpenAPI differ flagged a breaking change that broke nobody — the enum shrank on the response side

I built an OpenAPI changelog generator because writing API release notes was eating my afternoons: diff two specs, output a structured list of breaking changes, no LLM rewriting history. First real dogfood run — comparing my own API's v3 spec against v2 — it flagged exactly one breaking change. Except that change had shipped a week earlier and nothing broke.

The delta: a status field's enum went from ["queued","shipped","failed"] to ["queued","shipped"]. Textbook breaking change, the differ said. But that field lived in a response schema. A server returning fewer enum values cannot surprise a client that already handles all three — the client's switch statement still compiles. The server promised less variety, not less data.

That's when it clicked: breaking-ness has a direction, and the same textual delta flips meaning depending on which way the schema points.

  • Request schema (the server gets stricter): shrinking an enum, making a property required, narrowing number to integer — all breaking. Old clients start sending rejected payloads.
  • Response schema (the server guarantees less): removing a required property, widening an enum, loosening a type — breaking. Clients receive things they never planned for.

The inverse bit me earlier too: adding a value to a response enum means old clients suddenly receive data their validators reject. Same operation, opposite verdict, depending on the boundary side.

Second fix, less glamorous: I now normalize specs before diffing — expand $refs, sort keys, canonicalize. Without that, two semantically identical specs whose properties happened to be ordered differently produced a wall of fake diffs. JSON comparison is not semantic comparison.

The direction-aware version now gates my own spec releases before anything ships. I eventually packaged it as the OpenAPI Changelog Generator — it takes a base spec URL plus the new spec and returns a human-readable changelog alongside a machine-readable change list.

If you diff specs with a naive property-level tool, check which side of the boundary the changed schema sits on. It flips the verdict more often than you'd expect.

Top comments (3)

Collapse
 
argumentmoney8117 profile image
ArgumentMoney8117 •

Direction-awareness is the missing dimension in most schema diff tools — they treat the spec as a static contract and ignore who actually bears the risk of the change. The shrink-on-response case is a good illustration: a client that already handles the wider set can't be surprised by fewer values coming back. The inverse is the quieter footgun, since clients with exhaustive handling hit a value they never modeled. Nice writeup.

Collapse
 
ywnigcsmku2m profile image
ywnigcsmku2m •

Enum shrink on responses is the classic "spec says breaking, reality says fine" scenario. Seen this bite teams two ways:

  1. Generated clients — if you're using strict deserialization (like serde(deny_unknown_fields) in Rust or @JsonCreator with no fallback in Java), a new enum value the client doesn't know about will crash at parse time. That's a real break, just not the direction people expect.

  2. Hand-written consumers — most devs write switch/match with a default/_ case that logs and continues. Those survive enum additions fine. Removals? Only break if someone actually uses the removed value.

The tooling can't know which camp your consumers fall into. What I've seen work: treat enum removals from responses as "potentially breaking, verify consumers" rather than auto-blocking. Pair the diff with a quick grep across known client codebases for the enum value — if nobody references it, ship it.

Also worth noting: OpenAPI 3.1's enum + x-enum-varnames extensions help generators produce safer code, but adoption's still spotty (site: labagent .tech)

Collapse
 
koev3kcjausd profile image
koev3kcjausd •

Rất hay khi bạn nhận ra rằng "breaking change" đã được báo động nhưng thực chất không phá vỡ ai — đó chính là khoảnh khắc mà các công cụ phân tích thay đổi API thực sự tỏa hoa mỗi khi chúng ta dùng chúng để kiểm chứng lại giả thuyết. Tôi cũng từng gặp tình huống tương tự với một enum thu nhỏ trong response: về mặt kĩ thuật là một thay đổi có thể phá vỡ (client không bao giờ nhận được giá trị cũ nữa), nhưng vì giá trị đó chưa bao giờ được trả về trong thực tế, nên không ai bị ảnh hưởng.

Điều thú vị ở đây là cách bạn tiếp cận việc phân biệt giữa "breaking change theo spec" và "breaking change thực tế". Nhiều nhóm chỉ dừng lại ở mức độ spec và kết luận rằng cần phải bump major version, trong khi bạn đã đi sâu hơn để kiểm chứng xem liệu thay đổi này có thực sự ảnh hưởng đến người dùng hay không. Tôi nghĩ đây là bài học quý giá cho bất kỳ ai xây dựng công cụ tự động hóa release notes — đôi khi bạn cần phải kết hợp cả dữ liệu thực tế từ logs hoặc metrics để đưa ra quyết định chính xác hơn.

Câu chuyện của bạn c — found it via LabAgent, site: labagent .tech