Native OpenAPI 3.2.0 Tag Object support (parent, summary, kind, externalDocs) - #1077
Native OpenAPI 3.2.0 Tag Object support (parent, summary, kind, externalDocs)#1077jeffreyvanhees wants to merge 5 commits into
Conversation
|
Tests are failing because composer tries to install the oldest compatible versions of dependencies. Recent updates to Pest PHP and its dependencies now require PHP 8.2+ creating a conflict with the PHP 8.1 test configuration. |
|
@jeffreyvanhees what do you think about how this PR fits/relates to newly introduced OpenAPI 3.2.0 tags that support child/parent relationships? |
Replace the Redoc x-tagGroups extension approach with spec-native OpenAPI 3.2.0 Tag Object fields: parent, summary, kind, externalDocs. - Add ExternalDocumentation class - Extend Tag with summary, parent, kind, externalDocs properties - Add summary, kind, externalDocsUrl, externalDocsDescription to Group attribute - Update AddDocumentTags to set native fields and auto-create parent tags - Remove AddTagGroups transformer (no longer needed) - Bump OAS version from 3.1.0 to 3.2.0 - Rewrite tests for native parent field assertions
Sorry, this sat for far too long. Had another look and I think we were saying the same thing: that's what this PR does. The Since then it got more interesting. Scalar merged nested tag rendering last week (scalar/scalar#9406, in the 2026-08-20 release), and it reads the native I've merged current main in and pushed. CI is green across the matrix, and the PHP 8.1 job I complained about back in February is gone, so Three things came out of the merge:
The tag key is back to plain The
Auto-creating missing parent tags stays, since the spec wants the named parent to exist. The half that's still missing is circular parent/child detection. Right now a cycle just generates a document that quietly violates the spec, no warning. Happy to add it, though it might belong with the other diagnostics rather than in the transformer. Let me know which you'd prefer. |
Resolves the conflicts main introduced since this branch was opened, and
corrects two things the spec made clear on re-reading.
AddDocumentTags now reads the attribute through newInstance() and typed
properties, following main, instead of the positional getArguments() indexes
this branch was written against.
The tag key is back to the plain name. Keying on "{parent}/{name}" was meant to
allow one name under several parents, but tag names must be unique and
tags[].name is what operations reference, so two tags of one name cannot be
resolved either way.
The kind docblock no longer claims "navigation" (default) or "api". The spec
allows any string and names nav, badge and audience as the common ones.
GroupTest keeps both sets of tests: main's six and this branch's nine.
Snapshots moved from .yml to .json upstream; the .json ones now carry 3.2.0.
A parent named by `Group::$parent` holds no endpoints of its own, so nothing carried its description and it reached the document as a bare name. `Group` is not repeatable, and putting a second one on a method moves the endpoint into that group instead of describing the parent, so neither existing route worked. `Tag` states what a tag is, where `Group` states where an endpoint belongs. It is repeatable, so one class can declare the whole branch it hangs under, and it carries `parent` itself so a parent may sit under a parent. A group of the same name wins on every field it already states. Declaring a tag therefore never moves an endpoint or overrides where one says it belongs.
|
Pushed another commit, and updated the description to match. Some context on where it came from, since it was not planned. I took this branch for a spin on a real API (366 endpoints, three audiences) to see whether the nesting actually helps, and grouped everything into parents: Selling, Operating, Business, that sort of thing. It does help. But every parent came out of the generator as a bare name, because a parent holds no endpoints and nothing else was carrying its description. Two things I tried first, both dead ends worth recording: Putting a controller in the parent group works, but then those endpoints sit next to the subgroups instead of inside one, which is the flat list I was trying to get rid of. A second So the gap is real and neither existing route closes it. My first attempt was a
Five tests cover it, including that last precedence rule and the endpoint staying put. Full suite is green at 1216. One naming note: Still open from before: circular parent references aren't detected. Happy to add it, but it might sit better with the diagnostics than in the transformer. Your call on both that and whether Examples are upadted in PR description |
This PR adds support for the native OpenAPI 3.2.0 Tag Object fields: spec-compliant hierarchical tag grouping, plus the other metadata the object gained.
What changed
3.1.0to3.2.0parentfield on tags, so groups nestsummary,kind,externalDocsfields on tagsTagattribute for declaring a tag that no endpoint claimsHierarchical grouping
Groupsays where an endpoint belongs.parentsays what that group hangs under:{ "tags": [ { "name": "Products", "parent": "Catalogue" }, { "name": "Options", "parent": "Catalogue" }, { "name": "Catalogue" } ] }Declaring a tag with the
TagattributeCatalogueabove holds no endpoints of its own, so nothing describes it and it reaches the document as a bare name.Groupcannot fill that gap: it is not repeatable, and a second one on a method moves the endpoint into that group rather than describing the parent.Tagstates what a tag is, whereGroupstates where an endpoint belongs:{ "tags": [ { "name": "Products", "parent": "Catalogue" }, { "name": "Catalogue", "description": "Everything a restaurant sells, and what it costs." } ] }It is repeatable, so one class can declare a whole branch, and it carries
parentitself so a parent may sit under a parent:A
Groupof the same name wins on every field it already states, so declaring a tag never moves an endpoint or overrides where one says it belongs.Tag summary
A short summary, separate from the longer
description:Tag kind
A machine-readable category. The spec allows any string and names
nav,badgeandaudienceas the common ones:External documentation
{ "tags": [{ "name": "Payments", "externalDocs": { "url": "https://docs.stripe.com", "description": "Stripe API reference" } }] }Why this is worth having now
Scalar merged nested tag rendering in scalar/scalar#9406, shipped in the 2026-08-20 release. It reads the native
parentfield first and falls back tox-tagGroupsfor older documents, with arbitrary depth and a parent that holds no operations rendering as a section. So 3.2.0 tags out of Scramble now show up in the reference rather than only being more correct on paper.Breaking changes
3.2.0. Tools that do not support it may not recognise the new tag fields.Backward compatibility
Groupattributes withoutparentare unaffectedTagis additive; nothing needs itKnown gap
Circular
parentreferences are not detected. The spec forbids them, but a cycle currently generates a document that violates it without any warning. Happy to add that here, though it may belong with the other diagnostics rather than in the transformer.