Skip to content

fix(swagger): correct /shapes filter[route] parameter description - #1066

Open
Redeem-Grimm-Satoshi wants to merge 1 commit into
mbta:masterfrom
Redeem-Grimm-Satoshi:fix/shapes-filter-route-docs
Open

fix(swagger): correct /shapes filter[route] parameter description#1066
Redeem-Grimm-Satoshi wants to merge 1 commit into
mbta:masterfrom
Redeem-Grimm-Satoshi:fix/shapes-filter-route-docs

Conversation

@Redeem-Grimm-Satoshi

@Redeem-Grimm-Satoshi Redeem-Grimm-Satoshi commented Aug 9, 2026

Copy link
Copy Markdown

Summary

Asana Ticket: n/a, outside contribution. Context and reproductions below.

Corrects the filter[route] parameter description on GET /shapes, which documents a JSON pointer that no longer exists in the response on current API versions. Documentation only, no behavior change.

Context

I built a trip planner on the V3 API for the 2026 World Cup matches at Boston Stadium: https://matchachusetts.up.railway.app/

The venue is Gillette Stadium in Foxborough, roughly 30 miles south of the city, so most of the useful advice for a visitor turned out to be transit advice. The core of the app is a map drawing nine MBTA routes as coloured polylines with live vehicle positions on top: Red, Orange, Blue, the four Green branches, SL1, and Foxboro/Franklin.

Drawing a route in its own colour means knowing which route each returned polyline belongs to. That is the only reason I went looking at /shapes relationships in the first place.

Problem

The swagger docs describe filter[route] on /shapes as filtering by /data/{index}/relationships/route/data/id. Reading that, the obvious approach is a single request for all nine routes, then reading the route id off each shape:

GET /shapes?filter[route]=Red,Orange,Blue,Green-B,...&include=route

That returns 200 with a flat list of shapes carrying no relationships and no included member. The documented pointer does not resolve, because shape resources have no relationships member at all on current versions:

curl -s 'https://api-v3.mbta.com/shapes?filter[route]=Red&page[limit]=1' | jq '.data[0] | keys'
# ["attributes","id","links","type"]

Because the response is a success, the natural first conclusion is that the query is wrong rather than that the endpoint does not do what the documentation describes. I spent a while varying include syntax and checking JSON:API conformance before working out that the pointer had simply gone stale.

Varying only the MBTA-Version header shows a clean boundary:

MBTA-Version relationships attributes
2017-11-28 through 2019-07-01 route, stops direction_id, name, polyline, priority
2020-05-01, 2021-01-09 (absent) polyline

That matches ApiWeb.ShapeView.relationships/2, which returns an empty map for versions at or after 2020-05-01. The removal looks deliberate and pairs with the same gate on attributes/2. It is only the generated documentation that was left behind, and since 2021-01-09 is the default, the stale text is what most callers read.

The mismatch is harder to spot than it should be because /shapes also accepts include without validating it, where other endpoints reject it:

/stops?filter[route]=Red&include=banana     -> 400
/trips?filter[route]=Red&include=banana     -> 400
/vehicles?filter[route]=Red&include=banana  -> 400
/shapes?filter[route]=Red&include=banana    -> 200

Solution

ShapeController.index used the shared filter_param(:id, name: :route) helper, which renders the relationship pointer automatically. That helper is correct for /stops, where StopView still emits a route relationship, but wrong here. This replaces it with an explicit parameter whose description says what the filter actually accepts, keeps the existing comma-separated-list convention and required: true, and notes the 2020-05-01 boundary so callers needing route attribution know to request one route at a time.

Before:

Filter by `/data/{index}/relationships/route/data/id`.

Multiple IDs **MUST** be a comma-separated (U+002C COMMA, ",") list.

After:

Filter by route ID. Multiple route IDs **MUST** be a comma-separated (U+002C COMMA, ",") list.

Note that the shape resource does not include a `route` relationship on API versions
`2020-05-01` and later, so shapes in the response cannot be attributed to a route from the
response body on those versions. Request a single route at a time if that attribution is
needed.

I deliberately scoped this to the documentation. Restoring the relationship would resolve the mismatch too, but the removal reads as intentional resource slimming and reversing it is your call, not something to slip into a docs fix.

The include validation is likewise left alone here. Returning 400 would be correct for consistency, but it converts a currently successful response into an error, and anyone passing include=route to /shapes today is getting a 200, so it presumably needs a new MBTA-Version the way the 2020-05-01 changes were handled. I have raised that on the massdotdevelopers list rather than assume a direction, and I am glad to write that patch if you tell me what shape it should take.

Verification

Built against Elixir 1.19.5 and OTP 28, matching .tool-versions:

  • mix compile --force --warnings-as-errors passes
  • mix format --check-formatted passes on the changed file
  • mix credo --strict reports no issues on the changed file
  • mix test test/api_web/controllers/shape_controller_test.exs passes, 14 tests and 0 failures, including show conforms to swagger response

I also checked the regenerated priv/static/swagger.json directly: /shapes no longer references relationships/route/data/id, required: true survives, and the comma-separated-list wording is unchanged.

The `filter[route]` parameter on `/shapes` was documented via the shared
`filter_param(:id, name: :route)` helper, which renders the description
"Filter by `/data/{index}/relationships/route/data/id`".

That JSON pointer cannot be resolved against a `/shapes` response on any
currently supported API version. `ApiWeb.ShapeView.relationships/2` returns
an empty map for versions >= 2020-05-01, so shape resources carry no
`relationships` member at all. The pointer is accurate for 2019-07-01 and
earlier, where the view still emits `route` and `stops`, but it has been
stale for the default version since 2020-05-01.

Because `/shapes` also accepts `include` without validating it and returns
200, a client that follows the documented pointer and requests
`include=route` receives a success response with no linkage data and no
diagnostic, which makes the mismatch difficult to diagnose from the outside.

This replaces the generated pointer text with an accurate description of
what the filter accepts, keeps the existing comma-separated-list convention
and `required: true`, and notes the 2020-05-01 boundary so callers needing
route attribution know to request a single route at a time.

No behavior change; documentation only.
@Redeem-Grimm-Satoshi
Redeem-Grimm-Satoshi requested a review from a team as a code owner August 9, 2026 05:23
@Redeem-Grimm-Satoshi
Redeem-Grimm-Satoshi requested review from zyxw59 and removed request for a team August 9, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant