Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "6.0.0"
".": "7.0.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 45
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-f09e5f2c555d7ee764478b7bc73e92cd21f403d6ec189be14574c8367bc131ce.yml
openapi_spec_hash: bd0a8e001f14132c105992d40149909a
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-5b00a0bc705b1d5bfcb5ea79c7af544766d51ec12ccc4721825664ab397789d8.yml
openapi_spec_hash: 34891659cff31395ba7683a8153b1db5
config_hash: 53778a0b839c4f6ad34fbba051f5e8a6
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 7.0.0 (2025-05-16)

Full Changelog: [v6.0.0...v7.0.0](https://github.com/Finch-API/finch-api-java/compare/v6.0.0...v7.0.0)

### ⚠ BREAKING CHANGES

* **client:** extract auto pagination to shared classes
* **client:** **Migration:** - If you were referencing the `AutoPager` class on a specific `*Page` or `*PageAsync` type, then you should instead reference the shared `AutoPager` and `AutoPagerAsync` types, under the `core` package
- `AutoPagerAsync` now has different usage. You can call `.subscribe(...)` on the returned object instead to get called back each page item. You can also call `onCompleteFuture()` to get a future that completes when all items have been processed. Finally, you can call `.close()` on the returned object to stop auto-paginating early
- If you were referencing `getNextPage` or `getNextPageParams`:
- Swap to `nextPage()` and `nextPageParams()`
- Note that these both now return non-optional types (use `hasNextPage()` before calling these, since they will throw if it's impossible to get another page)

### Features

* **api:** api update ([f226da9](https://github.com/Finch-API/finch-api-java/commit/f226da9c89911bb4f3f39f3657c2f725d0773e7a))
* **api:** api update ([7932861](https://github.com/Finch-API/finch-api-java/commit/79328615d98546e904f7cf99c222f7645ecd9131))
* **api:** api update ([17bd5c1](https://github.com/Finch-API/finch-api-java/commit/17bd5c172f3ef94e68846f5b18670b42560e1fce))
* **client:** allow providing some params positionally ([ec6fa40](https://github.com/Finch-API/finch-api-java/commit/ec6fa40e9be337318708f336e853b3f9e551dd47))
* **client:** extract auto pagination to shared classes ([c4a8874](https://github.com/Finch-API/finch-api-java/commit/c4a8874a3f7799b0d689beaafd00cf1a3727f0ea))

## 6.0.0 (2025-05-08)

Full Changelog: [v5.5.0...v6.0.0](https://github.com/Finch-API/finch-api-java/compare/v5.5.0...v6.0.0)
Expand Down
94 changes: 70 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.tryfinch.api/finch-java)](https://central.sonatype.com/artifact/com.tryfinch.api/finch-java/6.0.0)
[![javadoc](https://javadoc.io/badge2/com.tryfinch.api/finch-java/6.0.0/javadoc.svg)](https://javadoc.io/doc/com.tryfinch.api/finch-java/6.0.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.tryfinch.api/finch-java)](https://central.sonatype.com/artifact/com.tryfinch.api/finch-java/7.0.0)
[![javadoc](https://javadoc.io/badge2/com.tryfinch.api/finch-java/7.0.0/javadoc.svg)](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.0.0)

<!-- x-release-please-end -->

Expand All @@ -15,7 +15,7 @@ It is generated with [Stainless](https://www.stainless.com/).

<!-- x-release-please-start-version -->

The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-java/6.0.0).
The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-java/7.0.0).

<!-- x-release-please-end -->

Expand All @@ -26,7 +26,7 @@ The REST API documentation can be found on [developer.tryfinch.com](https://deve
### Gradle

```kotlin
implementation("com.tryfinch.api:finch-java:6.0.0")
implementation("com.tryfinch.api:finch-java:7.0.0")
```

### Maven
Expand All @@ -35,7 +35,7 @@ implementation("com.tryfinch.api:finch-java:6.0.0")
<dependency>
<groupId>com.tryfinch.api</groupId>
<artifactId>finch-java</artifactId>
<version>6.0.0</version>
<version>7.0.0</version>
</dependency>
```

Expand Down Expand Up @@ -219,53 +219,101 @@ The SDK throws custom unchecked exception types:

## Pagination

For methods that return a paginated list of results, this library provides convenient ways access the results either one page at a time, or item-by-item across all pages.
The SDK defines methods that return a paginated lists of results. It provides convenient ways to access the results either one page at a time or item-by-item across all pages.

### Auto-pagination

To iterate through all results across all pages, you can use `autoPager`, which automatically handles fetching more pages for you:
To iterate through all results across all pages, use the `autoPager()` method, which automatically fetches more pages as needed.

### Synchronous
When using the synchronous client, the method returns an [`Iterable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html)

```java
import com.tryfinch.api.models.HrisDirectoryListPage;
import com.tryfinch.api.models.IndividualInDirectory;

// As an Iterable:
HrisDirectoryListPage page = client.hris().directory().list(params);
HrisDirectoryListPage page = client.hris().directory().list();

// Process as an Iterable
for (IndividualInDirectory directory : page.autoPager()) {
System.out.println(directory);
};
}

// As a Stream:
client.hris().directory().list(params).autoPager().stream()
// Process as a Stream
page.autoPager()
.stream()
.limit(50)
.forEach(directory -> System.out.println(directory));
```

### Asynchronous
When using the asynchronous client, the method returns an [`AsyncStreamResponse`](finch-java-core/src/main/kotlin/com/tryfinch/api/core/http/AsyncStreamResponse.kt):

```java
// Using forEach, which returns CompletableFuture<Void>:
asyncClient.hris().directory().list(params).autoPager()
.forEach(directory -> System.out.println(directory), executor);
import com.tryfinch.api.core.http.AsyncStreamResponse;
import com.tryfinch.api.models.HrisDirectoryListPageAsync;
import com.tryfinch.api.models.IndividualInDirectory;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

CompletableFuture<HrisDirectoryListPageAsync> pageFuture = client.async().hris().directory().list();

pageFuture.thenRun(page -> page.autoPager().subscribe(directory -> {
System.out.println(directory);
}));

// If you need to handle errors or completion of the stream
pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Handler<>() {
@Override
public void onNext(IndividualInDirectory directory) {
System.out.println(directory);
}

@Override
public void onComplete(Optional<Throwable> error) {
if (error.isPresent()) {
System.out.println("Something went wrong!");
throw new RuntimeException(error.get());
} else {
System.out.println("No more!");
}
}
}));

// Or use futures
pageFuture.thenRun(page -> page.autoPager()
.subscribe(directory -> {
System.out.println(directory);
})
.onCompleteFuture()
.whenComplete((unused, error) -> {
if (error != null) {
System.out.println("Something went wrong!");
throw new RuntimeException(error);
} else {
System.out.println("No more!");
}
}));
```

### Manual pagination

If none of the above helpers meet your needs, you can also manually request pages one-by-one. A page of results has a `data()` method to fetch the list of objects, as well as top-level `response` and other methods to fetch top-level data about the page. It also has methods `hasNextPage`, `getNextPage`, and `getNextPageParams` methods to help with pagination.
To access individual page items and manually request the next page, use the `items()`,
`hasNextPage()`, and `nextPage()` methods:

```java
import com.tryfinch.api.models.HrisDirectoryListPage;
import com.tryfinch.api.models.IndividualInDirectory;

HrisDirectoryListPage page = client.hris().directory().list(params);
while (page != null) {
for (IndividualInDirectory directory : page.individuals()) {
HrisDirectoryListPage page = client.hris().directory().list();
while (true) {
for (IndividualInDirectory directory : page.items()) {
System.out.println(directory);
}

page = page.getNextPage().orElse(null);
if (!page.hasNextPage()) {
break;
}

page = page.nextPage();
}
```

Expand Down Expand Up @@ -343,7 +391,6 @@ To set a custom timeout, configure the method call using the `timeout` method:

```java
import com.tryfinch.api.models.HrisDirectoryListPage;
import com.tryfinch.api.models.HrisDirectoryListParams;

HrisDirectoryListPage page = client.hris().directory().list(RequestOptions.builder().timeout(Duration.ofSeconds(30)).build());
```
Expand Down Expand Up @@ -573,7 +620,6 @@ Or configure the method call to validate the response using the `responseValidat

```java
import com.tryfinch.api.models.HrisDirectoryListPage;
import com.tryfinch.api.models.HrisDirectoryListParams;

HrisDirectoryListPage page = client.hris().directory().list(RequestOptions.builder().responseValidation(true).build());
```
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repositories {

allprojects {
group = "com.tryfinch.api"
version = "6.0.0" // x-release-please-version
version = "7.0.0" // x-release-please-version
}

subprojects {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
import java.util.concurrent.Executor
import kotlin.jvm.optionals.getOrNull

class FinchOkHttpClient private constructor() {
Expand Down Expand Up @@ -47,6 +48,10 @@ class FinchOkHttpClient private constructor() {

fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }

fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
}

fun clock(clock: Clock) = apply { clientOptions.clock(clock) }

fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
import java.util.concurrent.Executor
import kotlin.jvm.optionals.getOrNull

class FinchOkHttpClientAsync private constructor() {
Expand Down Expand Up @@ -47,6 +48,10 @@ class FinchOkHttpClientAsync private constructor() {

fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }

fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
}

fun clock(clock: Clock) = apply { clientOptions.clock(clock) }

fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// File generated from our OpenAPI spec by Stainless.

package com.tryfinch.api.core

import java.util.stream.Stream
import java.util.stream.StreamSupport

class AutoPager<T> private constructor(private val firstPage: Page<T>) : Iterable<T> {

companion object {

fun <T> from(firstPage: Page<T>): AutoPager<T> = AutoPager(firstPage)
}

override fun iterator(): Iterator<T> =
generateSequence(firstPage) { if (it.hasNextPage()) it.nextPage() else null }
.flatMap { it.items() }
.iterator()

fun stream(): Stream<T> = StreamSupport.stream(spliterator(), false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// File generated from our OpenAPI spec by Stainless.

package com.tryfinch.api.core

import com.tryfinch.api.core.http.AsyncStreamResponse
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicReference

class AutoPagerAsync<T>
private constructor(private val firstPage: PageAsync<T>, private val defaultExecutor: Executor) :
AsyncStreamResponse<T> {

companion object {

fun <T> from(firstPage: PageAsync<T>, defaultExecutor: Executor): AutoPagerAsync<T> =
AutoPagerAsync(firstPage, defaultExecutor)
}

private val onCompleteFuture = CompletableFuture<Void?>()
private val state = AtomicReference(State.NEW)

override fun subscribe(handler: AsyncStreamResponse.Handler<T>): AsyncStreamResponse<T> =
subscribe(handler, defaultExecutor)

override fun subscribe(
handler: AsyncStreamResponse.Handler<T>,
executor: Executor,
): AsyncStreamResponse<T> = apply {
// TODO(JDK): Use `compareAndExchange` once targeting JDK 9.
check(state.compareAndSet(State.NEW, State.SUBSCRIBED)) {
if (state.get() == State.SUBSCRIBED) "Cannot subscribe more than once"
else "Cannot subscribe after the response is closed"
}

fun PageAsync<T>.handle(): CompletableFuture<Void?> {
if (state.get() == State.CLOSED) {
return CompletableFuture.completedFuture(null)
}

items().forEach { handler.onNext(it) }
return if (hasNextPage()) nextPage().thenCompose { it.handle() }
else CompletableFuture.completedFuture(null)
}

executor.execute {
firstPage.handle().whenComplete { _, error ->
val actualError =
if (error is CompletionException && error.cause != null) error.cause else error
try {
handler.onComplete(Optional.ofNullable(actualError))
} finally {
try {
if (actualError == null) {
onCompleteFuture.complete(null)
} else {
onCompleteFuture.completeExceptionally(actualError)
}
} finally {
close()
}
}
}
}
}

override fun onCompleteFuture(): CompletableFuture<Void?> = onCompleteFuture

override fun close() {
val previousState = state.getAndSet(State.CLOSED)
if (previousState == State.CLOSED) {
return
}

// When the stream is closed, we should always consider it closed. If it closed due
// to an error, then we will have already completed the future earlier, and this
// will be a no-op.
onCompleteFuture.complete(null)
}
}

private enum class State {
NEW,
SUBSCRIBED,
CLOSED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package com.tryfinch.api.core
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.core.util.VersionUtil

fun checkRequired(name: String, condition: Boolean) =
check(condition) { "`$name` is required, but was not set" }

fun <T : Any> checkRequired(name: String, value: T?): T =
checkNotNull(value) { "`$name` is required, but was not set" }

Expand Down
Loading