feat: populate Tags and Headers from framework metadata - #154
Merged
Merged
Conversation
No collector ever populated Tags or Headers even though both are part
of the canonical model and rendered by the formatters. Declared request
headers placed in Parameters with In: "header" were dropped by the curl
and postman formatters (they only read query/path/form parameters), so
e.g. JAX-RS @HeaderParam exports silently lost headers.
Headers is now the canonical home for declared request headers across
collectors; all three formatters already render it, so no formatter
changes were needed.
Per-module changes:
- api-collector-java: the three conversion functions tag endpoints with
the controller/resource/client name and map header parameters to
ApiHeader instead of Parameters. The parsers override the parameter
name with the wire header name carried by @RequestHeader/@HeaderParam
("Authorization", not the Java parameter name) via the new
parser.ExplicitAnnotationName helper, and Spring MVC now honors
required/defaultValue on @RequestHeader like @RequestParam.
- api-collector-go: gin/echo/fiber tag endpoints with the router
variable the route was registered on, and header reads (c.GetHeader,
c.Get) flow into Headers. Echo's analyzeHandlerBody documented
c.Request().Header.Get support but never implemented it; the new
matchHeaderGet helper implements it.
- api-collector-node: express/fastify extract the router/app variable
from the route call's member expression and tag endpoints with it
(the fastify route-object form threads the receiver through the
extraction chain). NestJS tags endpoints with the controller class
name and moves @headers('x') bindings to Headers.
- api-collector-python: fastapi tags endpoints with the router/app
variable from the decorator and moves Header(...) parameters to
Headers; django tags class-based views with their class name and
urls.py entries with the view name (stripping module qualifiers and
as_view); flask parses Blueprint('name', ...) declarations and tags
blueprint routes with the blueprint name, app routes with the
registering variable, and RESTX resources with their class name.
- apilot-cli: golden files refreshed — the only stdout changes are
X-Request-Id now exported by the curl and postman formatters for the
gin fixture.
Acceptance criteria verified by TestCollect_TagsAndHeaders (java) and
TestParse_TagsAndHeaders/TestParse_Tags (go gin/echo/fiber, node
express/nestjs, python fastapi/django/flask): a Spring MVC handler with
@RequestHeader("Authorization") exports that header in Headers under
its wire name, and exports carry non-empty Tags reflecting the
controller or resource the endpoint came from.
|
📦 Build artifact for this PR is available in the GitHub Actions workflow run under Artifacts. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #154 +/- ##
========================================
Coverage ? 71.594%
========================================
Files ? 66
Lines ? 12381
Branches ? 0
========================================
Hits ? 8864
Misses ? 2932
Partials ? 585
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #140.
No collector ever populated
TagsorHeaders, even though both are part of the canonical model and both are rendered by the formatters. Declared request headers were especially damaged: the only framework that classified them at all (JAX-RS) sent them toParameterswithIn: "header", and the curl and postman formatters only read query/path/form parameters out ofParameters— so authentication headers, content negotiation, and API version headers silently vanished from two of the three export formats. WithTagsempty, there was also nothing to group or filter endpoints by controller, module, or resource in any output.This PR settles the issue's placement question first:
Headersis the canonical home for declared request headers. All three formatters already renderep.Headers(markdown view.go:166, postman formatter.go:213, curl formatter.go:57), so moving header declarations there required no formatter changes — it strictly stopped data loss.Tagsis populated from the framework-level grouping metadata each parser already has in hand.Changes
api-collector-java/parser/extractor.goExplicitAnnotationNamereads the wire name carried by a binding annotation (@RequestHeader("Authorization")→Authorization, accepting both the single-value andname = ...forms) so header names come out as they appear on the wire rather than as the Java parameter nameapi-collector-java/springmvc/parser.go@RequestHeaderparameters take the explicit annotation name asName; therequired/defaultValueswitch now coversRequestHeaderalongsideRequestParam, so@RequestHeader(value = "X-Api-Version", required = false)is optional instead of silently forced requiredapi-collector-java/jaxrs/parser.go@HeaderParamparameters take the explicit annotation name asNameapi-collector-java/feign/parser.go@RequestHeaderparameters on Spring-contract clients take the explicit annotation name asNameapi-collector-java/collector.gospringmvcEndpointToAPI,jaxrsEndpointToAPI,feignEndpointToAPI) setTags: []string{folder}from the controller/resource/client name already passed in, and routeParamType == "header"parameters intoHeaders(Name/Value/Description/Example/Required) instead ofParametersapi-collector-java/testdata/HeaderController.java,HeaderResource.java,HeaderUserClient.java@RequestHeader("Authorization"), an optional@RequestHeader(value=..., required=false),@HeaderParam("X-Request-Id"), and a Feign@RequestHeader("X-Session-Token")api-collector-java/collector_test.goTestCollect_TagsAndHeaderscovers both acceptance criteria end to end across all three frameworksapi-collector-go/gin/parser.go,echo/parser.go,fiber/parser.goraw.receiverVar(the router/group variable the route was registered on —v1for group sub-routers) and routes header-shapedrawParams intoHeadersinstead ofParametersapi-collector-go/echo/parser.gomatchHeaderGetimplements thec.Request().Header.Get("X")extraction theanalyzeHandlerBodydoc comment already promised but the switch never had a case forapi-collector-go/{gin,echo,fiber}/testdata/basic/main.goc.GetHeader,c.Request().Header.Get,c.Get)api-collector-go/{gin,echo,fiber}/parser_test.goTestParse_TagsAndHeaders: router-variable tags (including the group fixture'sv1), header lands inHeaders, and noIn: "header"remains inParametersapi-collector-node/express/parser.goextractReceiverNamereads the object identifier of the route call's member expression (app.get(...)→app); the live path tags endpoints with itapi-collector-node/fastify/parser.goapp.get(...)) and route-object (app.route({...})) forms, threading the receiver through theextractRouteObject*chainapi-collector-node/nestjs/parser.goextractClassName(TypeScript names the class-name nodetype_identifier) threads the controller class name intobuildEndpointand tags endpoints with it;@Headers('x')param bindings land inHeadersinstead ofParametersapi-collector-node/{express,nestjs}/parser_test.goTestParse_Tags(express router fixture:appvsroutertags) and extendedTestParse_BasicRoutes(NestJS controller tags + header move);assertEndpointgained Tags/Headers comparisonsapi-collector-python/fastapi/parser.goresolveAttribute→resolveCallExpression→resolveDecoratorCall→extractDecoratorInfo) now also returns the router/app variable name instead of discarding it, and endpoints are tagged with it;Header(...)parameters are routed intoHeadersinbuildEndpointapi-collector-python/django/parser.gourls.pyentries are tagged with the view name (viewGroupNamestrips module qualifiers andas_view/as_view())api-collector-python/flask/parser.gorouteInfocaptures the decorator receiver (bp.route→bp); newextractBlueprintNames/blueprintNameparseBlueprint('api', ...)assignments (top-level assignments are wrapped inexpression_statement) so blueprint routes are tagged with the declared blueprint name, app routes fall back to the registering variable, and RESTX resources are tagged with their class nameapi-collector-python/{fastapi,django,flask}/parser_test.goTestParse_TagsAndHeaders/TestParse_Tagsper framework, with aHeader()endpoint added to the FastAPI router fixtureapilot-cli/testdata/goproject/{curl,postman}.goldenX-Request-Id(read viac.GetHeaderin the gin fixture) now exported by the curl formatter (-H 'X-Request-Id: ', previously dropped entirely) and present in the postman request headers; markdown goldens are byte-identical because markdown already rendered both header homes identicallyNo formatter code changed: markdown renders
Parameters-header rows andHeadersrows identically, and postman/curl only ever readHeaders.Verification
go test ./...andgo vet ./...pass forapi-collector-java,api-collector-go,api-collector-node, andapi-collector-python; the full suite passes for all 12 workspace test-bearing modules (api-collector,api-docmeta, the three formatters,api-master,apilot-cliincluded).apilot-cligolden tests pass after the deliberate refresh;git diffon the goldens shows exactly the intendedX-Request-Idadditions and nothing else.@RequestHeader("Authorization")exports that header inHeadersunder its wire name (TestCollect_TagsAndHeaders,HeaderController.java).Tagsreflecting the controller or resource the endpoint came from — asserted per framework:HeaderController/HeaderResource/HeaderUserClient(Java),r/v1/e/app(Go),app/router/UserController(Node),app/router/UserViewSet/api(Python).