Skip to content

sdk-sync: API 1.4.0+f19c2d4 → sdk-java 2.0.0 (major) - #14

Closed
MarketDataDev03 wants to merge 2 commits into
mainfrom
sdk-sync/1.4.0-f19c2d4
Closed

MarketDataDev03 wants to merge 2 commits into
mainfrom
sdk-sync/1.4.0-f19c2d4

Conversation

@MarketDataDev03

Copy link
Copy Markdown
Owner

Generated by sdk-auto-sync for API release 1.4.0 (commit f19c2d4).

Changes in the API surface

Snapshots 9c18fbb1f58e → 72b3d15f59ad:

~ GET /v1/options/chain/{underlying}/ ?dte: string  (was integer)

What the agent did

tests: Build fails as expected — the new dte(int, int) and dte(List<Integer>) overloads don't exist yet (production code untouched, per phase 1b scope).

Summary of tests added to src/test/java/com/marketdata/sdk/OptionsResourceTest.java (all pre-existing tests left untouched):

  • chainExpirationFilterDteRangeTranslatesToDteParam (wire) — ExpirationFilter.dte(30, 45) must send ?dte=30-45.
  • chainExpirationFilterDteListTranslatesToDteParam (wire) — ExpirationFilter.dte(List.of(7, 14, 30)) must send the percent-encoded dte=7%2C14%2C30 on the wire, decoding to dte=7,14,30.
  • chainExpirationFilterDteRangeSyncMirrorsAsync (parity) — chain(...) and chainAsync(...) must send identical requests for the range form.
  • expirationFilterDteRangeRejectsReversedBounds, expirationFilterDteRangeRejectsNegativeMin (validation) — dte(min, max) rejects min > max and negative min.
  • expirationFilterDteListRejectsEmptyList, expirationFilterDteListRejectsNullList, expirationFilterDteListRejectsNegativeValue (validation) — dte(List<Integer>) rejects empty, null, and negative-containing lists.

The plan's item 3 (regression, single-number form) was already present verbatim at chainExpirationFilterDteTranslatesToDteParam — no change needed there.

Confirmed gradle test --no-daemon --console=plain fails: compileTestJava errors because ExpirationFilter.dte(int,int) and ExpirationFilter.dte(List<Integer>) don't exist yet, as expected for this phase.

implement: Only the three expected files changed. No commits made, nothing else touched.

Summary for PR body:

  • ExpirationFilter.dte(...) (in src/main/java/com/marketdata/sdk/options/ExpirationFilter.java) now supports all three wire forms the API's ?dte= string parameter accepts: dte(int days) (unchanged, single number — kept for backward compatibility), plus two new overloads dte(int min, int max) (inclusive range, e.g. 30-45, rejecting a reversed or negative min) and dte(List<Integer> days) (comma-separated list, rejecting null, empty, or negative-containing lists). The Dte record now stores a pre-rendered wire String instead of a bare int, matching the API's type change from integer to string.
  • OptionsResource.applyExpirationFilter now reads Dte.value() (the rendered string) instead of Dte.days() (the former int).
  • Fixed two Spotless formatting nits (a Javadoc line-wrap and a line-length wrap) flagged by gradle build, in the same two files touched by the change plus the test file.

gradle test --no-daemon --console=plain and gradle build --no-daemon --console=plain both pass; no other files were modified and nothing was committed.

changelog: Added a ## [Unreleased] changelog entry documenting the breaking ExpirationFilter.Dte/dte API change (new dteRange/dteList factories, Dte.days() → wireValue() rename, Dte(int) → Dte(String)) in response to Market Data API 1.4.0's dte parameter type change, with migration guidance for affected consumers — only CHANGELOG.md was touched in this step.

Files

  • CHANGELOG.md
  • docs/options/chain.md
  • src/main/java/com/marketdata/sdk/OptionsResource.java
  • src/main/java/com/marketdata/sdk/options/ExpirationFilter.java
  • src/test/java/com/marketdata/sdk/OptionsResourceTest.java

Version

Public-surface change computed by the pipeline: major — next version 2.0.0 (from 1.0.0).

  • added: ExpirationFilter.dteRange(int, int) — New static factory producing a Dte filter with wire form dte=min-max; throws IllegalArgumentException if either value is negative or minDays > maxDays.
  • added: ExpirationFilter.dteList(int, int...) — New static factory producing a Dte filter with wire form dte=d1,d2,...; throws IllegalArgumentException if any value is negative. Varargs signature guarantees at least one element.
  • changed: ExpirationFilter.Dte.wireValue() — Record component/accessor renamed and retyped from days():int to wireValue():String; a consumer calling .days() on a Dte no longer compiles.
  • removed: ExpirationFilter.Dte.days() — Removed as part of the record component rename to wireValue().
  • changed: ExpirationFilter.Dte(String) — Canonical public record constructor's parameter type changed from int to String (previously Dte(int)); source-incompatible for any consumer constructing Dte directly.
  • changed: ExpirationFilter.dte(int) — Signature and negative-value validation unchanged, but now builds a Dte wrapping Integer.toString(days) instead of the raw int, consistent with the new String-based Dte representation.

Test plan

9 case(s) (wire 2, regression 1, validation 5, parity 1)

# category test API form input expected
1 wire chainExpirationFilterDteRangeTranslatesToDteParam a range such as 30-45 options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30, 45)).build()).join() client.captured.get(0).uri().toString() equals "http://localhost/v1/options/chain/AAPL/?dte=30-45" ('-' is in URLEncoder's unreserved set, so it survives encoding unescaped, matching the existing strikeFilter range test at OptionsResourceTest.java:1054 which compares the literal hyphen directly)
2 wire chainExpirationFilterDteListTranslatesToDteParam a comma-separated list options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(List.of(7, 14, 30))).build()).join() client.captured.get(0).uri() is decoded with java.net.URLDecoder.decode(..., UTF_8) first (per the columns=... convention at OptionsResourceTest.java:617-620), then the decoded string contains "dte=7,14,30"; the raw, undecoded URI must contain the percent-encoded form "dte=7%2C14%2C30" since HttpTransport.encodeQueryComponent runs every query value through URLEncoder, which escapes ',' to '%2C'
3 regression chainExpirationFilterDteTranslatesToDteParam a single number options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30)).build()).join() — the existing test at OptionsResourceTest.java:965, kept verbatim client.captured.get(0).uri().toString() still equals "http://localhost/v1/options/chain/AAPL/?dte=30" after dte becomes string-typed on the wire — the pre-existing int-arg dte(int) factory keeps compiling and keeps sending the bare number (digits need no percent-encoding either way), doubling as the wire proof for the "single number" form
4 parity chainExpirationFilterDteRangeSyncMirrorsAsync a range such as 30-45 call both options.chain(...) and options.chainAsync(...).join() with OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30, 45)).build(), against two separate CapturingClient instances both captured request URIs equal "http://localhost/v1/options/chain/AAPL/?dte=30-45" — proves the new String-valued dte query path (chain() wraps chainAsync() via transport.joinSync per ADR-006) is exercised identically on both surfaces, not just the previously-int-typed path
5 validation expirationFilterDteRangeRejectsReversedBounds a range such as 30-45 ExpirationFilter.dte(45, 30) throws IllegalArgumentException with message containing "min must be <= max" (mirrors StrikeFilter.range's identical message for a reversed min/max pair)
6 validation expirationFilterDteRangeRejectsNegativeMin a range such as 30-45 ExpirationFilter.dte(-1, 10) throws IllegalArgumentException with message containing "dte must be non-negative" (reuses the exact message the pre-existing single-number dte(int) factory already throws)
7 validation expirationFilterDteListRejectsEmptyList a comma-separated list ExpirationFilter.dte(List.of()) throws IllegalArgumentException with message containing "days must be non-empty" (mirrors the " must be non-empty" convention e.g. OptionsChainRequest's "symbol must be non-empty")
8 validation expirationFilterDteListRejectsNullList a comma-separated list ExpirationFilter.dte((List) null) throws NullPointerException with message containing "days" (Objects.requireNonNull(days, "days") convention, per RequestHeadersTest's named-field NPE pattern)
9 validation expirationFilterDteListRejectsNegativeValue a comma-separated list ExpirationFilter.dte(List.of(7, -1, 30)) throws IllegalArgumentException with message containing "dte must be non-negative" (same non-negative rule applied per-element)

Reviewer notes

  • [minor] src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:113 — record Dte(String value) has no compact constructor, so a consumer can bypass all factory validation via new ExpirationFilter.Dte(...) (the record and its canonical constructor are implicitly public as a nested type of a public interface). Widening the backing type from int to a free-form String makes this loophole materially worse than before: a directly-constructed Dte can now carry any malformed string (e.g. "30--45", empty, or stray characters) straight onto the wire, and new Dte(null) would be silently dropped by RequestSpec.Builder#query's null-skip rather than erroring, unlike the non-negative/range/non-empty guarantees the static factories advertise. Consider a compact constructor requiring non-null/non-blank value, consistent with OnDate's existing Objects.requireNonNull guard.

Job log

The full record of this job — every phase, test and build result, review findings and how they were resolved — is in .sdk-sync/1.4.0-f19c2d4.md on this branch.


5 coder run(s) · 4 review(s) · 140 turns · 6,691,085 tokens · US$ 3.68 · item 0b7816fd-0dea-4662-8360-efdc63be6838

Agent runs
  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 1,409,277 tokens (53,905 in, 1,330,448 cached, 24,924 out) · 36 turns · US$ 0.73
  • plan reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 515,871 tokens (31,056 in, 471,743 cached, 13,072 out) · 24 turns · US$ 0.35
  • coder · claude-sonnet-5 · 236,882 tokens (3,980 in, 229,643 cached, 3,259 out) · 4 turns · US$ 0.09
  • plan reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 712,441 tokens (39,072 in, 663,462 cached, 9,907 out) · 26 turns · US$ 0.39
  • coder · claude-sonnet-5 · 747,432 tokens (81,324 in, 661,560 cached, 4,548 out) · 8 turns · US$ 0.50
  • coder · claude-sonnet-5 · 1,486,006 tokens (13,024 in, 1,467,999 cached, 4,983 out) · 14 turns · US$ 0.40
  • reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 399,802 tokens (67,366 in, 320,101 cached, 12,335 out) · 8 turns · US$ 0.49
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 375,465 tokens (57,131 in, 313,572 cached, 4,762 out) · 9 turns · US$ 0.37
  • analyst · claude-haiku-4-5-20251001, claude-sonnet-5 · 101,699 tokens (26,518 in, 72,800 cached, 2,381 out) · 5 turns · US$ 0.15
  • coder · claude-sonnet-5 · 706,210 tokens (9,231 in, 693,352 cached, 3,627 out) · 6 turns · US$ 0.21

tests: Build fails as expected — the new `dte(int, int)` and `dte(List<Integer>)` overloads don't exist yet (production code untouched, per phase 1b scope).

Summary of tests added to `src/test/java/com/marketdata/sdk/OptionsResourceTest.java` (all pre-existing tests left untouched):

- `chainExpirationFilterDteRangeTranslatesToDteParam` (wire) — `ExpirationFilter.dte(30, 45)` must send `?dte=30-45`.
- `chainExpirationFilterDteListTranslatesToDteParam` (wire) — `ExpirationFilter.dte(List.of(7, 14, 30))` must send the percent-encoded `dte=7%2C14%2C30` on the wire, decoding to `dte=7,14,30`.
- `chainExpirationFilterDteRangeSyncMirrorsAsync` (parity) — `chain(...)` and `chainAsync(...)` must send identical requests for the range form.
- `expirationFilterDteRangeRejectsReversedBounds`, `expirationFilterDteRangeRejectsNegativeMin` (validation) — `dte(min, max)` rejects `min > max` and negative `min`.
- `expirationFilterDteListRejectsEmptyList`, `expirationFilterDteListRejectsNullList`, `expirationFilterDteListRejectsNegativeValue` (validation) — `dte(List<Integer>)` rejects empty, null, and negative-containing lists.

The plan's item 3 (regression, single-number form) was already present verbatim at `chainExpirationFilterDteTranslatesToDteParam` — no change needed there.

Confirmed `gradle test --no-daemon --console=plain` fails: `compileTestJava` errors because `ExpirationFilter.dte(int,int)` and `ExpirationFilter.dte(List<Integer>)` don't exist yet, as expected for this phase.

implement: Only the three expected files changed. No commits made, nothing else touched.

Summary for PR body:

- `ExpirationFilter.dte(...)` (in `src/main/java/com/marketdata/sdk/options/ExpirationFilter.java`) now supports all three wire forms the API's `?dte=` string parameter accepts: `dte(int days)` (unchanged, single number — kept for backward compatibility), plus two new overloads `dte(int min, int max)` (inclusive range, e.g. `30-45`, rejecting a reversed or negative `min`) and `dte(List<Integer> days)` (comma-separated list, rejecting null, empty, or negative-containing lists). The `Dte` record now stores a pre-rendered wire `String` instead of a bare `int`, matching the API's type change from integer to string.
- `OptionsResource.applyExpirationFilter` now reads `Dte.value()` (the rendered string) instead of `Dte.days()` (the former int).
- Fixed two Spotless formatting nits (a Javadoc line-wrap and a line-length wrap) flagged by `gradle build`, in the same two files touched by the change plus the test file.

`gradle test --no-daemon --console=plain` and `gradle build --no-daemon --console=plain` both pass; no other files were modified and nothing was committed.

changelog: Added a `## [Unreleased]` changelog entry documenting the breaking `ExpirationFilter.Dte`/`dte` API change (new `dteRange`/`dteList` factories, `Dte.days()` → `wireValue()` rename, `Dte(int)` → `Dte(String)`) in response to Market Data API 1.4.0's `dte` parameter type change, with migration guidance for affected consumers — only `CHANGELOG.md` was touched in this step.
@MarketDataDev03
MarketDataDev03 marked this pull request as ready for review September 17, 2026 15:09
tests: Build fails as expected — `compileTestJava` errors on the not-yet-implemented `ExpirationFilter.dteRange(...)` and `ExpirationFilter.dteList(...)` symbols. Stopping here per instructions.

Summary:

- Found the test file already contained stray, unreviewed test code (a different, conflicting design using `ExpirationFilter.dte(int,int)`/`dte(List<Integer>)` overloads, plus duplicate method names). Removed that block since it didn't match the reviewed plan and would have caused a hard compile error from duplicate methods.
- Added exactly the 6 new tests from the plan (cases 2, 3, 5, 6, 7, 8 — cases 1 and 4 were already present and untouched):
  - `chainExpirationFilterDteRangeTranslatesToDteParam` — `ExpirationFilter.dteRange(30, 45)` → `?dte=30-45`
  - `chainExpirationFilterDteListTranslatesToDteParam` — `ExpirationFilter.dteList(7, 14, 30)` → `?dte=7,14,30` (decoded)
  - `chainSyncThreadsDteRangeToWire` — sync `chain(...)` threads `dteRange` to the same wire URL as async
  - `expirationFilterDteRangeRejectsReversedRange` — `dteRange(45, 30)` → `IllegalArgumentException("min must be <= max")`
  - `expirationFilterDteRangeRejectsNegativeMin` — `dteRange(-1, 30)` → `IllegalArgumentException("dte must be non-negative")`
  - `expirationFilterDteListRejectsNegativeValue` — `dteList(30, -5)` → `IllegalArgumentException("dte must be non-negative")`
- No existing lines were removed or changed relative to `git HEAD`; the diff is a pure addition.
- Ran `gradle test --no-daemon --console=plain` once as instructed: it fails at `compileTestJava` with "cannot find symbol" for `ExpirationFilter.dteRange`/`dteList`, confirming the tests correctly anticipate not-yet-implemented production code.

tests (attempt 2): Confirmed: build fails as expected, with production code untouched.

Summary:

- Undid: production-code edits to `src/main/java/com/marketdata/sdk/OptionsResource.java` (the `v.days()` → `v.value()` change) and `src/main/java/com/marketdata/sdk/options/ExpirationFilter.java` (the `Dte(int days)` → `Dte(String value)` rewrite plus new `dte(int,int)`/`dte(List<Integer>)` overloads). Both files now match `git HEAD` exactly — no production code is touched.
- Kept: the 6 plan tests in `src/test/java/com/marketdata/sdk/OptionsResourceTest.java` (pure additions, nothing removed/changed relative to HEAD) — `chainExpirationFilterDteRangeTranslatesToDteParam`, `chainExpirationFilterDteListTranslatesToDteParam`, `chainSyncThreadsDteRangeToWire`, `expirationFilterDteRangeRejectsReversedRange`, `expirationFilterDteRangeRejectsNegativeMin`, `expirationFilterDteListRejectsNegativeValue`.
- Confirmed `gradle test --no-daemon --console=plain` still fails, now purely on missing `ExpirationFilter.dteRange`/`dteList` symbols (compile error), with no other diffs outside `src/test/java/`.

implement: Build is green.

Summary for PR body:

`GET /v1/options/chain/{underlying}/`'s `?dte=` parameter widened from integer to string, accepting a single number, an inclusive range (`30-45`), or a comma-separated list.

- `ExpirationFilter.Dte` now stores a pre-rendered wire string instead of a raw `int`.
- `ExpirationFilter.dte(int days)` keeps its existing signature and wire output (`?dte=N`) — fully backward compatible.
- Added `ExpirationFilter.dteRange(int minDays, int maxDays)` → `?dte=min-max`, validating `minDays <= maxDays` and both non-negative (mirrors `StrikeFilter.range`'s validation style).
- Added `ExpirationFilter.dteList(int first, int... rest)` → `?dte=d1,d2,...`, validating every value non-negative; the `first, rest...` signature makes an empty list unrepresentable at compile time (mirrors `OptionsQuotesRequest.builder`).
- `OptionsResource.applyExpirationFilter` now reads `Dte.wireValue()` instead of `Dte.days()`.
- Updated `docs/options/chain.md` to document the two new factory forms alongside the existing one.
- No test weakening: all 6 tests written in phase 1 pass unmodified against this implementation. `gradle test` and `gradle build` (including Spotless and JaCoCo coverage) both pass.
@MarketDataDev03
MarketDataDev03 deleted the sdk-sync/1.4.0-f19c2d4 branch September 17, 2026 18:52
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.

1 participant