Skip to content

refactor: use structs for location params - #892

Merged
fletcherw merged 3 commits into
OneBusAway:mainfrom
fletcherw:location_params
Apr 26, 2026
Merged

refactor: use structs for location params#892
fletcherw merged 3 commits into
OneBusAway:mainfrom
fletcherw:location_params

Conversation

@fletcherw

@fletcherw fletcherw commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Update several functions that passed LocationParams individually to just accept a LocationParams struct directly.

Move checkIfOurOfBounds logic into manager to remove some duplication.

Summary by CodeRabbit

Release Notes

Refactors

  • Unified location input across location-based endpoints so handlers accept a single location object for searches.
  • Moved out-of-bounds checking into the shared manager to provide consistent range/coverage behavior.
  • Improved nearby-results ranking by standardizing distance calculation and sorting for more accurate closest-first ordering.

Update several functions that passed LocationParams individually to just
accept a LocationParams struct directly.

Move checkIfOurOfBounds logic into manager to remove some duplication.
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Consolidates location query parameters into a new gtfs.LocationParams type and updates GTFS manager methods, REST handlers, and tests to use it; adds BoundsFromParams and Manager.CheckIfOutOfBounds to centralize bounds/radius logic and adjusts distance sorting in the manager.

Changes

Cohort / File(s) Summary
GTFS Manager API
internal/gtfs/gtfs_manager.go
Method signatures changed to accept *LocationParams for location queries (GetStopsForLocation, GetStopsInBounds, GetStopIDsWithinBounds, GetRoutesForLocation). Distance-based ordering now uses slices.SortFunc with cmp.Compare and per-stop distances computed from loc.Lat/loc.Lon. Removed local bounds helper and dependency on local default radius.
GTFS Location Parameters Module
internal/gtfs/location_params.go
New LocationParams type with Lat, Lon, Radius, LatSpan, LonSpan. Added BoundsFromParams(loc *LocationParams) utils.CoordinateBounds and (*Manager) CheckIfOutOfBounds(loc *LocationParams) bool to compute bounds and check region overlap.
REST API parameter parsing
internal/restapi/location_params.go
Removed file-local LocationParams type; parseLocationParams now returns *gtfs.LocationParams and the package imports gtfs.
REST API handlers
internal/restapi/stops_for_location_handler.go, internal/restapi/routes_for_location_handler.go, internal/restapi/trips_for_location_handler.go
Handlers pass loc *gtfs.LocationParams into GTFS manager methods, set/default loc.Radius in-place, and replace local checkIfOutOfBounds logic with api.GtfsManager.CheckIfOutOfBounds(loc). Removed local bounding helpers.
Stop lookup & related handlers/tests
internal/restapi/arrivals_and_departure_for_stop.go, internal/restapi/arrivals_and_departures_for_stop_handler_test.go, internal/restapi/context_cancellation_test.go, internal/gtfs/gtfs_manager_test.go, internal/restapi/trips_for_location_handler_test.go
Call sites updated to construct and pass &gtfs.LocationParams{Lat:…, Lon:…, Radius:…} (and spans where applicable) to new manager method signatures; one test widened lat/lon spans. Minor import updates to include gtfs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Ahmedhossamdev
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: use structs for location params' accurately summarizes the main change: consolidating multiple location parameters into a single struct throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/restapi/trips_for_location_handler.go (1)

133-141: Consider simplifying by passing loc directly to the handler.

The parseAndValidateRequest function parses location into a *internalgtfs.LocationParams at line 133-134, then immediately unpacks it back into separate lat, lon, latSpan, lonSpan variables at lines 137-140, only to reconstruct new LocationParams structs at lines 40 and 120.

This could be simplified by returning loc directly from parseAndValidateRequest and using it throughout the handler, avoiding the unpacking/repacking cycle.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/restapi/trips_for_location_handler.go` around lines 133 - 141,
parseLocationParams is parsed into a *internalgtfs.LocationParams then
immediately unpacked into lat/lon/latSpan/lonSpan only to be reconstructed
later; change parseAndValidateRequest (or the handler signature) to return or
accept the *internalgtfs.LocationParams directly so the handler uses that loc
throughout instead of unpacking/repacking. Update the code paths that currently
build new LocationParams (the constructions near the top and around where trips
are requested) to consume the existing loc pointer, and adjust call sites of
parseAndValidateRequest/parseLocationParams to propagate the
*internalgtfs.LocationParams return value instead of separate primitives. Ensure
any nil checks and validation remain (use loc == nil) and remove the redundant
variable unpacking (lat, lon, latSpan, lonSpan).
internal/gtfs/location_params.go (1)

23-27: Handle NoRadiusLimit sentinel value in radius fallback.

The constant NoRadiusLimit = -1 is defined in gtfs_manager.go. If a caller passes Radius: -1 (NoRadiusLimit) without valid LatSpan/LonSpan, this code will pass -1 to CalculateBounds, potentially producing invalid bounds.

Current call sites appear safe (they provide valid spans when using -1), but this is fragile.

Proposed fix
 	radius := loc.Radius
-	if radius == 0 {
+	if radius <= 0 {
 		radius = models.DefaultSearchRadiusInMeters
 	}
 	return utils.CalculateBounds(loc.Lat, loc.Lon, radius)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/gtfs/location_params.go` around lines 23 - 27, The current radius
fallback uses loc.Radius directly and can pass the sentinel NoRadiusLimit (-1)
into utils.CalculateBounds; change the logic in the function that computes
bounds (the block using loc.Radius, models.DefaultSearchRadiusInMeters and
calling utils.CalculateBounds) so that if loc.Radius == 0 OR loc.Radius ==
NoRadiusLimit and both loc.LatSpan and loc.LonSpan are zero, you set radius to
models.DefaultSearchRadiusInMeters before calling utils.CalculateBounds; only
allow NoRadiusLimit (-1) to be used as-is when a caller also provides non-zero
loc.LatSpan or loc.LonSpan. This ensures utils.CalculateBounds never receives -1
unexpectedly while preserving the intended NoRadiusLimit behavior when spans are
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/restapi/arrivals_and_departure_for_stop.go`:
- Around line 642-643: The LocationParams is currently setting LatSpan and
LonSpan which causes boundsFromParams to prefer span-based bounds and ignore
Radius; update the construction of the params passed to
api.GtfsManager.GetStopIDsWithinBounds so that LatSpan and LonSpan are omitted
or set to zero (e.g., instantiate &internalgtfs.LocationParams{Lat: lat, Lon:
lon, Radius: 10000}) so the radius-based search is used when calling
GetStopIDsWithinBounds.

In `@internal/restapi/trips_for_location_handler.go`:
- Line 40: Remove the unnecessary Radius: -1 field from the LocationParams
passed to api.GtfsManager.GetStopsInBounds on this call; update the call that
currently uses &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: -1,
LatSpan: latSpan, LonSpan: lonSpan} to omit Radius entirely (use
&internalgtfs.LocationParams{Lat: lat, Lon: lon, LatSpan: latSpan, LonSpan:
lonSpan}) so it matches the other call pattern and avoids violating the
non-negative radius validation in GetStopsInBounds.

---

Nitpick comments:
In `@internal/gtfs/location_params.go`:
- Around line 23-27: The current radius fallback uses loc.Radius directly and
can pass the sentinel NoRadiusLimit (-1) into utils.CalculateBounds; change the
logic in the function that computes bounds (the block using loc.Radius,
models.DefaultSearchRadiusInMeters and calling utils.CalculateBounds) so that if
loc.Radius == 0 OR loc.Radius == NoRadiusLimit and both loc.LatSpan and
loc.LonSpan are zero, you set radius to models.DefaultSearchRadiusInMeters
before calling utils.CalculateBounds; only allow NoRadiusLimit (-1) to be used
as-is when a caller also provides non-zero loc.LatSpan or loc.LonSpan. This
ensures utils.CalculateBounds never receives -1 unexpectedly while preserving
the intended NoRadiusLimit behavior when spans are present.

In `@internal/restapi/trips_for_location_handler.go`:
- Around line 133-141: parseLocationParams is parsed into a
*internalgtfs.LocationParams then immediately unpacked into
lat/lon/latSpan/lonSpan only to be reconstructed later; change
parseAndValidateRequest (or the handler signature) to return or accept the
*internalgtfs.LocationParams directly so the handler uses that loc throughout
instead of unpacking/repacking. Update the code paths that currently build new
LocationParams (the constructions near the top and around where trips are
requested) to consume the existing loc pointer, and adjust call sites of
parseAndValidateRequest/parseLocationParams to propagate the
*internalgtfs.LocationParams return value instead of separate primitives. Ensure
any nil checks and validation remain (use loc == nil) and remove the redundant
variable unpacking (lat, lon, latSpan, lonSpan).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b16ae9-8dd9-420d-841b-07185e5e7177

📥 Commits

Reviewing files that changed from the base of the PR and between fc7a10a and 2bf405a.

📒 Files selected for processing (10)
  • internal/gtfs/gtfs_manager.go
  • internal/gtfs/gtfs_manager_test.go
  • internal/gtfs/location_params.go
  • internal/restapi/arrivals_and_departure_for_stop.go
  • internal/restapi/arrivals_and_departures_for_stop_handler_test.go
  • internal/restapi/context_cancellation_test.go
  • internal/restapi/location_params.go
  • internal/restapi/routes_for_location_handler.go
  • internal/restapi/stops_for_location_handler.go
  • internal/restapi/trips_for_location_handler.go

Comment on lines +642 to +643
loc := &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000, LatSpan: 100, LonSpan: 100}
nearbyIDs := api.GtfsManager.GetStopIDsWithinBounds(ctx, loc, 5)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how GetStopIDsWithinBounds handles the combination of Radius and LatSpan/LonSpan
ast-grep --pattern $'func ($_ *Manager) GetStopIDsWithinBounds($$$) $_ {
  $$$
}'

# Check if boundsFromParams prioritizes one over the other
rg -n "LatSpan|LonSpan|Radius" internal/gtfs/location_params.go -B 2 -A 2

Repository: OneBusAway/maglev

Length of output: 2388


Remove LatSpan and LonSpan to use the intended radius-based search.

The boundsFromParams function prioritizes span-based bounds when both LatSpan > 0 and LonSpan > 0, completely ignoring the Radius parameter. Setting LatSpan: 100, LonSpan: 100 causes a continent-scale search (~11,000km per side) instead of the intended 10km radius. Change to:

Diff
-loc := &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000, LatSpan: 100, LonSpan: 100}
+loc := &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loc := &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000, LatSpan: 100, LonSpan: 100}
nearbyIDs := api.GtfsManager.GetStopIDsWithinBounds(ctx, loc, 5)
loc := &internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000}
nearbyIDs := api.GtfsManager.GetStopIDsWithinBounds(ctx, loc, 5)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/restapi/arrivals_and_departure_for_stop.go` around lines 642 - 643,
The LocationParams is currently setting LatSpan and LonSpan which causes
boundsFromParams to prefer span-based bounds and ignore Radius; update the
construction of the params passed to api.GtfsManager.GetStopIDsWithinBounds so
that LatSpan and LonSpan are omitted or set to zero (e.g., instantiate
&internalgtfs.LocationParams{Lat: lat, Lon: lon, Radius: 10000}) so the
radius-based search is used when calling GetStopIDsWithinBounds.

Comment thread internal/restapi/trips_for_location_handler.go Outdated

@Ahmedhossamdev Ahmedhossamdev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work

Remove custom bounding box logic, reuse logic from gtfsmanager.
Fix bug that was revealed by this change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/gtfs/location_params.go`:
- Around line 19-28: BoundsFromParams currently dereferences a nilable
*LocationParams and can panic; change its signature from func
BoundsFromParams(loc *LocationParams) to func BoundsFromParams(loc
LocationParams) (and update upstream callers and manager methods to accept a
value) so nil is impossible, or alternatively add an immediate nil-guard at the
top of BoundsFromParams that returns a sensible default bounds when loc==nil;
ensure any references to loc.Lat/loc.Lon/loc.Radius are updated to the value
parameter (or guarded) and keep the same calls to utils.CalculateBoundsFromSpan
and utils.CalculateBounds with models.DefaultSearchRadiusInMeters fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ceb407e-2d51-441b-b19c-42062602c160

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf405a and 4363a74.

📒 Files selected for processing (8)
  • internal/gtfs/gtfs_manager.go
  • internal/gtfs/gtfs_manager_test.go
  • internal/gtfs/location_params.go
  • internal/restapi/arrivals_and_departure_for_stop.go
  • internal/restapi/arrivals_and_departures_for_stop_handler_test.go
  • internal/restapi/stops_for_location_handler.go
  • internal/restapi/trips_for_location_handler.go
  • internal/restapi/trips_for_location_handler_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/restapi/trips_for_location_handler_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/restapi/arrivals_and_departures_for_stop_handler_test.go
  • internal/restapi/arrivals_and_departure_for_stop.go
  • internal/restapi/stops_for_location_handler.go
  • internal/restapi/trips_for_location_handler.go

Comment on lines +19 to +28
func BoundsFromParams(loc *LocationParams) utils.CoordinateBounds {
if loc.LatSpan > 0 && loc.LonSpan > 0 {
return utils.CalculateBoundsFromSpan(loc.Lat, loc.Lon, loc.LatSpan/2, loc.LonSpan/2)
}
radius := loc.Radius
if radius == 0 {
radius = models.DefaultSearchRadiusInMeters
}
return utils.CalculateBounds(loc.Lat, loc.Lon, radius)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard shared bounds conversion against nil input to prevent panics.

BoundsFromParams dereferences loc unconditionally. Because multiple manager APIs now accept *LocationParams, a nil value will crash request handling instead of returning a safe response.

A robust fix is to make LocationParams a value parameter on this helper (and upstream manager methods), so nil is impossible by type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/gtfs/location_params.go` around lines 19 - 28, BoundsFromParams
currently dereferences a nilable *LocationParams and can panic; change its
signature from func BoundsFromParams(loc *LocationParams) to func
BoundsFromParams(loc LocationParams) (and update upstream callers and manager
methods to accept a value) so nil is impossible, or alternatively add an
immediate nil-guard at the top of BoundsFromParams that returns a sensible
default bounds when loc==nil; ensure any references to
loc.Lat/loc.Lon/loc.Radius are updated to the value parameter (or guarded) and
keep the same calls to utils.CalculateBoundsFromSpan and utils.CalculateBounds
with models.DefaultSearchRadiusInMeters fallback.

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.

2 participants