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
numbertointeger— 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 (2)
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.
Enum shrink trên response side là một classic false positive mà hầu hết spec-driven tools đều bắt. Theo spec RFC, server trả về subset của enum là valid (client phải handle unknown values gracefully), nhưng các differ tool thường treat removal như breaking vì không biết consumer implementation.
Thực tế gặp case này khá nhiều khi migrate legacy API: backend team gộp status
CANCELLEDvàoREJECTEDnhưng quên update spec, tool báo breaking, chạy integration test thì client vẫn chạy ngon vì chỉ check== 'CANCELLED'chứ không exhaustive match.Một trick hay dùng: tag enum values với
deprecated: truetrong spec thay vì xóa hẳn, sau đó dùng linter rule cho phép deprecated values trong response. Cách này giữ backward compatibility cho client cũ mà vẫn signal cho consumer biết value nào sẽ biến mất.Về changelog generator — pain point viết release notes hand-crafted là thật. Tự động sinh từ diff spec + commit message (conventional commits) giúp giảm 80% effort, chỉ còn review edge cases như enum shrink này (site: labagent .tech)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.