Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept request URL in lieu of configuration file #728

Merged
merged 5 commits into from Aug 23, 2022
Merged

Conversation

1ec5
Copy link
Contributor

@1ec5 1ec5 commented Aug 20, 2022

Overhauled the command line tool’s configuration option. Instead of the --config option that takes a JSON configuration file path, the tool now takes a single positional argument that can either be a JSON configuration file path or a Directions API or Map Matching API request URL. When you pass in a request URL, the URL is round-tripped through a DirectionsOptions object back to a URL, exercising the entire typical code path (and then some).

But if you do decide to pass in a JSON configuration file instead of a URL, the command line tool now respects the MAPBOX_HOST environment variable alongside the MAPBOX_ACCESS_TOKEN environment variable. An access token is scoped to a single API endpoint, so this configuration option makes it possible to run the tool against a proxy or staging server for testing purposes.

RouteOptions(url:) now returns nil if given a Mapbox Map Matching API request URL, and MatchOptions(url:) returns nil if given a Mapbox Directions API request URL. Previously, you could mix and match URLs to convert between RouteOptions and MatchOptions. Now, you’ll need to use JSONEncoder and JSONDecoder as well:

guard let url = URL(string: "https://api.mapbox.com/directions/v5/mapbox/driving-traffic/…"),
    let routeOptions = RouteOptions(url: url),
    let encodedOptions = try? JSONEncoder().encode(self) else { return }
try JSONDecoder().decode(MatchOptions.self, from: encodedOptions)

#564 would unblock a more convenient conversion workflow.

Some tangential changes along the way:

  • When the MAPBOX_ACCESS_TOKEN environment variable is unset, the tool exits with an error code instead of crashing.
  • When a JSON configuration file path is passed into the tool, any tilde is expanded during validation, fixing an issue where an otherwise valid file path got rejected.
  • Removed mentions of the --waypoints and --url options from the command line tool’s documentation that probably came from Convert from Directions API URL to RouteOptions #580.

Fixes #692 and fixes #724. Supersedes #580. Depends on #727.

/cc @mapbox/navigation-ios @purew

Force-unwrapping the access token potentially crashes if it isn’t set in the environment. Instead, gently try to get the access token and return an error code if it’s unavailable.
Replaced the --config option with a positional argument that is either a path to the JSON configuration file, as before, or the URL to a Directions or Map Matching API request.

Expand a tilde in the configuration file path when validating the arguments.

Removed a passage from the documentation that refers to a not-yet-implemented feature.
@1ec5 1ec5 added bug op-ex Refactoring, Tech Debt or any other operational excellence work. command line tool feature New feature request. labels Aug 20, 2022
@1ec5 1ec5 self-assigned this Aug 20, 2022
}

let hostURL: URL?
if let host = ProcessInfo.processInfo.environment["MAPBOX_HOST"] ??
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this environment variable is consistent with the Mapbox Python SDK, which similarly reads credentials from the environment. It differs from the MGLMapboxAPIBaseURL Info.plist key that applications would set, but the naming conventions for environment variables are different than for Info.plist keys anyways.

struct ProcessingOptions: ParsableArguments {

@Option(name: [.short, .customLong("input")], help: "[Optional] Filepath to the input JSON. If no filepath provided - will fall back to Directions API request using locations in config file.")
var inputPath: String?

@Option(name: [.short, .customLong("config")], help: "Filepath to the JSON, containing serialized Options data.")
var configPath: String
@Argument(help: "Path to a JSON file containing serialized RouteOptions or MatchOptions properties, or the full URL of a Mapbox Directions API or Mapbox Map Matching API request.")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of unwieldy as an argument description. A possible future improvement would be to accept multiple configurations, perhaps a mix of files and URLs, and merge them somehow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will you document that possible future improvement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s track it as tail work in #729.

Comment on lines +177 to +178
urlWithAccessToken.queryItems = (urlWithAccessToken.queryItems ?? []) + [.init(name: "access_token", value: self.credentials.accessToken)]
let credentials = Credentials(requestURL: urlWithAccessToken.url!)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was pretty annoying: Credentials(url:) calls the required initializer Credentials(accessToken:host:). If the URL doesn’t have the access_token query parameter set, then the required initializer gets a nil access token, resulting in a precondition failure:

precondition(accessToken != nil && !accessToken!.isEmpty, "A Mapbox access token is required. Go to <https://account.mapbox.com/access-tokens/>. In Info.plist, set the MBXAccessToken key to your access token, or use the Directions(accessToken:host:) initializer.")

The code here has no opportunity to fall back to the access token from the environment, so instead it has to proactively fall back inside the URL before initializing the Credentials object.

directionsOptions = try decoder.decode(OptionsType.self, from: configData)
} else if let url = URL(string: options.config) {
// Try to convert the URL to an options object.
if let parsedOptions = (RouteOptions(url: url) ?? MatchOptions(url: url)) as? OptionsType {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why DirectionsOptions(url:) has to return nil when given a URL whose path conflicts with the abridgedPath property. If it doesn’t return nil, then either RouteOptions and MatchOptions would be equally likely to initialize based on the same URL, mooting the coalescing operator.

Maybe it would be cleaner to nix the OptionsType generic parameter in favor of an enumeration with associated values that gets passed into this method, since the two APIs aren’t all that different. But I’ll leave it for now in case another API needs this kind of flexibility.

Base automatically changed from 1ec5-xcode13-725 to main August 22, 2022 17:37
@1ec5 1ec5 merged commit 8ded1f6 into main Aug 23, 2022
@1ec5 1ec5 deleted the 1ec5-cli-credentials branch August 23, 2022 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug command line tool feature New feature request. op-ex Refactoring, Tech Debt or any other operational excellence work.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to configure API endpoint using command line tool Command line tool should accept URL as input
2 participants