Skip to content

sdk-sync: API 2.0.0+ffb34b7 → sdk-java 2.0.0 (major) - #81

Closed
marketdata-sdk-sync[bot] wants to merge 1 commit into
mainfrom
sdk-sync/2.0.0-ffb34b7
Closed

marketdata-sdk-sync[bot] wants to merge 1 commit into
mainfrom
sdk-sync/2.0.0-ffb34b7

Conversation

@marketdata-sdk-sync

Copy link
Copy Markdown

Generated by sdk-auto-sync for API release 2.0.0 (commit ffb34b7).

Changes in the API surface

Snapshots ae2b047c8f6f → c2a31e277345:

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

What the agent did

tests: Build fails as expected — DteFilter doesn't exist yet, which is exactly the compilation failure phase 1b requires.

Added to OptionsResourceTest.java (no production code touched):

  • 4 wire tests for the new ?dte forms (single value, comma list, closed range, comparison operator)
  • 1 parity test (sync vs async) for the list form
  • 6 validation tests (new 36500 upper bound on dte(int) and DteFilter.range, plus DteFilter.list/range/comparison input checks)
  • The regression case (expirationFilterDteRejectsNegative) already existed unchanged, so nothing was added for it
  • Added the DteFilter import needed by the new cases

implement: Build and tests pass, only production/test source files touched, no forbidden paths modified.

Added DteFilter.java (new sealed type: Exact/ListOf/Range/Comparison, mirroring StrikeFilter) to model the four wire forms of the now-string ?dte= parameter, with shared [0, 36500] validation. ExpirationFilter.dte(int) now delegates to DteFilter.exact(...) (keeping old call sites and negative-rejection behavior working) plus a new dte(DteFilter) overload. OptionsResource gained a dteFilterWireValue helper analogous to strikeFilterWireValue to render each variant onto the query string. gradle test and gradle build pass (required a spotlessApply formatting pass on the test file).

changelog: Added changelog entry documenting the DteFilter sealed type for the string-valued ?dte= parameter in API 2.0.0, with migration guidance for the removed days() accessor.

Files

  • CHANGELOG.md
  • src/main/java/com/marketdata/sdk/OptionsResource.java
  • src/main/java/com/marketdata/sdk/options/DteFilter.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: DteFilter
  • added: DteFilter.MAX_DAYS
  • added: DteFilter.exact(int)
  • added: DteFilter.list(int[])
  • added: DteFilter.range(int, int)
  • added: DteFilter.comparison(DteFilter.Operator, int)
  • added: DteFilter.Exact
  • added: DteFilter.ListOf
  • added: DteFilter.Range
  • added: DteFilter.Comparison
  • added: DteFilter.Operator
  • added: DteFilter.Operator.GT
  • added: DteFilter.Operator.GTE
  • added: DteFilter.Operator.LT
  • added: DteFilter.Operator.LTE
  • added: DteFilter.Operator.wireValue()
  • added: ExpirationFilter.dte(DteFilter)
  • changed: ExpirationFilter.Dte — Record component changed from int days to DteFilter filter; constructor and filter() accessor signatures now differ.
  • removed: ExpirationFilter.Dte.days()

Test plan

12 case(s) (wire 4, regression 1, validation 6, parity 1)

# category test API form input expected
1 wire chainExpirationFilterDteSingleValueTranslatesToDteParam A single value options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(30)).build()) request URI equals http://localhost/v1/options/chain/AAPL/?dte=30
2 wire chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam A comma-separated list (15,30,45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.list(15, 30, 45))).build()) URL-decoded request URI equals http://localhost/v1/options/chain/AAPL/?dte=15,30,45
3 wire chainExpirationFilterDteRangeTranslatesToDashedDteParam A closed range (0-45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.range(0, 45))).build()) request URI equals http://localhost/v1/options/chain/AAPL/?dte=0-45
4 wire chainExpirationFilterDteComparisonRendersWithOperatorPrefix a comparison operator (>=30, <45) options.chainAsync(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.comparison(DteFilter.Operator.GTE, 30))).build()) request URI contains dte=%3E%3D30 (URL-encoded >=30)
5 regression expirationFilterDteRejectsNegative n/a — pre-existing single-integer dte(int) factory ExpirationFilter.dte(-1) IllegalArgumentException "dte must be non-negative" (unchanged from before the type change)
6 validation expirationFilterDteRejectsAboveMaximum Values are whole numbers of days, at most 36500 ExpirationFilter.dte(36501) IllegalArgumentException "dte must be at most 36500"
7 validation dteFilterListRejectsEmptyList A comma-separated list (15,30,45) DteFilter.list() IllegalArgumentException "days must be non-empty"
8 validation dteFilterListRejectsNegativeElement A comma-separated list (15,30,45) DteFilter.list(15, -1, 45) IllegalArgumentException "dte must be non-negative"
9 validation dteFilterRangeRejectsMinGreaterThanMax A closed range (0-45) DteFilter.range(45, 0) IllegalArgumentException "min must be <= max"
10 validation dteFilterRangeRejectsMaxAboveMaximum Values are whole numbers of days, at most 36500 DteFilter.range(0, 36501) IllegalArgumentException "dte must be at most 36500"
11 validation dteFilterComparisonRejectsNegativeValue a comparison operator (>=30, <45) DteFilter.comparison(DteFilter.Operator.LT, -1) IllegalArgumentException "dte must be non-negative"
12 parity chainExpirationFilterDteListSyncSendsSameWireAsAsync A comma-separated list (15,30,45) options.chain(OptionsChainRequest.builder("AAPL").expirationFilter(ExpirationFilter.dte(DteFilter.list(15, 30, 45))).build()) vs options.chainAsync(...).join() both sync and async requests' URL-decoded URI equal http://localhost/v1/options/chain/AAPL/?dte=15,30,45

Reviewer notes

  • [minor] CHANGELOG.md — The dte parameter type change (int to string forms) is a breaking public API change (ExpirationFilter.Dte record field replaced, new DteFilter type) but the Unreleased section of CHANGELOG.md is still empty. Per the repo's Keep a Changelog convention this should be documented. CHANGELOG.md is outside the author's allowed write paths, so a human needs to add it.
  • [minor] src/test/java/com/marketdata/sdk/OptionsResourceTest.java:981 — chainExpirationFilterDteSingleValueTranslatesToDteParam duplicates the pre-existing chainExpirationFilterDteTranslatesToDteParam test verbatim (same request, same assertion). Required by the committed test plan, but the two could be consolidated.

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/2.0.0-ffb34b7.md on this branch.


4 coder run(s) · 3 review(s) · 64 turns · 3,827,706 tokens · US$ 2.81 · item 5c097984-5f7b-401b-abba-235a3f417767

Agent runs
  • coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 266,674 tokens (72,462 in, 174,364 cached, 19,848 out) · 10 turns · US$ 0.53
  • plan reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 215,709 tokens (38,422 in, 171,065 cached, 6,222 out) · 12 turns · US$ 0.25
  • coder · claude-sonnet-5 · 809,972 tokens (104,973 in, 699,441 cached, 5,558 out) · 8 turns · US$ 0.62
  • coder · claude-sonnet-5 · 1,978,579 tokens (20,522 in, 1,949,518 cached, 8,539 out) · 18 turns · US$ 0.56
  • reviewer · claude-haiku-4-5-20251001, claude-sonnet-5 · 358,273 tokens (68,591 in, 280,942 cached, 8,740 out) · 7 turns · US$ 0.45
  • security · claude-haiku-4-5-20251001, claude-sonnet-5 · 65,096 tokens (56,799 in, 6,534 cached, 1,763 out) · 2 turns · US$ 0.28
  • analyst · claude-haiku-4-5-20251001 · 70,683 tokens (24,237 in, 42,600 cached, 3,846 out) · 4 turns · US$ 0.08
  • changelog · claude-haiku-4-5-20251001 · 62,720 tokens (21,607 in, 40,152 cached, 961 out) · 3 turns · US$ 0.05

tests: Build fails as expected — `DteFilter` doesn't exist yet, which is exactly the compilation failure phase 1b requires.

Added to `OptionsResourceTest.java` (no production code touched):
- 4 wire tests for the new `?dte` forms (single value, comma list, closed range, comparison operator)
- 1 parity test (sync vs async) for the list form
- 6 validation tests (new 36500 upper bound on `dte(int)` and `DteFilter.range`, plus `DteFilter.list`/`range`/`comparison` input checks)
- The regression case (`expirationFilterDteRejectsNegative`) already existed unchanged, so nothing was added for it
- Added the `DteFilter` import needed by the new cases

implement: Build and tests pass, only production/test source files touched, no forbidden paths modified.

Added `DteFilter.java` (new sealed type: `Exact`/`ListOf`/`Range`/`Comparison`, mirroring `StrikeFilter`) to model the four wire forms of the now-string `?dte=` parameter, with shared `[0, 36500]` validation. `ExpirationFilter.dte(int)` now delegates to `DteFilter.exact(...)` (keeping old call sites and negative-rejection behavior working) plus a new `dte(DteFilter)` overload. `OptionsResource` gained a `dteFilterWireValue` helper analogous to `strikeFilterWireValue` to render each variant onto the query string. `gradle test` and `gradle build` pass (required a `spotlessApply` formatting pass on the test file).

changelog: Added changelog entry documenting the `DteFilter` sealed type for the string-valued `?dte=` parameter in API 2.0.0, with migration guidance for the removed `days()` accessor.
@marketdata-sdk-sync
marketdata-sdk-sync Bot marked this pull request as ready for review September 30, 2026 12:57
@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.29%. Comparing base (26dc4b5) to head (7f9b0f3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main      #81      +/-   ##
============================================
+ Coverage     98.27%   98.29%   +0.02%     
- Complexity     1043     1056      +13     
============================================
  Files           123      124       +1     
  Lines          2606     2646      +40     
  Branches        301      305       +4     
============================================
+ Hits           2561     2601      +40     
  Partials         45       45              
Files with missing lines Coverage Δ
.../main/java/com/marketdata/sdk/OptionsResource.java 98.58% <100.00%> (+0.05%) ⬆️
...ain/java/com/marketdata/sdk/options/DteFilter.java 100.00% <100.00%> (ø)
...a/com/marketdata/sdk/options/ExpirationFilter.java 95.00% <100.00%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d6f1077...7f9b0f3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@marketdata-code-review

Copy link
Copy Markdown

⛔ Blocked — Code review

This adds four string wire forms for the options-chain DTE filter while retaining the integer factory. Version impact: major (1.0.0 -> 2.0.0).

Rating patch 3/6 (incomplete) · proof 5/6 (reproducible) · overall 3/6

Before merge

Findings

Blocking

  • src/main/java/com/marketdata/sdk/options/DteFilter.java:91-99 Public records bypass DTE validation (correctness, 9de3ff54)
    The nested records have public canonical constructors, so callers can bypass every factory check with values such as new Exact(-1), new Range(45, 0), or new ListOf(null). This permits malformed requests, null failures, and mutable list state despite the type's documented invariant. Add validating compact constructors to every record and defensively copy ListOf.days; the factories can then delegate to them.

    record Exact(int days) implements DteFilter {} and the other three records expose unchecked constructors.

symbol before after breaking
DteFilter absent public sealed interface no
DteFilter.MAX_DAYS absent public constant no
DteFilter.exact(int) absent public factory no
DteFilter.list(int...) absent public factory no
DteFilter.range(int,int) absent public factory no
DteFilter.comparison(Operator,int) absent public factory no
DteFilter.Operator absent public enum no
Operator.GT absent enum constant no
Operator.GTE absent enum constant no
Operator.LT absent enum constant no
Operator.LTE absent enum constant no
Operator.wireValue() absent public method no
DteFilter.Exact absent public record no
Exact(int) / days() absent public constructor/accessor no
DteFilter.ListOf absent public record no
ListOf(List<Integer>) / days() absent public constructor/accessor no
DteFilter.Range absent public record no
Range(int,int) / min() / max() absent public constructor/accessors no
DteFilter.Comparison absent public record no
Comparison(Operator,int) / operator() / days() absent public constructor/accessors no
ExpirationFilter.dte(int) accepted every non-negative value throws above 36500 yes: error behavior
ExpirationFilter.dte(DteFilter) absent public overload no
ExpirationFilter.Dte(int) public constructor removed yes: source and binary
ExpirationFilter.Dte.days() public accessor removed yes: source and binary
ExpirationFilter.Dte(DteFilter) absent public constructor no
ExpirationFilter.Dte.filter() absent public accessor no

Should fix

  • src/main/java/com/marketdata/sdk/options/DteFilter.java:53-61 Reachable null guard is excluded from coverage (tests, ec517ad2)
    The comment says the null guard is unreachable, but any Java caller can invoke the public DteFilter.comparison(null, 30) method. Marking the method @Generated hides this reachable behavior from coverage. Remove the exclusion and add a test for the null-operator contract.

    // @Generated: the null-operator guard is unreachable through the public comparison factories followed by the public interface method static Comparison comparison(Operator operator, int days).

  • src/main/java/com/marketdata/sdk/options/ExpirationFilter.java:49-57 Public DTE API lacks resource documentation (docs, 58973cd5)
    The pull request adds a public overload and changes the public Dte constructor/accessor contract, but no resource page under docs/ is changed. Update the options-chain documentation with all four forms and the migration from days() to filter().

    The diff adds static Dte dte(DteFilter filter), while the changed-file list contains no file under docs/.


Reviewed 7f9b0f3 · revision 1 · codex (gpt-5.6-sol) · claude unavailable · rules: default, sdk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant