Skip to content

Validate schedule-for-stop date before the agency lookup - #1385

Open
ARCoder181105 wants to merge 2 commits into
OneBusAway:mainfrom
ARCoder181105:fix/schedule-for-stop-date-validation-order
Open

Validate schedule-for-stop date before the agency lookup#1385
ARCoder181105 wants to merge 2 commits into
OneBusAway:mainfrom
ARCoder181105:fix/schedule-for-stop-date-validation-order

Conversation

@ARCoder181105

Copy link
Copy Markdown
Collaborator

Fixes #1382

Stacked on #1384 — its two commits show up in this diff until it merges. Review the last commit here.

What changed

The agency lookup ran before the date parameter was parsed, so an unparseable date on an ID whose agency prefix does not exist answered 404 instead of a field error.

The date's format is now checked up front, via a new utils.ValidateServiceDate that delegates to ParseDate so the validator and the parser cannot drift apart. Resolving the date to a service date still happens after the agency lookup, where the agency's timezone is available.

Spec

Minimal Guarantees: "When the date parameter is unrecognisable, a field-error response is returned before any stop lookup is attempted." Extension 2a specifies HTTP 400 with a fieldErrors.date array.

Verified against api.pugetsound.onebusaway.org:

GET schedule-for-stop/99_1001.json?date=garbage
OBA:     400 {"fieldErrors":{"date":[...]}}
maglev:  404 resource not found      (before this change)

Tests

TestScheduleForStopHandlerDateValidationPrecedesLookup covers unknown agency with a bad date, known agency with a bad date, and both unknown-agency and unknown-stop with a valid date still returning 404.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@burma-shave, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f8dbbb6-d505-4824-aa65-7d9be88a6164

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd8bea and 1141e4f.

📒 Files selected for processing (3)
  • internal/restapi/schedule_for_stop_handler.go
  • internal/restapi/schedule_for_stop_handler_test.go
  • internal/utils/validation.go

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.

The agency lookup ran first, so an unparseable date on an ID whose agency
prefix does not exist answered 404 instead of the field error the
reference server returns. The spec requires the field error to come back
before any stop lookup is attempted.

Check the date's format up front and keep resolving it to a service date
below, where the agency's timezone is available.
@ARCoder181105
ARCoder181105 force-pushed the fix/schedule-for-stop-date-validation-order branch from cb3817f to a495f99 Compare August 21, 2026 17:05
@burma-shave

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Head commit changed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sonarqubecloud

Copy link
Copy Markdown

@burma-shave burma-shave left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two findings from an automated review, both confirmed against this PR's actual diff (git diff origin/main...HEAD). Left as inline comments below.

Note: the review also flagged three other issues (a duplicated route-reference builder, a routeless-stop early-return removal, and an unrelated test-file change) but those all point at code outside this PR's diff — pre-existing code this branch didn't touch — so they're left out here rather than posted as inline comments GitHub would reject.

// ValidateServiceDate reports whether a service date parameter is parseable, in either
// of the forms ParseDate accepts. Handlers use it to reject a malformed date before
// looking up the agency whose timezone ParseDate then resolves the date against.
func ValidateServiceDate(date string) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ValidateServiceDate duplicates the pre-existing ValidateDate (line 95) instead of extending or replacing it. ValidateDate (YYYY-MM-DD only) appears unused anywhere in production code, so the file now carries two similarly-named date validators with overlapping scope — a future caller or bugfix can easily reach for the wrong one. Worth considering whether ValidateDate should be removed or merged into this new function instead of living alongside it.

@@ -46,12 +56,10 @@ func (api *RestAPI) scheduleForStopHandler(w http.ResponseWriter, r *http.Reques

if dateParam != "" {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment ("only fails on an unusable agency timezone") is inaccurate, and the branch below it looks unreachable now. ValidateServiceDate (line 36) already runs ParseDate(dateParam, time.UTC) and returns 400 on any format/bounds failure; neither of ParseDate's two success paths depends on the loc argument for success/failure, so once ValidateServiceDate has succeeded, ParseDate(dateParam, loc) here can't fail for any valid loc. A bad agency timezone is also already caught earlier by loadAgencyLocation (lines 48-52), before this second parse runs. Since this branch appears dead, could it be removed (or the comment corrected if there's a case I'm missing)?

@burma-shave burma-shave left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

OBA API Change Review: PR #1385 / current branch — single endpoint

Input: Working tree on branch pr-1385; open PR #1385 found and local HEAD matches PR head SHA.
Stated goal: Fix #1382schedule-for-stop should return a 400 fieldErrors.date response for an unparseable date even when the agency prefix is unknown.
Scope: Single endpoint.
Affected endpoint(s): schedule-for-stop.
Changes: Both production and test changes.

Overview

What this change does

Before this PR, /api/where/schedule-for-stop/{id}.json split the combined stop ID, looked up the agency, and only parsed date after the agency lookup. That meant:

  • GET /schedule-for-stop/25_<known-stop>.json?date=garbage returned the expected 400 field error.
  • GET /schedule-for-stop/99_1001.json?date=garbage, where agency 99 does not exist, returned 404 before Maglev ever examined the bad date.

The PR adds utils.ValidateServiceDate, which delegates to the existing ParseDate, and calls it before the agency lookup. The later timezone-aware date resolution still happens after the agency is loaded. So the bad-date / unknown-agency case now returns HTTP 400 with fieldErrors.date, while valid-date unknown agency/stop cases still return 404.

Domain background

schedule-for-stop accepts a combined stop ID shaped like {agencyId}_{stopId} and an optional service-date parameter. The service date may be YYYY-MM-DD or a Unix millisecond timestamp. For YYYY-MM-DD, Maglev eventually needs the agency timezone to resolve “midnight on that date,” but it does not need the agency just to determine whether the string is parseable. The OBA spec requires malformed field values to produce structured field errors before resource lookup failures take precedence.

Details ### Goal check

Goal check: schedule-for-stop

Stated goal: malformed date should return 400 fieldErrors.date regardless of whether the agency/stop exists; valid-date unknown agency/stop should remain 404; agency timezone should still be used for real date resolution.

  • ✓ Invalid date with unknown agency returns 400 before agency lookup.
  • ✓ Invalid date with known agency still returns 400.
  • ✓ Valid date with unknown agency still returns 404.
  • ✓ Valid date with unknown stop still returns 404.
  • ✓ Timezone-aware parsing remains after agency lookup.

Test coverage: Adequate for the central regression. The new test covers unknown-agency bad date, known-agency bad date, unknown-agency valid date, and unknown-stop valid date. Targeted test run passed:

go test -tags "sqlite_fts5 sqlite_math_functions" ./internal/restapi -run TestScheduleForStopHandlerDateValidationPrecedesLookup

Overall: fully closed.

Client impact

Caveat: client checkouts had anomalies: /workspace/wayfinder is on branch develop, and /workspace/maglev.wiki has an untracked file.

Client impact: schedule-for-stop

Behaviour Wayfinder/SDK iOS Android
Invalid date + unknown agency changes from 404 to 400 fieldErrors.date Minimal/direct-call impact only. JS SDK has date?: string; Wayfinder passes date through from a date picker and does not intentionally send malformed dates. No routine impact. iOS builds yyyy-MM-dd from Date, so malformed dates are not normally sent. No routine impact. Android endpoint accepts date: String?, but normal callers would send valid dates.

No response payload fields or success shapes change. The observable change is limited to an error-precedence edge case.

Spec check

Spec check: schedule-for-stop

  • Consistent — The wiki spec’s Minimal Guarantees explicitly says: “When the date parameter is unrecognisable, a field-error response is returned before any stop lookup is attempted.”
  • Consistent — Extension 2a specifies HTTP 400 with fieldErrors.date.
  • Consistent — Valid-date unknown stop/agency remains 404 in Maglev, matching the existing Implementation Decision that Maglev intentionally corrects legacy Java’s null-body unknown-stop defect.

Deviation recording: No new deviation needed. This change implements existing Maglev spec behavior rather than introducing a new legacy divergence.

Overall: spec-consistent.

Summary

PR #1385 is a narrow schedule-for-stop error-precedence fix. It moves date-format validation ahead of agency lookup while preserving timezone-aware service-date resolution after the agency is known. The implementation matches the stated issue and the wiki spec, keeps valid unknown-resource behavior unchanged, and adds focused regression coverage.

@burma-shave

Copy link
Copy Markdown
Collaborator

@ARCoder181105 checking in on this stack. This base PR still has requested changes open, so we cannot sensibly review further up the stack yet. Could you please address the requested changes here first, then let us know when the base is ready?

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.

schedule-for-stop: invalid date returns 404 instead of 400 for unknown agency prefix

2 participants