DEV Community

Cover image for My OpenAPI changelog generator learned that 'deprecated' is a promise your spec never kept
InApp
InApp

Posted on Originally published at imapp.blogspot.com

My OpenAPI changelog generator learned that 'deprecated' is a promise your spec never kept

I built a deterministic OpenAPI differ because I got tired of LLMs writing changelogs that sounded confident and were completely wrong. The tool takes a base spec and a new spec, walks the paths, schemas, and parameters, and emits a structured diff. No model in the loop — just tree-walking and rule tables.

The first version flagged anything with deprecated: true as a breaking change. That turned out to be naive. Real specs mark fields deprecated for years while keeping them alive for legacy clients. A field marked deprecated in v2 and still present in v4 is not breaking — removing it would be. My differ was crying wolf on every release, and my release notes became a boy-who-cried-breaking-change document that nobody read.

The fix was to stop treating deprecated as a tombstone and start treating it as a schedule. Now the differ reports three separate things: newly deprecated (warning, not breaking), still deprecated from before (informational), and actually removed while previously deprecated (now that's breaking). It also cross-references: if a field was deprecated in the base and is gone in the new spec, the severity depends on whether the base spec advertised a removal date. Specs rarely do. So most "removals of deprecated things" get flagged as "breaking, but you told them it was coming."

There's a second gotcha I only found by diffing a spec against itself after a YAML re-serialization. Key ordering changed, inline schemas got hoisted into components, and my differ reported 40 "changes." I added a canonicalization pass — sort keys, resolve $ref chains before comparing, normalize example vs examples. The diff now answers "what did the API author actually change?" not "what did the YAML formatter change?"

The honest framing: deterministic diffing is a parsing problem wearing a changelog problem's clothes. Get the canonicalization wrong and you're generating noise. Get the deprecation semantics wrong and you're generating panic. Both took real-world spec pairs to catch.

It's packaged at https://x402.freeq.one/tools/changelog_openapi.html — give it two spec URLs and it returns the structured diff. I use it before every release of my own APIs now, mostly to catch the changes I forgot I made.

Top comments (0)