Skip to content

fix: schedule-for-route spec compliance: invalid date 400, headsign fallback, route-scoped ServiceDateOutOfRange#1044

Open
Ahmedhossamdev wants to merge 2 commits into
mainfrom
fix/schedule-for-route-spec-gaps
Open

fix: schedule-for-route spec compliance: invalid date 400, headsign fallback, route-scoped ServiceDateOutOfRange#1044
Ahmedhossamdev wants to merge 2 commits into
mainfrom
fix/schedule-for-route-spec-gaps

Conversation

@Ahmedhossamdev

@Ahmedhossamdev Ahmedhossamdev commented Jun 10, 2026

Copy link
Copy Markdown
Member

Fixes: #992, #1042, #1043

Summary

Fixes three spec gaps found during review of the schedule-for-route endpoint.

1- Invalid date (HTTP 400)
Unparseable date params now return {"fieldErrors":{"date":[..]}} with HTTP 400,
matching the Java OBA reference implementation. Previously returned HTTP 200 & code 510.

2- Headsign fallback to last stop name
When a trip has no trip_headsign, the name of its last stop is used as the fallback,
per the spec. Previously such trips were silently skipped.

3- Route-scoped ServiceDateOutOfRange
Replaced the feed-wide end-date check with a RouteHasFutureService SQL query that
checks whether this specific route has calendar coverage after the requested date.
Routes that end before the feed does now correctly return ServiceDateOutOfRange
instead of NoServiceThatDay.

Summary by CodeRabbit

  • Bug Fixes

    • Improved date parameter validation in schedule queries with clearer error messages for invalid inputs.
    • Enhanced route service availability detection for more accurate schedule information.
  • Improvements

    • Refined trip destination information display when available in route schedules.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a database query to detect route future service availability and refactors the /scheduleForRoute handler to validate dates as field errors, use the new query to distinguish between out-of-range dates and dates with no service, and improve headsign generation with stop name fallback.

Changes

Schedule Handler Service Availability & Date Validation

Layer / File(s) Summary
RouteHasFutureService query definition
gtfsdb/query.sql, gtfsdb/query.sql.go
SQL query using SELECT EXISTS across calendar end dates and calendar_dates exceptions to determine if a route has trips with service after a given date.
GTFSDB statement lifecycle wiring
gtfsdb/db.go, gtfsdb/models.go
Adds routeHasFutureServiceStmt field to Queries struct and integrates it through Prepare, Close, and WithTx methods; updates sqlc version comments.
Field error response helper
internal/restapi/responses.go
New sendFieldError method returns HTTP 400 JSON responses with fieldErrors keyed by field name for input validation errors.
Handler date validation and service availability logic
internal/restapi/schedule_for_route_handler.go
Date parsing now triggers field errors for invalid inputs; removes GetFeedEndDate check; introduces noTripsResponse closure that calls RouteHasFutureService to decide between ServiceDateOutOfRange and NoServiceThatDay responses when no trips are found.
Headsign generation with stop name fallback
internal/restapi/schedule_for_route_handler.go
Enhanced headsign collection: when a trip lacks a headsign, falls back to the last stop name via additional stop lookups, then deduplicates against existing headsigns.
Test updates for date validation and service logic
internal/restapi/schedule_for_route_handler_test.go, internal/restapi/openapi_conformance_test.go
Updated assertions to expect HTTP 400 with fieldErrors for invalid dates instead of HTTP 510; added explicit date=2025-06-12 query parameter to conformance test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • OneBusAway/maglev#1043: Directly addresses the requirement to add RouteHasFutureService query and use it to decide ServiceDateOutOfRange vs NoServiceThatDay responses in the schedule handler.
  • OneBusAway/maglev#1042: Fixes the schedule handler to return HTTP 400 field errors for invalid/non-ISO date inputs instead of the previous HTTP 510 behavior.

Possibly related PRs

  • OneBusAway/maglev#911: Modifies schedule_for_route_handler.go and its tests for date handling and "no service/out-of-range" response flow.
  • OneBusAway/maglev#905: Updates schedule_for_route_handler_test.go assertions for date handling and service availability responses.

Suggested reviewers

  • burma-shave
  • aaronbrethorst
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Title check ✅ Passed The title accurately summarizes the three main fixes: invalid date validation returning 400, headsign fallback to stop names, and route-scoped ServiceDateOutOfRange logic.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/schedule-for-route-spec-gaps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.2 ms
Error rate 0.00%
Total requests 340
Req/sec 11.1

Smoke test config: 5 VUs x 30s. Thresholds: p(95) < 300ms, error rate < 1%.

Full results uploaded as workflow artifact: k6-smoke-summary.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/restapi/schedule_for_route_handler.go (1)

202-230: 💤 Low value

Consider deduplicating fallback stop IDs before querying.

When multiple trips lack headsigns and share the same last stop, the current implementation appends the same stopID multiple times to fallbackStopIDs (line 214), then queries all of them via GetStopsByIDs (line 218). While deduplication happens later when inserting into seenHeadsigns (line 225), the database query could be more efficient.

⚡ Optional optimization to deduplicate stop IDs
 // Collect headsigns; fall back to last stop name when a trip has no headsign.
 seenHeadsigns := make(map[string]bool)
 var headsigns []string
-var fallbackStopIDs []string
+fallbackStopIDSet := make(map[string]struct{})
 for _, trip := range tripsInGroup {
 	hs := trip.TripHeadsign.String
 	if hs != "" {
 		if !seenHeadsigns[hs] {
 			seenHeadsigns[hs] = true
 			headsigns = append(headsigns, hs)
 		}
 	} else if sts := stopTimesByTrip[trip.ID]; len(sts) > 0 {
-		fallbackStopIDs = append(fallbackStopIDs, sts[len(sts)-1].StopID)
+		fallbackStopIDSet[sts[len(sts)-1].StopID] = struct{}{}
 	}
 }
-if len(fallbackStopIDs) > 0 {
+if len(fallbackStopIDSet) > 0 {
+	fallbackStopIDs := make([]string, 0, len(fallbackStopIDSet))
+	for sid := range fallbackStopIDSet {
+		fallbackStopIDs = append(fallbackStopIDs, sid)
+	}
 	lastStops, err := api.GtfsManager.GtfsDB.Queries.GetStopsByIDs(ctx, fallbackStopIDs)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/schedule_for_route_handler.go` around lines 202 - 230, The
loop that builds fallbackStopIDs (iterating tripsInGroup and using
stopTimesByTrip) can append duplicate stop IDs, causing GetStopsByIDs to be
called with repeated IDs; before calling
api.GtfsManager.GtfsDB.Queries.GetStopsByIDs, deduplicate fallbackStopIDs (e.g.,
build a map[string]struct{} or seen map while collecting or transform
fallbackStopIDs into a unique slice) and pass the unique slice to GetStopsByIDs,
keeping the existing seenHeadsigns and headsigns logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/restapi/schedule_for_route_handler.go`:
- Around line 97-108: The noTripsResponse closure currently conflates DB errors
with no-future-service by treating any err from
GtfsManager.GtfsDB.Queries.RouteHasFutureService (RouteHasFutureService) as
ServiceDateOutOfRange; change it so that if err != nil the function returns a
500 server-error ResponseModel (include server error context), otherwise if
hasFuture == 0 return the 510 ServiceDateOutOfRange response, and otherwise call
buildNoServiceThatDayResponse as before; also update the call sites that invoke
noTripsResponse to handle/propagate the 500 case appropriately instead of
assuming a date-range error.

---

Nitpick comments:
In `@internal/restapi/schedule_for_route_handler.go`:
- Around line 202-230: The loop that builds fallbackStopIDs (iterating
tripsInGroup and using stopTimesByTrip) can append duplicate stop IDs, causing
GetStopsByIDs to be called with repeated IDs; before calling
api.GtfsManager.GtfsDB.Queries.GetStopsByIDs, deduplicate fallbackStopIDs (e.g.,
build a map[string]struct{} or seen map while collecting or transform
fallbackStopIDs into a unique slice) and pass the unique slice to GetStopsByIDs,
keeping the existing seenHeadsigns and headsigns logic unchanged.
🪄 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: ecf1b6c2-163a-4137-88cb-28246d258a77

📥 Commits

Reviewing files that changed from the base of the PR and between e78c4b7 and dd2cee6.

📒 Files selected for processing (8)
  • gtfsdb/db.go
  • gtfsdb/models.go
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/restapi/openapi_conformance_test.go
  • internal/restapi/responses.go
  • internal/restapi/schedule_for_route_handler.go
  • internal/restapi/schedule_for_route_handler_test.go

Comment thread internal/restapi/schedule_for_route_handler.go Outdated
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.1 ms
Error rate 0.00%
Total requests 337
Req/sec 11.0

Smoke test config: 5 VUs x 30s. Thresholds: p(95) < 300ms, error rate < 1%.

Full results uploaded as workflow artifact: k6-smoke-summary.

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.

Spec review: schedule-for-route

1 participant