Skip to content

feat: populate Tags and Headers from framework metadata - #154

Merged
tangcent merged 1 commit into
mainfrom
feature/issue-140-tags-headers
Sep 30, 2026
Merged

tangcent merged 1 commit into
mainfrom
feature/issue-140-tags-headers

Conversation

@tangcent

Copy link
Copy Markdown
Owner

Summary

Closes #140.

No collector ever populated Tags or Headers, 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 to Parameters with In: "header", and the curl and postman formatters only read query/path/form parameters out of Parameters — so authentication headers, content negotiation, and API version headers silently vanished from two of the three export formats. With Tags empty, 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: Headers is the canonical home for declared request headers. All three formatters already render ep.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. Tags is populated from the framework-level grouping metadata each parser already has in hand.

Changes

File Change
api-collector-java/parser/extractor.go New ExplicitAnnotationName reads the wire name carried by a binding annotation (@RequestHeader("Authorization") → Authorization, accepting both the single-value and name = ... forms) so header names come out as they appear on the wire rather than as the Java parameter name
api-collector-java/springmvc/parser.go @RequestHeader parameters take the explicit annotation name as Name; the required/defaultValue switch now covers RequestHeader alongside RequestParam, so @RequestHeader(value = "X-Api-Version", required = false) is optional instead of silently forced required
api-collector-java/jaxrs/parser.go @HeaderParam parameters take the explicit annotation name as Name
api-collector-java/feign/parser.go @RequestHeader parameters on Spring-contract clients take the explicit annotation name as Name
api-collector-java/collector.go All three conversion functions (springmvcEndpointToAPI, jaxrsEndpointToAPI, feignEndpointToAPI) set Tags: []string{folder} from the controller/resource/client name already passed in, and route ParamType == "header" parameters into Headers (Name/Value/Description/Example/Required) instead of Parameters
api-collector-java/testdata/HeaderController.java, HeaderResource.java, HeaderUserClient.java Fixtures for @RequestHeader("Authorization"), an optional @RequestHeader(value=..., required=false), @HeaderParam("X-Request-Id"), and a Feign @RequestHeader("X-Session-Token")
api-collector-java/collector_test.go TestCollect_TagsAndHeaders covers both acceptance criteria end to end across all three frameworks
api-collector-go/gin/parser.go, echo/parser.go, fiber/parser.go The endpoint build loop tags endpoints with raw.receiverVar (the router/group variable the route was registered on — v1 for group sub-routers) and routes header-shaped rawParams into Headers instead of Parameters
api-collector-go/echo/parser.go New matchHeaderGet implements the c.Request().Header.Get("X") extraction the analyzeHandlerBody doc comment already promised but the switch never had a case for
api-collector-go/{gin,echo,fiber}/testdata/basic/main.go Each fixture gains one header read (c.GetHeader, c.Request().Header.Get, c.Get)
api-collector-go/{gin,echo,fiber}/parser_test.go TestParse_TagsAndHeaders: router-variable tags (including the group fixture's v1), header lands in Headers, and no In: "header" remains in Parameters
api-collector-node/express/parser.go New extractReceiverName reads the object identifier of the route call's member expression (app.get(...) → app); the live path tags endpoints with it
api-collector-node/fastify/parser.go Same receiver extraction for both the shorthand (app.get(...)) and route-object (app.route({...})) forms, threading the receiver through the extractRouteObject* chain
api-collector-node/nestjs/parser.go New extractClassName (TypeScript names the class-name node type_identifier) threads the controller class name into buildEndpoint and tags endpoints with it; @Headers('x') param bindings land in Headers instead of Parameters
api-collector-node/{express,nestjs}/parser_test.go TestParse_Tags (express router fixture: app vs router tags) and extended TestParse_BasicRoutes (NestJS controller tags + header move); assertEndpoint gained Tags/Headers comparisons
api-collector-python/fastapi/parser.go The decorator resolver chain (resolveAttribute → 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 into Headers in buildEndpoint
api-collector-python/django/parser.go Class-based views are tagged with their ViewSet/View class name; urls.py entries are tagged with the view name (viewGroupName strips module qualifiers and as_view/as_view())
api-collector-python/flask/parser.go routeInfo captures the decorator receiver (bp.route → bp); new extractBlueprintNames/blueprintName parse Blueprint('api', ...) assignments (top-level assignments are wrapped in expression_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 name
api-collector-python/{fastapi,django,flask}/parser_test.go TestParse_TagsAndHeaders/TestParse_Tags per framework, with a Header() endpoint added to the FastAPI router fixture
apilot-cli/testdata/goproject/{curl,postman}.golden Refreshed deliberately after diff review — the only stdout change is X-Request-Id (read via c.GetHeader in 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 identically

No formatter code changed: markdown renders Parameters-header rows and Headers rows identically, and postman/curl only ever read Headers.

Verification

  • go test ./... and go vet ./... pass for api-collector-java, api-collector-go, api-collector-node, and api-collector-python; the full suite passes for all 12 workspace test-bearing modules (api-collector, api-docmeta, the three formatters, api-master, apilot-cli included).
  • apilot-cli golden tests pass after the deliberate refresh; git diff on the goldens shows exactly the intended X-Request-Id additions and nothing else.
  • Acceptance criteria from the issue:
    • A Spring MVC handler with @RequestHeader("Authorization") exports that header in Headers under its wire name (TestCollect_TagsAndHeaders, HeaderController.java).
    • Exports carry non-empty Tags reflecting 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).

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.
@github-actions

Copy link
Copy Markdown

📦 Build artifact for this PR is available in the GitHub Actions workflow run under Artifacts.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.76471% with 36 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@d676394). Learn more about missing BASE report.

Files with missing lines Patch % Lines
api-collector-go/echo/parser.go 72.973% 5 Missing and 5 partials ⚠️
api-collector-java/parser/extractor.go 0.000% 8 Missing ⚠️
api-collector-python/fastapi/parser.go 77.419% 7 Missing ⚠️
api-collector-node/express/parser.go 75.000% 2 Missing and 1 partial ⚠️
api-collector-node/fastify/parser.go 86.364% 2 Missing and 1 partial ⚠️
api-collector-go/gin/parser.go 86.667% 1 Missing and 1 partial ⚠️
api-collector-python/flask/parser.go 96.491% 2 Missing ⚠️
api-collector-node/nestjs/parser.go 96.296% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##             main      #154   +/-   ##
========================================
  Coverage        ?   71.594%           
========================================
  Files           ?        66           
  Lines           ?     12381           
  Branches        ?         0           
========================================
  Hits            ?      8864           
  Misses          ?      2932           
  Partials        ?       585           
Flag Coverage Δ
unittests 71.594% <86.765%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
api-collector-go/fiber/parser.go 83.836% <100.000%> (ø)
api-collector-java/collector.go 88.797% <100.000%> (ø)
api-collector-java/feign/parser.go 78.683% <100.000%> (ø)
api-collector-java/jaxrs/parser.go 81.154% <100.000%> (ø)
api-collector-java/springmvc/parser.go 77.567% <100.000%> (ø)
api-collector-python/django/parser.go 85.877% <100.000%> (ø)
api-collector-node/nestjs/parser.go 80.882% <96.296%> (ø)
api-collector-go/gin/parser.go 78.836% <86.667%> (ø)
api-collector-python/flask/parser.go 66.860% <96.491%> (ø)
api-collector-node/express/parser.go 57.990% <75.000%> (ø)
... and 4 more

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d676394...7f8cf34. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tangcent
tangcent merged commit 367e792 into main Sep 30, 2026
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Populate Tags and Headers from framework metadata across all collectors

2 participants