Skip to content

Add a parser for obacloud's bundle-inputs manifest - #1814

Open
aaronbrethorst wants to merge 5 commits into
ab/multi-feed-a1b-plumbingfrom
ab/multi-feed-a2-manifest
Open

aaronbrethorst wants to merge 5 commits into
ab/multi-feed-a1b-plumbingfrom
ab/multi-feed-a2-manifest

Conversation

@aaronbrethorst

@aaronbrethorst aaronbrethorst commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Part of #1976

Stacked on #1813. Phase A.2 of the multi-feed static GTFS spec (#1811).

What it does

Adds internal/bundleinputs, a package that reads the bundle-inputs.json manifest (version 1) that obacloud publishes to list the per-agency feeds an OneBusAway server should load. Parse decodes and validates a manifest; Fetch reads one from an https:// URL or a local file. Manifest.StaticFeeds() maps each entry onto a gtfs-static-feeds item (id, url, default-agency-id). sha256 and byteSize are parsed and kept for later download verification; stopConsolidationUrl is parsed for phase D.

Validation that applies to every feed list, wherever it came from (unique ids, unique default agencies, no _ in agency ids, the per-feed checks), is shared with the file config: appconf.ValidateStaticFeeds is exported and the manifest calls it. The manifest adds only its own format rules: version 1, a non-empty list, id and defaultAgencyId required, feed and consolidation URLs must be http(s) (a remote manifest must not name local files), and a well-formed sha256. appconf.IsHTTPURL replaces three copies of the same prefix check.

Nothing reads the package yet; the config key that uses it is the next PR in the stack.

Root cause

Production deployments get their feed list from obacloud, not from a hand-written config. Maglev had no way to read that contract.

Testing

  • Test failing on main before this change: n/a (new package). Table-driven tests cover a golden manifest fixture, every validation rule (including a duplicate defaultAgencyId failing through the shared validator), trailing data after the JSON value, and Fetch over HTTP, from a local file, a 404, an over-size body, a directory and a missing file.
  • Manual check: n/a: the new package has no runtime caller yet, so the tests above are the check.
  • go vet with both tag sets, make test and go fmt pass.

Checklist

  • This PR addresses one issue.
  • It is under ~200 changed lines, or the description says why it can't be split.
    • About 200 lines of production Go across the package and the two appconf exports; the rest is tests and a fixture.
  • This PR is not a bug fix, or the new or changed test fails on main and passes on this branch.
  • Each commit is one logical change with a clear message.
  • I did not hand-edit sqlc-generated files under gtfsdb/ (db.go, models.go, query.sql.go) or testdata/openapi.yml (synced from upstream).
  • This PR changes no handler, or handlers pass r.Context() to DB/service calls, not context.Background().
  • The response comparison above is filled in, or this PR doesn't change a Java-served response.
  • This PR adds no endpoint, or each new endpoint matches testdata/openapi.yml, has an issue open on OneBusAway/sdk-config, or is outside the SDK and the description says why.
  • I read every line of this diff and can explain it if asked.

Notes for reviewers

  • The HTTP fetch (1 MiB cap, 30 s timeout, regular-file check for local paths) repeats the shape of rawGtfsData in internal/gtfs/static.go. Sharing them is left for phase C, because the streaming import stack rewrites that fetch.
  • Manifest errors use the config spelling of field names (default-agency-id) because they come from the shared validator.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 59 minutes.

Check out review usage here.

View limit details

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

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: c97ebbb7-6a60-40fb-9e72-2a241c629cdb
Commits

Reviewing files that changed from the base of the PR and between 5cd06e0 and b0cf517.

Files selected for processing: 8

  • internal/appconf/json_config.go
  • internal/appconf/static_feeds_test.go
  • internal/bundleinputs/bundleinputs.go
  • internal/bundleinputs/bundleinputs_test.go
  • internal/bundleinputs/fetch.go
  • internal/bundleinputs/fetch_test.go
  • internal/bundleinputs/testdata/bundle-inputs.json
  • internal/gtfs/config.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The description follows the PR template.

@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 330
Req/sec 10.8

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

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

@aaronbrethorst
aaronbrethorst force-pushed the ab/multi-feed-a2-manifest branch from 99d7f53 to 69c18d4 Compare November 7, 2026 18:35
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.0 ms
Error rate 0.00%
Total requests 341
Req/sec 11.2

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

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

@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 1.7 ms
Error rate 0.00%
Total requests 345
Req/sec 11.3

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

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

@github-actions github-actions Bot added the follows-template The PR description follows the PR template label Nov 8, 2026
@aaronbrethorst
aaronbrethorst force-pushed the ab/multi-feed-a2-manifest branch from 69c18d4 to 5967bf9 Compare November 8, 2026 17:57
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.0 ms
Error rate 0.00%
Total requests 343
Req/sec 11.3

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

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

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.2 ms
Error rate 0.00%
Total requests 335
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.

@aaronbrethorst
aaronbrethorst force-pushed the ab/multi-feed-a2-manifest branch from 5967bf9 to 6b398a6 Compare November 8, 2026 22:25
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.0 ms
Error rate 0.00%
Total requests 344
Req/sec 11.2

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

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

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.3 ms
Error rate 0.00%
Total requests 345
Req/sec 11.4

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

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

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

Reviewed at 6b398a6. This looks good.

  • Reusing appconf.ValidateStaticFeeds keeps a manifest list and a config list under the same rules.
  • Requiring http(s) for feed and consolidation URLs in a remote manifest is the right restriction.
  • The read is well guarded: a size cap, a check for trailing data, and a regular-file check for local paths, so a FIFO can't hang startup.
  • The tests cover each rule, and CI is green.

I have one small inline nit, about byteSize not being range-checked. The signed-URL point (Fetch errors include the full source URL) is raised on #1815, where that URL gets logged and dumped.


Generated by Claude Code

if !appconf.IsHTTPURL(feed.URL) {
return fmt.Errorf("url %q must be an http:// or https:// URL", feed.URL)
}
if feed.SHA256 != "" && !isSHA256Hex(feed.SHA256) {

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.

Nit: sha256 gets a format check, but byteSize doesn't, so a negative value parses fine. Nothing reads it yet. Since this PR is defining the format rules, it might be worth rejecting byteSize < 0 here, rather than leaving it to the phase C download check to handle a nonsense value.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in b0cf517. A negative byteSize is now rejected next to the sha256 check.

The bundle-inputs manifest yields a static feed list too, and it must
pass the same rules as a configured one: unique ids, unique default
agencies without '_', and valid fields in each feed. Exporting
ValidateStaticFeeds lets the manifest reuse those checks instead of
repeating them. validateStaticFeeds keeps the config-only rules (an
empty list) and calls it with the key the feeds came from.

IsHTTPURL replaces the http/https prefix test that the feed url check
and gtfs.Config.isLocalFile each wrote out, so the manifest code can
use it too. Behavior is unchanged.
obacloud publishes a version 1 bundle-inputs.json that lists the
per-agency static feeds an OBA server loads, in load order, with an
optional stop consolidation URL. Maglev needs to read it to run in
manifest mode.

Parse decodes the manifest and checks the manifest format: version
1, at least one feed, an id and defaultAgencyId on each feed, and a
64-character hex sha256 when one is given. Feed and consolidation
URLs must be http(s), so a manifest served by another host cannot
point the server at local files. The feeds then go through
appconf.ValidateStaticFeeds, the same checks a configured feed list
gets, so ids and defaultAgencyIds are unique and agency ids have no
'_'.

StaticFeeds maps the feeds onto appconf.GtfsStaticFeed. SHA256 and
ByteSize stay on the manifest feed for the download check that comes
later.
Fetch reads the manifest from an http(s) URL or a local path and
parses it. Errors name the source so a startup failure points at the
manifest that broke it.

The HTTP code lives in the package rather than reusing the static
GTFS download in internal/gtfs: that one is tied to gtfs.Config's
auth header and gtfstidy, and the manifest needs neither. A manifest
is a few kilobytes, so the body is capped at 1 MiB and the request
times out after 30 seconds.
Parse now rejects data after the first JSON value, so a concatenated or partly overwritten manifest is an error instead of silently using its first object. A local manifest path must be a regular file, as LoadFromFile already requires of the config file; a directory or FIFO fails up front instead of hanging or producing a confusing JSON error.
sha256 already had a format check, but byteSize did not, so a manifest
with a negative size parsed cleanly. Nothing reads the size yet; the
format rules belong here rather than in the later download check.
@aaronbrethorst
aaronbrethorst force-pushed the ab/multi-feed-a2-manifest branch from 6b398a6 to b0cf517 Compare November 10, 2026 00:04
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 1.2 ms
Error rate 0.00%
Total requests 331
Req/sec 10.9

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

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

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

follows-template The PR description follows the PR template

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants