Closed (fixed)
Project:
Mapillary
Version:
1.0.x-dev
Component:
Code
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Reporter:
Created:
9 Oct 2026 at 18:42 UTC
Updated:
24 Oct 2026 at 13:15 UTC
Jump to comment: Most recent
The access token is stored in the base module (mapillary.settings:access_token), but the Graph API client that uses it, MapillaryApiClient, lives in mapillary_field, together with MapillaryApiCallEvent and MissingAccessTokenException. This causes several problems:
ApiSettingsForm builds the Graph API URL and the Authorization header by hand to check a new token.mapillary_detection, mapillary_map_feature and mapillary_logs depend on mapillary_field partly or only for the client and its event.\RuntimeException. MapillaryThumbnailRefresher::isServiceWideFailure() and isBadIdAnswer() unwrap the Guzzle exception with getPrevious() and parse the JSON body again to find the HTTP status and the Graph API error code.Retry-After header are not handled; the caller cannot tell a rate limit from any other failure without the same unwrapping.mapillary_field channel at error level, including expected answers such as 400 with error code 100 (a deleted or unknown image). The callers that run unattended (queue workers, the thumbnail refresher, Drush) log the same failure again, and Mapillary Logs logs it a third time when "Log errors" is on.MapillaryApiCallEvent, so Mapillary Logs never records it.get(), one per exit path.MapillaryLeafletWidgetBase) check the ID format. The Image, Sequence and Map Feature ID widgets, the image and sequence import forms and MapillaryDetectionImportForm send any input as '/' . $id, so an input such as me or 123/detections reaches another Graph API endpoint with the server token.mapillary_field, mapillary_logs and dblog, set a valid access token, and turn on "Log API calls" at /admin/config/services/mapillary/logs./admin/content/mapillary/images/import and import the image ID me./admin/reports/dblog. Mapillary Logs shows a request to /me, the account of the token owner.drush cset mapillary.settings access_token '', and import any image ID. The form shows "Mapillary access token is not configured.", and there is no mapillary_logs entry for it.MapillaryApiClient, MapillaryApiCallEvent and MissingAccessTokenException to the base module, and register the client as mapillary.api_client.MapillaryApiException (extends \RuntimeException), thrown for every failed request. It carries the HTTP status, the Graph API error code and, for a 429, the Retry-After value, and answers isNotFound(), isBadId() (404, or 400 with error code 100) and isServiceWide() (missing or rejected token, missing permissions, rate limits). MissingAccessTokenException extends it.getImage(), getImages() (the multi-ID lookup), getSequenceImageIds(), getMapFeature(), getDetections(). Image, map feature and detection IDs are digits only; sequence IDs are letters, digits, _ and -, as in the Leaflet widgets. An invalid ID throws a MapillaryApiException for which isBadId() is TRUE. The importers, the thumbnail refresher and mapillary_field_update_11102() use these methods. get() stays for other paths.checkToken() for ApiSettingsForm, so the form no longer builds the request itself.MapillaryApiCallEvent once, in a finally block, also when the token is missing.MapillaryThumbnailRefresher uses the exception methods instead of unwrapping Guzzle exceptions.mapillary_logs subscribes to the event under its new class. It keeps its mapillary_field dependency for now, because MapillaryViewerRenderEvent stays there.Removing the mapillary_field dependency of mapillary_detection and mapillary_map_feature is left to a follow-up that moves the image and sequence importer to mapillary_entity; both modules still need that importer.
Retry-After is read as seconds and as an HTTP date; an invalid ID sends no request; the event is dispatched exactly once on every path, including a missing token.MissingAccessTokenException.ApiSettingsFormTest, MapillaryThumbnailRefresherFailureTest, the importer tests and the Mapillary Logs tests for the new classes.mapillary so that drush updb rebuilds the container.mapillary_field channel; failed requests are logged by the caller and by Mapillary Logs.mapillary.api_client. mapillary_field.api_client stays as a deprecated alias.mapillary: MapillaryApiClient, MapillaryApiCallEvent, MissingAccessTokenException, MapillaryApiException. The old class names in mapillary_field stay as deprecated aliases, so type hints, event listeners and catch blocks that use them keep working.mapillary_field.api_call does not change.MapillaryApiException, a \RuntimeException, so code that catches \RuntimeException keeps working.MapillaryThumbnailRefresher::isServiceWideFailure() is deprecated in favour of MapillaryApiException::isServiceWide().mapillary_field logger channel no longer receives client errors.Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
orkutmuratyilmaz