Skip to content

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

Open
ARCoder181105 wants to merge 3 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 3 commits into
OneBusAway:mainfrom
ARCoder181105:fix/schedule-for-stop-date-validation-order

Conversation

@ARCoder181105

@ARCoder181105 ARCoder181105 commented Aug 21, 2026

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.

Summary by CodeRabbit

  • Bug Fixes

    • Invalid service dates now return a clear field-level validation error, even when the requested stop or agency cannot be found.
    • Valid dates continue to return the appropriate not-found response for unknown agencies or stops.
    • Improved handling of unexpected date parsing failures associated with agency time zone data.
  • Tests

    • Added coverage for date validation order and responses across valid, invalid, known, and unknown lookup scenarios.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Team

Run ID: aa8a59b3-e8ef-4b55-a96e-970022c17b57

📥 Commits

Reviewing files that changed from the base of the PR and between fe8b0e1 and c91112d.

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

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The schedule-for-stop handler now validates the date parameter before agency and stop lookup. A new utility performs UTC-based service-date validation. Tests cover invalid dates and valid-date not-found responses.

Changes

Schedule-for-stop validation

Layer / File(s) Summary
Service-date validation and response handling
internal/utils/validation.go, internal/restapi/schedule_for_stop_handler.go, internal/restapi/schedule_for_stop_handler_test.go
The handler validates non-empty dates before lookup and returns a date field error for invalid input. Later parse failures return a server error. Table-driven tests cover unknown agencies, known agencies, and unknown stops.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to c9111

Invalid schedule-for-stop dates now consistently return a date field error before lookup, while valid dates retain existing not-found behavior and agency-timezone resolution. The covered behavior is ready to merge.

Suggested reviewers: burma-shave, 3rabiii

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. 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 clearly and concisely describes the primary change: validating the schedule-for-stop date before the agency lookup.
Linked Issues check ✅ Passed The changes satisfy issue #1382. They validate the date before agency and stop lookups, return HTTP 400 with a date field error for invalid dates, preserve 404 responses for valid dates with unknown a…
Out of Scope Changes check ✅ Passed All changes support issue #1382. The handler update, validation helper, and focused tests are within scope.
  • Fix all pre-merge checks with AI

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.

@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?

@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?

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

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