Add mapbox directions route - #43
mattpodwysocki wants to merge 3 commits into
Conversation
The Navigation API category (Directions, Isochrone, Map Matching, Matrix, Optimization, EV Charge Finder) has had no CLI coverage — this is the first of those, hand-authored into custom-openapi/ since openapi-specs publishes no spec for it yet. Excludes the ~30 electric-vehicle-routing parameters (engine=electric and everything under it): those describe a vehicle's charging curve down to the watt, which an integration passes in from a vehicle profile rather than something to hand-type as a CLI flag. The EV Charge Finder API is a better fit for that case. Also excludes POST, which this spec format has no way to express alongside GET for the same operation — a real gap for a very long coordinate list, not a design choice. Two path-parameter bugs surfaced while wiring this up, fixed generically rather than just for this command: - A spec parameter literally named `profile` (the routing profile, mapbox/driving etc.) silently collided with the global --profile credentials flag — clap has one namespace of ids per command, and the generated positional replaced the global one outright instead of erring. `mapbox directions route mapbox/driving …` failed with `Invalid profile name "mapbox/driving"` before this. Fixed with a small, documented arg-name override table (ARG_NAME_OVERRIDES), the same shape BODY_CONTENT_TYPE_OVERRIDES already uses. - An enum-constrained path parameter whose only valid values contain a literal `/` (again, mapbox/driving) was being percent-encoded to %2F by the same escaping that stops a free-text path parameter from smuggling in extra path segments or retargeting the request. Safe to skip once clap's PossibleValuesParser has already limited the value to one of the spec's own literal strings — there is nothing left for a caller to inject. Smoke-tested against production: real routes for driving and walking profiles, with --steps, --geometries geojson, --overview full, and --annotations all verified to return the documented shape. 621 tests, fmt and clippy clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
first_sentence() in src/main.rs cuts a --help line at the first '.', which is right after "`mapbox/driving` only" or "`mapbox/driving` and `mapbox/driving-traffic`" on eight parameters here — a real sentence on its own that was eating everything substantive that followed it in --help. --schema and docs/commands.md were never affected; they show the full text. Fixed by moving the profile-scope note to the end of each description instead of the front, so the useful part survives the cut. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This sentence doesn't make a lot of sense. The EV Charge Finder API is for finding charging stations. The Please make sure to validate that the vibe-coding here is reviewed by folks that know the APIs properly - it seems that the LLM is coming to some faulty conclusions somehow. |
Two pieces of review feedback landed on this PR (danpat, and product guidance relayed separately), addressed together since both touch the same lines: 1. Product guidance was to drop the verb for single-operation Nav commands: mapbox directions instead of mapbox directions route, matching the shape mapbox usage already has. Generalized rather than hand-coded per service: spec::FLATTENED_SERVICES names which services get this, build_service_command attaches the one operation's own args directly to the service-level command instead of nesting it, and run()'s dispatch skips the subcommand walk for one of these. Every reader of a command's identity (--schema, docs/commands.md, generate-skills, remedy's suggestions) goes through Operation::command(), which is now flattening-aware, so they all render correctly for free. 2. Real bug, not just wording: the routing profile's enum (the four documented values) rejected anything else client-side via clap's PossibleValuesParser. Some accounts — OEM agreements, mainly — have additional profiles never published to docs.mapbox.com, so the enum would have broken this command for exactly the customers who need a profile named beyond driving/walking/cycling. Reported in review. profile is now a free-form string, sent exactly as typed. Removing the enum broke the mechanism that kept profile's literal `/` from being percent-encoded — that fix was keyed on "does this parameter have an enum", which stopped being true. Replaced with a named table, spec::UNESCAPED_PATH_PARAMS, keyed on (service, parameter name) instead: the same outcome, but as an explicit decision rather than a side effect of a constraint that needed removing for an unrelated reason. Also corrected a factual error a reviewer caught in this PR's own explanatory text: EV Charge Finder is a location-search product (charging stations near a point), not related to the Directions API's own engine=electric vehicle-profile parameters that were excluded — the two are unrelated capabilities, not alternatives for the same thing. Fixed everywhere that sentence appeared (custom-openapi/directions.yaml, CHANGELOG.md). 487 tests, fmt and clippy clean. Two new regression tests (a_flattened_service_takes_no_subcommand, command_only_drops_the_path_for_a_flattened_service) plus the existing path-escaping tests rewritten for the table-based mechanism instead of the enum-based one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@danpat you're right, and it wasn't just clumsy wording, it was actually wrong. EV Charge Finder finds charging station locations. I've fixed the wording in the spec header comment and updated the PR description to match: it now describes what Fair point on review too. Appreciate you catching it. |
Second of the Navigation-category APIs with no prior CLI coverage. Same shape as mapbox directions route (#43): hand-authored into custom-openapi/ since openapi-specs has no spec for this API either, and reuses that PR's fix for a spec parameter named `profile` colliding with the global --profile flag (ARG_NAME_OVERRIDES gets a second row, not a second mechanism). `contours_minutes` and `contours_meters` are mutually exclusive but neither is individually required by this CLI's own validation — same "not enforced before the request goes out" precedent search category already uses for its own proximity/near/bbox/route disjunction. The API answers 422 if both or neither are given. Smoke-tested against production: real contour polygons and linestrings for driving and walking profiles, --polygons, --contours-minutes with multiple values, verified to return the documented GeoJSON shape. Also fixes a self-inflicted --help regression found while writing this: first_sentence() in src/main.rs cuts a --help line at the first '.', and several profile-scoped parameter descriptions in directions.yaml (already merged in this branch) led with a complete sentence before the substantive content, e.g. "`mapbox/driving` only." — eating everything after it in --help. isochrone.yaml's own `denoise` had the same shape ("0.0-1.0: ...") and would have rendered as literally "0". Both fixed by moving the qualifier to the end of the description instead of the front. 485 tests, fmt and clippy clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Third of the Navigation-category APIs with no prior CLI coverage. Same shape as directions/isochrone (#43, #44): hand-authored into custom-openapi/ since openapi-specs has no spec for this API either, reusing ARG_NAME_OVERRIDES for the same profile-vs-global-flag collision (third row, not a third mechanism). Excludes POST, for the same documented reason directions route does: this spec format can't express "GET or POST, caller's choice" for one operationId, and the API's own POST exists specifically for a trace too long for a URL (~8100 bytes) — a real gap, not a design choice. Smoke-tested against production: a three-point San Francisco trace returned a real match with legs/steps/geometry, including a null tracepoint for a point too far from the road network to match — the documented shape for that case, not a bug. 486 tests, fmt and clippy clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
What
mapbox directions, the first command for the Navigation API category(Directions, Isochrone, Map Matching, Matrix). None of these had CLI
coverage before this. Hand-authored into
custom-openapi/sinceopenapi-specs doesn't publish a spec for the Directions API yet, following
the same convention
searchalready uses.No subcommand: this API has exactly one operation, so there's nothing a
second word (like the old
route) would disambiguate. Same shape asmapbox usage. Seespec::FLATTENED_SERVICES.Routes between 2-25 waypoints for driving (with or without live traffic),
walking, or cycling.
Excluded on purpose:
engine=electricandeverything under it). These describe one vehicle's charge/discharge
curve down to the watt, which the Directions API needs to plan charging
stops along the route. That's data an integration passes in from its own
vehicle profile, not something to hand-type as a CLI flag on every call.
Left for a follow-up.
choice" for the same operation. A real gap for a very long coordinate
list, not a design choice; noted in the spec's own header for whoever
picks it up.
Two path-parameter bugs found and fixed generically
Both surfaced from
profile's value (mapbox/drivingetc.):profilesilently collided with the global--profilecredentials flag. Clap has one namespace of ids percommand; the generated positional replaced the global one outright
instead of erring, so
mapbox directions mapbox/driving ...failed withInvalid profile name "mapbox/driving". Fixed with a small, documentedoverride table (
ARG_NAME_OVERRIDESinsrc/spec.rs), same shape asthe existing
BODY_CONTENT_TYPE_OVERRIDES./was being percent-encoded. The sameescaping that stops a free-text path parameter from smuggling in extra
path segments (
../../tokens/v2/victimetc.) was turningmapbox/driving's/into%2F, a 404.profileis a free-form valuenow, not a fixed enum (see below), so it needed its own opt-in signal
rather than riding on "has an enum":
UNESCAPED_PATH_PARAMSinsrc/spec.rsnames the (service, parameter) pairs whose values aretrusted to carry a structural character like
/on purpose. Newpath_segment_forinsrc/executor.rs, unit-tested against both listedand unlisted parameters.
Routing profile is free-form, not a fixed list of four
Caught in review: an earlier version of this PR validated
profileagainst the four documented values (
mapbox/driving-traffic,mapbox/driving,mapbox/walking,mapbox/cycling) client-side, via aclap enum. Some OEM accounts have additional profiles of their own that
are never published to docs.mapbox.com, so that validation would have
broken this command for exactly the accounts that most need it. Fixed:
profileis now sent exactly as typed, with the description saying soinstead of listing four values as if they were exhaustive.
Verification
Smoke-tested against production, not just unit tests: real routes for
mapbox/drivingandmapbox/walking, with--steps,--geometries geojson,--overview full, and--annotationsall verified to return thedocumented shape. Full transcript and captured response are in
docs/commands.md's new section.621 tests,
cargo fmt --checkandcargo clippy --all-targets -- -D warningsboth clean.Not done
Isochrone, Map Matching, and Matrix are tracked as separate follow-up PRs,
not bundled into this one.
🤖 Generated with Claude Code