Skip to content

Commit 93a74bc

Browse files
add from -> to validation
1 parent 274c325 commit 93a74bc

6 files changed

Lines changed: 60 additions & 7 deletions

File tree

src/main/java/com/marketdata/sdk/funds/FundRequests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ private FundRequests() {}
1212
* Validates the historical-window parameters: {@code date} is a single-point lookup incompatible
1313
* with any ranging parameter; {@code countback} is an alternative to {@code from} for the left
1414
* edge (per the API: "if you use from, countback is not required"), so the two cannot be
15-
* combined; and {@code countback} must be positive.
15+
* combined; {@code countback} must be positive; and {@code from} must not be after {@code to}.
1616
*/
1717
static void validateWindow(
1818
@Nullable LocalDate date,
@@ -22,6 +22,9 @@ static void validateWindow(
2222
if (date != null && (from != null || to != null || countback != null)) {
2323
throw new IllegalArgumentException("date and from/to/countback are mutually exclusive");
2424
}
25+
if (from != null && to != null && from.isAfter(to)) {
26+
throw new IllegalArgumentException("from must not be after to");
27+
}
2528
if (countback != null) {
2629
if (countback <= 0) {
2730
throw new IllegalArgumentException("countback must be positive");

src/main/java/com/marketdata/sdk/options/OptionsQuoteRequest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ public OptionsQuoteRequest build() {
112112
/**
113113
* Shared validation for the historical-window parameters across both quote request forms: {@code
114114
* date} is a single snapshot incompatible with any ranging parameter; {@code countback} is an
115-
* alternative to {@code from} for the left edge, so the two cannot be combined; and {@code
116-
* countback} must be positive.
115+
* alternative to {@code from} for the left edge, so the two cannot be combined; {@code countback}
116+
* must be positive; and {@code from} must not be after {@code to}.
117117
*/
118118
static void validateWindow(
119119
@Nullable LocalDate date,
@@ -123,6 +123,9 @@ static void validateWindow(
123123
if (date != null && (from != null || to != null || countback != null)) {
124124
throw new IllegalArgumentException("date and from/to/countback are mutually exclusive");
125125
}
126+
if (from != null && to != null && from.isAfter(to)) {
127+
throw new IllegalArgumentException("from must not be after to");
128+
}
126129
if (countback != null) {
127130
if (countback <= 0) {
128131
throw new IllegalArgumentException("countback must be positive");

src/main/java/com/marketdata/sdk/stocks/StockRequests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ private StockRequests() {}
1212
* Validates the historical-window parameters shared by candles/news/earnings: {@code date} is a
1313
* single-point lookup incompatible with any ranging parameter; {@code countback} is an
1414
* alternative to {@code from} for the left edge (per the API: "if you use from, countback is not
15-
* required"), so the two cannot be combined; and {@code countback} must be positive.
15+
* required"), so the two cannot be combined; {@code countback} must be positive; and {@code from}
16+
* must not be after {@code to}.
1617
*/
1718
static void validateWindow(
1819
@Nullable LocalDate date,
@@ -22,6 +23,9 @@ static void validateWindow(
2223
if (date != null && (from != null || to != null || countback != null)) {
2324
throw new IllegalArgumentException("date and from/to/countback are mutually exclusive");
2425
}
26+
if (from != null && to != null && from.isAfter(to)) {
27+
throw new IllegalArgumentException("from must not be after to");
28+
}
2529
if (countback != null) {
2630
if (countback <= 0) {
2731
throw new IllegalArgumentException("countback must be positive");

src/test/java/com/marketdata/sdk/FundsResourceTest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,14 @@ void candlesAcceptsDateOnlyTimestampStringForDailyBars() {
151151

152152
@Test
153153
void candlesNoDataEnvelopeYieldsEmptyList() {
154-
CapturingClient client = okWith("{\"s\":\"no_data\"}");
154+
// The backend signals no_data with 404 + {"s":"no_data"} (the API-wide convention) — the SDK
155+
// surfaces that as a successful empty response (isNoData() == true), not an exception.
156+
CapturingClient client = notFoundWith("{\"s\":\"no_data\"}");
155157
FundsResource funds = resourceWith(client);
156158

157-
assertThat(funds.candles(FundCandlesRequest.of(FundResolution.DAILY, "NOPE")).values())
158-
.isEmpty();
159+
var response = funds.candles(FundCandlesRequest.of(FundResolution.DAILY, "NOPE"));
160+
assertThat(response.values()).isEmpty();
161+
assertThat(response.isNoData()).isTrue();
159162
}
160163

161164
@Test
@@ -374,6 +377,18 @@ void candlesRequestRejectsCountbackWithFrom() {
374377
.hasMessageContaining("countback and from");
375378
}
376379

380+
@Test
381+
void candlesRequestRejectsFromAfterTo() {
382+
assertThatThrownBy(
383+
() ->
384+
FundCandlesRequest.builder(FundResolution.DAILY, "VFINX")
385+
.from(LocalDate.of(2025, Month.JANUARY, 31))
386+
.to(LocalDate.of(2025, Month.JANUARY, 1))
387+
.build())
388+
.isInstanceOf(IllegalArgumentException.class)
389+
.hasMessageContaining("from must not be after to");
390+
}
391+
377392
@Test
378393
void candlesRequestRequiresNonEmptySymbol() {
379394
assertThatThrownBy(() -> FundCandlesRequest.of(FundResolution.DAILY, ""))
@@ -406,6 +421,10 @@ private static CapturingClient okWith(String body) {
406421
return new CapturingClient(200, body.getBytes(StandardCharsets.UTF_8), EMPTY_HEADERS);
407422
}
408423

424+
private static CapturingClient notFoundWith(String body) {
425+
return new CapturingClient(404, body.getBytes(StandardCharsets.UTF_8), EMPTY_HEADERS);
426+
}
427+
409428
private static final class CapturingClient extends TestHttpClients.StubHttpClient {
410429
final List<HttpRequest> captured = new ArrayList<>();
411430
final int status;

src/test/java/com/marketdata/sdk/OptionsResourceTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,18 @@ void quoteRequestRejectsNonPositiveCountback() {
636636
.hasMessageContaining("countback must be positive");
637637
}
638638

639+
@Test
640+
void quoteRequestRejectsFromAfterTo() {
641+
assertThatThrownBy(
642+
() ->
643+
OptionsQuoteRequest.builder("X")
644+
.from(LocalDate.of(2025, Month.JANUARY, 31))
645+
.to(LocalDate.of(2025, Month.JANUARY, 1))
646+
.build())
647+
.isInstanceOf(IllegalArgumentException.class)
648+
.hasMessageContaining("from must not be after to");
649+
}
650+
639651
// ---------- quote: response decoding ----------
640652

641653
@Test

src/test/java/com/marketdata/sdk/StocksResourceTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,18 @@ void earningsRequestRejectsCountbackWithFrom() {
735735
.hasMessageContaining("countback and from");
736736
}
737737

738+
@Test
739+
void candlesRequestRejectsFromAfterTo() {
740+
assertThatThrownBy(
741+
() ->
742+
StockCandlesRequest.builder(StockResolution.DAILY, "AAPL")
743+
.from(LocalDate.of(2025, Month.JANUARY, 31))
744+
.to(LocalDate.of(2025, Month.JANUARY, 1))
745+
.build())
746+
.isInstanceOf(IllegalArgumentException.class)
747+
.hasMessageContaining("from must not be after to");
748+
}
749+
738750
@Test
739751
void quotesRequestRequiresAtLeastOneSymbol() {
740752
assertThatThrownBy(() -> StockQuotesRequest.builder(""))

0 commit comments

Comments
 (0)