From f8fad9b162c7bf0f35032a4b8aae839b2083624e Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 23 Sep 2026 18:32:57 -0400 Subject: [PATCH 1/2] Add mapbox isochrone contours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second of the Navigation-category APIs with no prior CLI coverage. Same shape as mapbox directions route (mapbox/mapbox-cli#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 --- CHANGELOG.md | 30 +++- README.md | 1 + .../isochrone/openapi/isochrone.yaml | 164 ++++++++++++++++++ docs/commands.md | 95 ++++++++++ src/remedy.rs | 4 + src/spec.rs | 43 ++++- tests/fixtures/api_command_surface.txt | 1 + 7 files changed, 328 insertions(+), 10 deletions(-) create mode 100644 custom-openapi/isochrone/openapi/isochrone.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index edd8e26..eca8a84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,16 +19,28 @@ that may never merge. They are not releases and are not listed here. ## Unreleased +- `mapbox isochrone`, how far you can get from a point in a given time or + distance, for driving (with or without live traffic), walking, or + cycling, returned as GeoJSON polygons or linestrings. No subcommand: + like `mapbox directions` below, this API has one operation, so there's + nothing a second word would disambiguate; see `spec::FLATTENED_SERVICES`. + Hand-authored into `custom-openapi/` for the same reason `mapbox + directions` was: no upstream spec exists yet. Reuses `directions`'s fix + for a spec parameter named `profile` colliding with the global + `--profile` flag (`ARG_NAME_OVERRIDES` already covered the mechanism, + this is just a second row, not a second fix) and its free-form (not + `enum`) routing profile, for the same OEM-account reason. + - `mapbox directions`, routes between 2-25 waypoints for driving (with or without live traffic), walking, or cycling. No subcommand: this API - has one operation, so — like `mapbox usage` — there's nothing a second + has one operation, so, like `mapbox usage`, there's nothing a second word would disambiguate; see `spec::FLATTENED_SERVICES`. Hand-authored - into `custom-openapi/` rather than waiting on an upstream spec — the whole - Navigation API category had no CLI coverage before this; excludes the - ~30 electric-vehicle-routing parameters (`engine=electric` and everything - under it), which describe one vehicle's charge/discharge curve down to - the watt and are a poor fit for a hand-typed CLI flag — left for a - follow-up. + into `custom-openapi/` rather than waiting on an upstream spec. The + whole Navigation API category had no CLI coverage before this; excludes + the ~30 electric-vehicle-routing parameters (`engine=electric` and + everything under it), which describe one vehicle's charge/discharge + curve down to the watt and are a poor fit for a hand-typed CLI flag. + Left for a follow-up. The routing profile (`mapbox/driving` etc.) is a free-form value, not a fixed set of four: an early version rejected anything else client-side, @@ -43,8 +55,8 @@ that may never merge. They are not releases and are not listed here. outright; and a path parameter whose every legitimate value contains a literal `/` (`mapbox/driving`) was being percent-encoded to `%2F` by the same escaping that stops a free-text value from smuggling in extra path - segments — safe to skip for a parameter named in a small table - (`UNESCAPED_PATH_PARAMS`) as one whose values are trusted to carry that + segments. That's safe to skip for a parameter named in a small table + (`UNESCAPED_PATH_PARAMS`) whose values are trusted to carry that character on purpose. - `MAPBOX_CLI_EXTRA_QUERY` appends raw query parameters to every request, in diff --git a/README.md b/README.md index 6735806..2009662 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ Each API is a top-level subcommand, one sub-subcommand per operation: mapbox accounts * mapbox fonts * mapbox geocoder * +mapbox isochrone * mapbox search * mapbox sprites * mapbox static * diff --git a/custom-openapi/isochrone/openapi/isochrone.yaml b/custom-openapi/isochrone/openapi/isochrone.yaml new file mode 100644 index 0000000..538a9b0 --- /dev/null +++ b/custom-openapi/isochrone/openapi/isochrone.yaml @@ -0,0 +1,164 @@ +openapi: "3.0.0" +# `parse_spec` turns `info.description` below into this service's clap +# `long_about`, so it also reaches `mapbox isochrone --help`, `--schema` and +# `generate-skills` output. Keep it to API prose only — the provenance below +# is for whoever edits this file, not for a CLI user: +# +# Hand-authored down to the parameters documented at +# docs.mapbox.com/api/navigation/isochrone. See `custom-openapi/README.md` +# for how a file like this is wired in, and `directions.yaml`'s header for +# why `profile` needs `ARG_NAME_OVERRIDES` in `src/spec.rs` — the same +# reason applies here. +info: + title: "Mapbox Isochrone API" + description: >- + How far you can get from a point in a given time or distance, for + driving (with or without live traffic), walking, or cycling — as a + GeoJSON polygon or line per contour. + version: "0.0.0" +servers: + - url: https://api.mapbox.com + description: Isochrone API +paths: + /isochrone/v1/{profile}/{coordinates}: + get: + operationId: contours + summary: Isochrone contours around one point. + description: >- + Returns one contour per value in `--contours-minutes` or + `--contours-meters` (exactly one of the two is required; not + enforced before the request goes out — the API answers 422 if both + or neither are given), as GeoJSON linestrings or, with `--polygons`, + polygons. + parameters: + - name: "profile" + in: path + required: true + description: >- + The routing profile. `mapbox/driving-traffic` accounts for live + traffic conditions; `mapbox/driving` does not. + schema: + type: string + enum: + [ + "mapbox/driving-traffic", + "mapbox/driving", + "mapbox/walking", + "mapbox/cycling", + ] + example: "mapbox/driving" + - name: "coordinates" + in: path + required: true + description: "The isochrone center, `{longitude},{latitude}`." + schema: + type: string + minLength: 1 + example: "-122.42,37.78" + - name: "access_token" + in: query + required: true + description: "Mapbox API Access Token" + schema: + type: string + minLength: 1 + # Prose rather than an `enum`: up to 4 comma-separated integers, and + # the command builder turns a spec `enum` into a clap + # `PossibleValuesParser`, which accepts one value and would refuse a + # list. Same reasoning as `directions.yaml`'s `annotations`. + - name: "contours_minutes" + in: query + required: false + description: >- + Up to 4 times in minutes, 1-60, comma-separated and increasing — + one contour per value. Exactly one of this or + `--contours-meters` is required. + schema: + type: string + example: "5,10,15,20" + - name: "contours_meters" + in: query + required: false + description: >- + Up to 4 distances in meters, 1-100000, comma-separated and + increasing — one contour per value. Exactly one of this or + `--contours-minutes` is required. + schema: + type: string + example: "1000,5000" + - name: "contours_colors" + in: query + required: false + description: >- + A hex color per contour (no `#`), comma-separated — must match + the contour count. + schema: + type: string + example: "ff0000,00ff00" + - name: "polygons" + in: query + required: false + description: >- + Return each contour as a GeoJSON polygon instead of a + linestring. + schema: + type: boolean + - name: "denoise" + in: query + required: false + # A number before the range, not after: `first_sentence` in + # `src/main.rs` cuts a `--help` line at the first `.`, and + # `0.0-1.0:` on its own left `--help` showing just `0`. + # `--schema` and `docs/commands.md` still show it in full. + description: >- + A smaller value removes more of the smaller contours, 0.0-1.0, + defaulting to 1.0. + schema: + type: number + minimum: 0 + maximum: 1 + - name: "generalize" + in: query + required: false + description: >- + Douglas-Peucker simplification tolerance in meters. A higher + value is a coarser, smaller contour. + schema: + type: number + # Prose rather than an `enum`, for the reason given on + # `contours_minutes` above. + - name: "exclude" + in: query + required: false + description: >- + Road types to route around, comma-separated. Options are + `motorway`, `toll`, `ferry`, `unpaved`, `cash_only_tolls`. + schema: + type: string + example: "motorway,toll" + - name: "depart_at" + in: query + required: false + description: >- + Departure time, ISO 8601 — for `mapbox/driving-traffic`, which + live traffic conditions to route against. + schema: + type: string + responses: + "200": + description: >- + A GeoJSON `FeatureCollection`, one feature per contour, each + carrying `contour` (the minute or meter value), `metric` + (`time` or `distance`), and rendering hints (`color`, + `opacity`, and — with `--polygons` — `fill`/`fill-opacity`). + "401": + description: Unauthorized + "403": + description: Forbidden + "404": + description: Not Found — an invalid profile. + "422": + description: >- + Unprocessable Entity — invalid coordinates, an out-of-range + contour value, or neither/both of `contours_minutes` and + `contours_meters` given. diff --git a/docs/commands.md b/docs/commands.md index 83e55a8..efb5fa8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -81,6 +81,9 @@ nests, and is typed `mapbox styles draft get`. [geocoder.reverse](#mapbox-geocoder-reverse) · [geocoder.batch](#mapbox-geocoder-batch) +**[Isochrone](#isochrone)** — +[isochrone.contours](#mapbox-isochrone-contours) + **[Search](#search)** — [search.forward](#mapbox-search-forward) · [search.reverse](#mapbox-search-reverse) · [search.category](#mapbox-search-category) · @@ -1235,6 +1238,98 @@ A query malformed enough that its own list can't be built falls the whole batch back to pretty-printed JSON, same as one broken feature does for a single query. +--- +## Isochrone + +How far you can get from a point in a given time or distance, for driving +(with or without live traffic), walking, or cycling. Curated by hand down to +the parameters documented at docs.mapbox.com/api/navigation/isochrone — see +`custom-openapi/README.md` for why this command group doesn't come from the +vendored specs the way most others do. + +### `mapbox isochrone contours` + +One contour per value in `--contours-minutes` or `--contours-meters`, as +GeoJSON around the given center point. + +#### Parameters + +`` and `` (both positional) are required. +`` is one of `mapbox/driving-traffic`, `mapbox/driving`, +`mapbox/walking`, `mapbox/cycling`. `` is one +`{longitude},{latitude}` pair — unlike `directions route`, this command +takes a single center point, not a list of waypoints. + +Exactly one of `--contours-minutes` or `--contours-meters` is required by +the API, though nothing here enforces it before the request goes out. + +| Parameter | Effect | +| --- | --- | +| `--contours-minutes ` | Up to 4 times in minutes, 1-60, comma-separated and increasing. One contour per value. | +| `--contours-meters ` | Up to 4 distances in meters, 1-100000, comma-separated and increasing. One contour per value. | +| `--contours-colors ` | A hex color per contour (no `#`), comma-separated — must match the contour count. | +| `--polygons` | Return each contour as a GeoJSON polygon instead of a linestring. | +| `--denoise <0.0-1.0>` | A smaller value removes more of the smaller contours. Defaults to 1.0. | +| `--generalize ` | Douglas-Peucker simplification tolerance — a higher value is a coarser, smaller contour. | +| `--exclude ` | Road types to route around, comma-separated (`motorway`, `toll`, `ferry`, `unpaved`, `cash_only_tolls`). | +| `--depart-at ` | For `mapbox/driving-traffic`, which live traffic conditions to route against. | + +#### Examples + +```sh +mapbox isochrone contours mapbox/driving "-122.42,37.78" --contours-minutes 5,10,15 +mapbox isochrone contours mapbox/walking "-122.42,37.78" --contours-minutes 5,10 --polygons +``` + +#### Outputs + +Captured live against `mapbox/walking`, two 5- and 10-minute contours as +polygons. This response is a real GeoJSON `FeatureCollection` — unlike +`directions route`'s response — but isochrone isn't one of the three +services (`search`, `geocoder`, `tilequery`) this CLI has a bespoke +list-per-feature rendering for yet (`output.rs`'s `list_rendering` is an +exact service allow-list, not a "looks like GeoJSON" test), so both output +modes print the same JSON, `-o text` pretty-printed and `-o json` on one +line — same shape as `directions route`'s Outputs section above: + + + + +
Terminal — -o textAgent — -o json
+ +```json +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "color": "#bf4040", + "contour": 10, + "fill": "#bf4040", + "fill-opacity": 0.33, + "fillColor": "#bf4040", + "fillOpacity": 0.33, + "metric": "time", + "opacity": 0.33 + }, + "geometry": { "type": "Polygon", "coordinates": "…" } + } + ] +} +``` + + + +```json +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"color":"#bf4040","contour":10,"fill":"#bf4040","fill-opacity":0.33,"fillColor":"#bf4040","fillOpacity":0.33,"metric":"time","opacity":0.33},"geometry":{"type":"Polygon","coordinates":"…"}}]} +``` + +
+ +Trimmed to one of the two features (the response has one per +`--contours-minutes` value) and the polygon's coordinates, for length. + --- ## Search diff --git a/src/remedy.rs b/src/remedy.rs index 7520844..63f129d 100644 --- a/src/remedy.rs +++ b/src/remedy.rs @@ -84,6 +84,10 @@ const SERVICE_DOCS: &[(&str, &str)] = &[ "geocoder", "https://docs.mapbox.com/api/search/geocoding-v6/", ), + ( + "isochrone", + "https://docs.mapbox.com/api/navigation/isochrone/", + ), ("search", "https://docs.mapbox.com/api/search/search-box/"), // Static Images and Static Tiles merged into one `static` command group // (#116); neither upstream page covers both, so this points at Static diff --git a/src/spec.rs b/src/spec.rs index b1ade70..9bc74a5 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -106,7 +106,10 @@ const BODY_CONTENT_TYPE_OVERRIDES: &[(&str, &str, &str)] = &[("styles", "starFil /// Kept as a table rather than a branch, for the same reason /// [`BODY_CONTENT_TYPE_OVERRIDES`] is: the fix sits next to the operation /// it's for, and outgrowing a global name later is just deleting a row. -const ARG_NAME_OVERRIDES: &[(&str, &str, &str)] = &[("directions", "profile", "routing-profile")]; +const ARG_NAME_OVERRIDES: &[(&str, &str, &str)] = &[ + ("directions", "profile", "routing-profile"), + ("isochrone", "profile", "routing-profile"), +]; /// The `arg_name` a parameter should present as, when its spec name collides /// with a global argument's id. See [`ARG_NAME_OVERRIDES`]. @@ -708,6 +711,10 @@ pub const CUSTOM_SPEC_ENTRIES: &[SpecEntry] = &[ name: "directions", yaml: include_str!("../custom-openapi/directions/openapi/directions.yaml"), }, + SpecEntry { + name: "isochrone", + yaml: include_str!("../custom-openapi/isochrone/openapi/isochrone.yaml"), + }, ]; /// The list the CLI actually generates commands from: [`MAPBOX_SPEC_ENTRIES`], @@ -2119,12 +2126,46 @@ paths: assert_eq!(op.command(), "svc list-styles"); } + /// Same regression as `the_directions_profile_parameter_does_not_collide…` + /// above, for the second spec that ran into it — `ARG_NAME_OVERRIDES` + /// taking effect is per-row, so a second entry earns its own proof + /// rather than trusting the first test to cover it. + #[test] + fn the_isochrone_profile_parameter_does_not_collide_with_the_global_flag() { + let spec = parse_spec( + "isochrone", + include_str!("../custom-openapi/isochrone/openapi/isochrone.yaml"), + ) + .expect("isochrone.yaml parses"); + + let contours = spec + .operations + .iter() + .find(|op| op.command_path == ["contours"]) + .expect("the contours operation exists"); + + let profile = contours + .path_params + .iter() + .find(|p| p.name == "profile") + .expect("a path parameter named profile"); + + assert_ne!( + profile.arg_name, "profile", + "must not collide with the global --profile id" + ); + } + #[test] fn arg_name_override_only_fires_for_the_row_it_names() { assert_eq!( arg_name_override("directions", "profile"), Some("routing-profile") ); + assert_eq!( + arg_name_override("isochrone", "profile"), + Some("routing-profile") + ); assert_eq!(arg_name_override("directions", "coordinates"), None); assert_eq!(arg_name_override("styles", "profile"), None); } diff --git a/tests/fixtures/api_command_surface.txt b/tests/fixtures/api_command_surface.txt index 21d1f9e..4a774b4 100644 --- a/tests/fixtures/api_command_surface.txt +++ b/tests/fixtures/api_command_surface.txt @@ -8,6 +8,7 @@ mapbox fonts upload | aliases: (none) mapbox geocoder batch | aliases: (none) mapbox geocoder forward | aliases: (none) mapbox geocoder reverse | aliases: (none) +mapbox isochrone contours | aliases: (none) mapbox search category | aliases: (none) mapbox search forward | aliases: (none) mapbox search list-category | aliases: (none) From 16e44edd5e947d1d6b2e9c4a4eadbe1cd3adecfe Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Fri, 25 Sep 2026 10:52:14 -0400 Subject: [PATCH 2/2] Flatten mapbox isochrone to a bare command, drop its profile enum Cascades the fix already shipped on mapbox directions: isochrone's API also has exactly one operation, so mapbox isochrone replaces mapbox isochrone contours , the same shape mapbox usage already has (spec::FLATTENED_SERVICES). And isochrone's own profile path parameter had the same closed four-value enum, which would reject an OEM account's undocumented profiles client-side; it's now free-form and reaches the URL unescaped via UNESCAPED_PATH_PARAMS, same mechanism as directions. Co-Authored-By: Claude Sonnet 5 --- README.md | 9 +++--- .../isochrone/openapi/isochrone.yaml | 24 +++++++++------ docs/commands.md | 30 +++++++++++-------- src/spec.rs | 23 ++++++++++++-- tests/fixtures/api_command_surface.txt | 2 +- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 2009662..473843e 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,6 @@ Each API is a top-level subcommand, one sub-subcommand per operation: mapbox accounts * mapbox fonts * mapbox geocoder * -mapbox isochrone * mapbox search * mapbox sprites * mapbox static * @@ -178,10 +177,10 @@ mapbox styles * mapbox tilesets * ``` -`mapbox directions` is the one exception: its API has a single operation, -so there's a bare command with no subcommand at all, the same shape -`mapbox usage` already has — see [docs/commands.md](./docs/commands.md) for -its own parameters. +`mapbox directions` and `mapbox isochrone` are the exceptions: each API has +a single operation, so there's a bare command with no subcommand at all, +the same shape `mapbox usage` already has, see +[docs/commands.md](./docs/commands.md) for their own parameters. A command group is not the same thing as a spec file: which one an operation belongs to is decided per operation. So `sprites` and `tilesets` are each diff --git a/custom-openapi/isochrone/openapi/isochrone.yaml b/custom-openapi/isochrone/openapi/isochrone.yaml index 538a9b0..88ef58b 100644 --- a/custom-openapi/isochrone/openapi/isochrone.yaml +++ b/custom-openapi/isochrone/openapi/isochrone.yaml @@ -34,18 +34,24 @@ paths: - name: "profile" in: path required: true + # Not an `enum`: the four documented values are what's public, but + # not what's exhaustive — some customers (OEM agreements, mainly) + # have additional profiles never published to docs.mapbox.com. + # An `enum` here becomes a clap `PossibleValuesParser` that + # rejects anything else client-side, which would break this CLI + # for exactly the accounts that most need a routing profile + # named beyond `driving`/`walking`/`cycling`. Same fix as + # `directions.yaml`'s `profile`. description: >- - The routing profile. `mapbox/driving-traffic` accounts for live - traffic conditions; `mapbox/driving` does not. + The routing profile — `mapbox/driving-traffic` (accounts for + live traffic), `mapbox/driving`, `mapbox/walking`, or + `mapbox/cycling` are documented, but not necessarily + exhaustive: some accounts have additional profiles of their + own. Sent exactly as typed; the API is the authority on + whether a value is valid, not this description. schema: type: string - enum: - [ - "mapbox/driving-traffic", - "mapbox/driving", - "mapbox/walking", - "mapbox/cycling", - ] + minLength: 1 example: "mapbox/driving" - name: "coordinates" in: path diff --git a/docs/commands.md b/docs/commands.md index efb5fa8..dfc9b21 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -81,8 +81,7 @@ nests, and is typed `mapbox styles draft get`. [geocoder.reverse](#mapbox-geocoder-reverse) · [geocoder.batch](#mapbox-geocoder-batch) -**[Isochrone](#isochrone)** — -[isochrone.contours](#mapbox-isochrone-contours) +**[Isochrone](#isochrone)** — [isochrone](#mapbox-isochrone) **[Search](#search)** — [search.forward](#mapbox-search-forward) · [search.reverse](#mapbox-search-reverse) · @@ -1247,18 +1246,23 @@ the parameters documented at docs.mapbox.com/api/navigation/isochrone — see `custom-openapi/README.md` for why this command group doesn't come from the vendored specs the way most others do. -### `mapbox isochrone contours` +### `mapbox isochrone` One contour per value in `--contours-minutes` or `--contours-meters`, as -GeoJSON around the given center point. +GeoJSON around the given center point. No subcommand: this API has one +operation, so there's nothing a second word would disambiguate, the same +reason `mapbox directions` has none either. #### Parameters `` and `` (both positional) are required. -`` is one of `mapbox/driving-traffic`, `mapbox/driving`, -`mapbox/walking`, `mapbox/cycling`. `` is one -`{longitude},{latitude}` pair — unlike `directions route`, this command -takes a single center point, not a list of waypoints. +`` is sent exactly as typed, not checked against a fixed +list: `mapbox/driving-traffic`, `mapbox/driving`, `mapbox/walking` and +`mapbox/cycling` are documented, but some accounts (OEM agreements, mainly) +have additional profiles of their own that were never published, the API +is the authority on whether a value is valid, not this page. `` +is one `{longitude},{latitude}` pair, unlike `mapbox directions`, this +command takes a single center point, not a list of waypoints. Exactly one of `--contours-minutes` or `--contours-meters` is required by the API, though nothing here enforces it before the request goes out. @@ -1277,20 +1281,20 @@ the API, though nothing here enforces it before the request goes out. #### Examples ```sh -mapbox isochrone contours mapbox/driving "-122.42,37.78" --contours-minutes 5,10,15 -mapbox isochrone contours mapbox/walking "-122.42,37.78" --contours-minutes 5,10 --polygons +mapbox isochrone mapbox/driving "-122.42,37.78" --contours-minutes 5,10,15 +mapbox isochrone mapbox/walking "-122.42,37.78" --contours-minutes 5,10 --polygons ``` #### Outputs Captured live against `mapbox/walking`, two 5- and 10-minute contours as -polygons. This response is a real GeoJSON `FeatureCollection` — unlike -`directions route`'s response — but isochrone isn't one of the three +polygons. This response is a real GeoJSON `FeatureCollection`, unlike +`mapbox directions`'s response, but isochrone isn't one of the three services (`search`, `geocoder`, `tilequery`) this CLI has a bespoke list-per-feature rendering for yet (`output.rs`'s `list_rendering` is an exact service allow-list, not a "looks like GeoJSON" test), so both output modes print the same JSON, `-o text` pretty-printed and `-o json` on one -line — same shape as `directions route`'s Outputs section above: +line, same shape as `mapbox directions`'s Outputs section above: diff --git a/src/spec.rs b/src/spec.rs index 9bc74a5..248292e 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -147,7 +147,7 @@ fn arg_name_override(service_name: &str, param_name: &str) -> Option<&'static st /// `generate-skills`, this file's own `command()` above — reads a /// [`FLATTENED_SERVICES`] service correctly for free, because they all go /// through `command()` rather than reconstructing the string themselves. -pub const FLATTENED_SERVICES: &[&str] = &["directions"]; +pub const FLATTENED_SERVICES: &[&str] = &["directions", "isochrone"]; /// (service, path parameter name) pairs whose value is trusted to reach the /// URL unescaped, because every legitimate value already contains a @@ -167,7 +167,8 @@ pub const FLATTENED_SERVICES: &[&str] = &["directions"]; /// (`mapbox/driving`, `mapbox/cycling`, an OEM's own profile name, …), which /// the routing profile's own path segment depends on reaching the API /// unescaped regardless of which spelling was typed. -pub const UNESCAPED_PATH_PARAMS: &[(&str, &str)] = &[("directions", "profile")]; +pub const UNESCAPED_PATH_PARAMS: &[(&str, &str)] = + &[("directions", "profile"), ("isochrone", "profile")]; /// The media types an operation's request body may be sent as. /// @@ -2154,6 +2155,24 @@ paths: profile.arg_name, "profile", "must not collide with the global --profile id" ); + // Deliberately not an `enum`: see `UNESCAPED_PATH_PARAMS`'s own doc + // comment for why a closed set was wrong here (OEM accounts have + // undocumented profiles of their own). + assert!( + profile.enum_values.is_empty(), + "profile must accept any value, not just the four documented ones" + ); + assert!( + UNESCAPED_PATH_PARAMS.contains(&("isochrone", "profile")), + "profile's literal `/` must still reach the URL unescaped, \ + now that it can't rely on being an enum to prove that" + ); + + // `isochrone` has exactly one operation and is in + // `FLATTENED_SERVICES` — `command()` must say so, dropping + // `command_path` from the string entirely, even though + // `command_path` itself stays `["contours"]` for internal lookups. + assert_eq!(contours.command(), "isochrone"); } #[test] diff --git a/tests/fixtures/api_command_surface.txt b/tests/fixtures/api_command_surface.txt index 4a774b4..4b89ea8 100644 --- a/tests/fixtures/api_command_surface.txt +++ b/tests/fixtures/api_command_surface.txt @@ -8,7 +8,7 @@ mapbox fonts upload | aliases: (none) mapbox geocoder batch | aliases: (none) mapbox geocoder forward | aliases: (none) mapbox geocoder reverse | aliases: (none) -mapbox isochrone contours | aliases: (none) +mapbox isochrone | aliases: (none) mapbox search category | aliases: (none) mapbox search forward | aliases: (none) mapbox search list-category | aliases: (none)
Terminal — -o textAgent — -o json