diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ebbaa260..d76a3e43 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,17 +1,19 @@
name: CI
on:
push:
- branches:
- - main
- pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
jobs:
lint:
+ timeout-minutes: 10
name: lint
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'stainless-sdks/orb-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+
steps:
- uses: actions/checkout@v4
@@ -21,7 +23,7 @@ jobs:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
@@ -30,8 +32,9 @@ jobs:
- name: Run lints
run: ./scripts/lint
test:
+ timeout-minutes: 10
name: test
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'stainless-sdks/orb-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v4
@@ -41,7 +44,7 @@ jobs:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index 66b43d31..96822978 100644
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -33,7 +33,7 @@ jobs:
export -- GPG_SIGNING_KEY_ID
printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD"
GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')"
- ./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD"
+ ./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache
env:
SONATYPE_USERNAME: ${{ secrets.ORB_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.ORB_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 87d3d84c..2afb750c 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.56.0"
+ ".": "0.57.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 88277273..c71a786b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 106
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-e8dad7eee5621fe2ba948dfd00dabf170d9d92ce615a9f04b0f546f4d8bf39ba.yml
openapi_spec_hash: 3f6a98e3a1b3a47acebd67a960090ebf
-config_hash: 7e523cf79552b8936bd772f2e1025e5f
+config_hash: f6da12790e8f46d93592def474d41c69
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d6c32c8..e90b3676 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,58 @@
# Changelog
+## 0.57.0 (2025-05-22)
+
+Full Changelog: [v0.56.0...v0.57.0](https://github.com/orbcorp/orb-java/compare/v0.56.0...v0.57.0)
+
+### ⚠ BREAKING CHANGES
+
+* **client:** improve some class names
+* **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
+
+* **client:** allow providing some params positionally ([7ddd872](https://github.com/orbcorp/orb-java/commit/7ddd872ee4fb49e2e3f4b6d5938bc9a5ed0e2342))
+* **client:** extract auto pagination to shared classes ([9affbde](https://github.com/orbcorp/orb-java/commit/9affbdee875bdf75fa150d58ed8de23b9d51eaf2))
+
+
+### Bug Fixes
+
+* **internal:** fix name collision errors from Unit import ([1f7beef](https://github.com/orbcorp/orb-java/commit/1f7beefa05c6cb1356d1973bbcb0955f7eb65fbd))
+
+
+### Performance Improvements
+
+* **internal:** improve compilation+test speed ([f72c4ad](https://github.com/orbcorp/orb-java/commit/f72c4ad7c4604330f2ff972b517b2e3f03f5356f))
+
+
+### Chores
+
+* **ci:** only use depot for staging repos ([68dcdd8](https://github.com/orbcorp/orb-java/commit/68dcdd85f1eac797fae1136e383afae1ad5c0e28))
+* **ci:** run on more branches and use depot runners ([ddcc77e](https://github.com/orbcorp/orb-java/commit/ddcc77e1835fe8ec200fbfeb5d0427ef19fb6d25))
+* **docs:** grammar improvements ([7b5fb07](https://github.com/orbcorp/orb-java/commit/7b5fb07cdbe4b1ac8f41d95de243b913fce60a13))
+* **internal:** codegen related update ([9df208b](https://github.com/orbcorp/orb-java/commit/9df208bf6cf18c90cc09bf1f3eba9ba1e5b4c767))
+* **internal:** codegen related update ([7db8db6](https://github.com/orbcorp/orb-java/commit/7db8db63862be728dc201e36aa3ff164307c5e4e))
+* **internal:** java 17 -> 21 on ci ([7f2fb10](https://github.com/orbcorp/orb-java/commit/7f2fb1063cd794a4c46fb88203944d4262a0a1db))
+* **internal:** remove flaky `-Xbackend-threads=0` option ([b085373](https://github.com/orbcorp/orb-java/commit/b085373fc56676f510a0b0900e8cecd2af633313))
+* **internal:** update java toolchain ([2b4abdf](https://github.com/orbcorp/orb-java/commit/2b4abdff056e4bd158c6a2bc605c4ba09e4760d0))
+
+
+### Documentation
+
+* **client:** update jackson compat error message ([b3ceb27](https://github.com/orbcorp/orb-java/commit/b3ceb2741476209419bd210d7caebec7fbb5eaa7))
+* explain http client customization ([31827ac](https://github.com/orbcorp/orb-java/commit/31827acf060986c871832a7619f09dae7aab1640))
+* explain jackson compat in readme ([fb2505d](https://github.com/orbcorp/orb-java/commit/fb2505d8cdf9fb61589715396da17d424de5f7c0))
+
+
+### Refactors
+
+* **client:** improve some class names ([9460c52](https://github.com/orbcorp/orb-java/commit/9460c523ec5235f4b8b6335336be8e3b82b8527f))
+
## 0.56.0 (2025-04-09)
Full Changelog: [v0.55.0...v0.56.0](https://github.com/orbcorp/orb-java/compare/v0.55.0...v0.56.0)
diff --git a/README.md b/README.md
index 6bb662be..fd591b88 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-[](https://central.sonatype.com/artifact/com.withorb.api/orb-java/0.56.0)
+[](https://central.sonatype.com/artifact/com.withorb.api/orb-java/0.57.0)
@@ -19,7 +19,7 @@ The REST API documentation can be found on [docs.withorb.com](https://docs.witho
### Gradle
```kotlin
-implementation("com.withorb.api:orb-java:0.56.0")
+implementation("com.withorb.api:orb-java:0.57.0")
```
### Maven
@@ -28,7 +28,7 @@ implementation("com.withorb.api:orb-java:0.56.0")
com.withorb.api
orb-java
- 0.56.0
+ 0.57.0
```
@@ -215,53 +215,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.withorb.api.models.Coupon;
import com.withorb.api.models.CouponListPage;
-// As an Iterable:
-CouponListPage page = client.coupons().list(params);
+CouponListPage page = client.coupons().list();
+
+// Process as an Iterable
for (Coupon coupon : page.autoPager()) {
System.out.println(coupon);
-};
+}
-// As a Stream:
-client.coupons().list(params).autoPager().stream()
+// Process as a Stream
+page.autoPager()
+ .stream()
.limit(50)
.forEach(coupon -> System.out.println(coupon));
```
-### Asynchronous
+When using the asynchronous client, the method returns an [`AsyncStreamResponse`](orb-java-core/src/main/kotlin/com/withorb/api/core/http/AsyncStreamResponse.kt):
```java
-// Using forEach, which returns CompletableFuture:
-asyncClient.coupons().list(params).autoPager()
- .forEach(coupon -> System.out.println(coupon), executor);
+import com.withorb.api.core.http.AsyncStreamResponse;
+import com.withorb.api.models.Coupon;
+import com.withorb.api.models.CouponListPageAsync;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+CompletableFuture pageFuture = client.async().coupons().list();
+
+pageFuture.thenRun(page -> page.autoPager().subscribe(coupon -> {
+ System.out.println(coupon);
+}));
+
+// If you need to handle errors or completion of the stream
+pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Handler<>() {
+ @Override
+ public void onNext(Coupon coupon) {
+ System.out.println(coupon);
+ }
+
+ @Override
+ public void onComplete(Optional 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(coupon -> {
+ System.out.println(coupon);
+ })
+ .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.withorb.api.models.Coupon;
import com.withorb.api.models.CouponListPage;
-CouponListPage page = client.coupons().list(params);
-while (page != null) {
- for (Coupon coupon : page.data()) {
+CouponListPage page = client.coupons().list();
+while (true) {
+ for (Coupon coupon : page.items()) {
System.out.println(coupon);
}
- page = page.getNextPage().orElse(null);
+ if (!page.hasNextPage()) {
+ break;
+ }
+
+ page = page.nextPage();
}
```
@@ -291,6 +339,17 @@ both of which will raise an error if the signature is invalid.
Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first).
The `.unwrap()` method can parse this JSON for you.
+## Jackson
+
+The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.18.2 by default.
+
+The SDK throws an exception if it detects an incompatible Jackson version at runtime (e.g. if the default version was overridden in your Maven or Gradle config).
+
+If the SDK threw an exception, but you're _certain_ the version is compatible, then disable the version check using the `checkJacksonVersionCompatibility` on [`OrbOkHttpClient`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt) or [`OrbOkHttpClientAsync`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt).
+
+> [!CAUTION]
+> We make no guarantee that the SDK works correctly when the Jackson version check is disabled.
+
## Network options
### Retries
@@ -327,7 +386,6 @@ To set a custom timeout, configure the method call using the `timeout` method:
```java
import com.withorb.api.models.Customer;
-import com.withorb.api.models.CustomerCreateParams;
Customer customer = client.customers().create(
params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
@@ -367,6 +425,42 @@ OrbClient client = OrbOkHttpClient.builder()
.build();
```
+### Custom HTTP client
+
+The SDK consists of three artifacts:
+
+- `orb-java-core`
+ - Contains core SDK logic
+ - Does not depend on [OkHttp](https://square.github.io/okhttp)
+ - Exposes [`OrbClient`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClient.kt), [`OrbClientAsync`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientAsync.kt), [`OrbClientImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientImpl.kt), and [`OrbClientAsyncImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientAsyncImpl.kt), all of which can work with any HTTP client
+- `orb-java-client-okhttp`
+ - Depends on [OkHttp](https://square.github.io/okhttp)
+ - Exposes [`OrbOkHttpClient`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt) and [`OrbOkHttpClientAsync`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt), which provide a way to construct [`OrbClientImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientImpl.kt) and [`OrbClientAsyncImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientAsyncImpl.kt), respectively, using OkHttp
+- `orb-java`
+ - Depends on and exposes the APIs of both `orb-java-core` and `orb-java-client-okhttp`
+ - Does not have its own logic
+
+This structure allows replacing the SDK's default HTTP client without pulling in unnecessary dependencies.
+
+#### Customized [`OkHttpClient`](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.html)
+
+> [!TIP]
+> Try the available [network options](#network-options) before replacing the default client.
+
+To use a customized `OkHttpClient`:
+
+1. Replace your [`orb-java` dependency](#installation) with `orb-java-core`
+2. Copy `orb-java-client-okhttp`'s [`OkHttpClient`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OkHttpClient.kt) class into your code and customize it
+3. Construct [`OrbClientImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientImpl.kt) or [`OrbClientAsyncImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientAsyncImpl.kt), similarly to [`OrbOkHttpClient`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt) or [`OrbOkHttpClientAsync`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt), using your customized client
+
+### Completely custom HTTP client
+
+To use a completely custom HTTP client:
+
+1. Replace your [`orb-java` dependency](#installation) with `orb-java-core`
+2. Write a class that implements the [`HttpClient`](orb-java-core/src/main/kotlin/com/withorb/api/core/http/HttpClient.kt) interface
+3. Construct [`OrbClientImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientImpl.kt) or [`OrbClientAsyncImpl`](orb-java-core/src/main/kotlin/com/withorb/api/client/OrbClientAsyncImpl.kt), similarly to [`OrbOkHttpClient`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt) or [`OrbOkHttpClientAsync`](orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt), using your new client class
+
## Undocumented API functionality
The SDK is typed for convenient usage of the documented API. However, it also supports working with undocumented or not yet supported parts of the API.
@@ -540,7 +634,6 @@ Or configure the method call to validate the response using the `responseValidat
```java
import com.withorb.api.models.Customer;
-import com.withorb.api.models.CustomerCreateParams;
Customer customer = client.customers().create(
params, RequestOptions.builder().responseValidation(true).build()
diff --git a/SECURITY.md b/SECURITY.md
index 6f64d22c..3011c342 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,11 +16,11 @@ before making any information public.
## Reporting Non-SDK Related Security Issues
If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Orb please follow the respective company's security reporting guidelines.
+or products provided by Orb, please follow the respective company's security reporting guidelines.
### Orb Terms and Policies
-Please contact team@withorb.com for any questions or concerns regarding security of our services.
+Please contact team@withorb.com for any questions or concerns regarding the security of our services.
---
diff --git a/build.gradle.kts b/build.gradle.kts
index 3973ccca..5f840836 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,4 @@
allprojects {
group = "com.withorb.api"
- version = "0.56.0" // x-release-please-version
+ version = "0.57.0" // x-release-please-version
}
diff --git a/buildSrc/src/main/kotlin/orb.java.gradle.kts b/buildSrc/src/main/kotlin/orb.java.gradle.kts
index e39d9ac6..dfbacb86 100644
--- a/buildSrc/src/main/kotlin/orb.java.gradle.kts
+++ b/buildSrc/src/main/kotlin/orb.java.gradle.kts
@@ -21,7 +21,7 @@ configure {
java {
toolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
+ languageVersion.set(JavaLanguageVersion.of(21))
}
sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/buildSrc/src/main/kotlin/orb.kotlin.gradle.kts b/buildSrc/src/main/kotlin/orb.kotlin.gradle.kts
index 55b2fc55..2d4a5c55 100644
--- a/buildSrc/src/main/kotlin/orb.kotlin.gradle.kts
+++ b/buildSrc/src/main/kotlin/orb.kotlin.gradle.kts
@@ -9,7 +9,7 @@ plugins {
kotlin {
jvmToolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
+ languageVersion.set(JavaLanguageVersion.of(21))
}
compilerOptions {
@@ -34,8 +34,7 @@ configure {
}
}
-// Run tests in parallel to some degree.
tasks.withType().configureEach {
- maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
- forkEvery = 100
+ systemProperty("junit.jupiter.execution.parallel.enabled", true)
+ systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
}
diff --git a/gradle.properties b/gradle.properties
index 0c8d4ded..ff76593f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,17 @@
org.gradle.caching=true
+org.gradle.configuration-cache=true
org.gradle.parallel=true
org.gradle.daemon=false
-org.gradle.jvmargs=-Xmx4g
-kotlin.daemon.jvmargs=-Xmx4g
+# These options improve our compilation and test performance. They are inherited by the Kotlin daemon.
+org.gradle.jvmargs=\
+ -Xms1g \
+ -Xmx4g \
+ -XX:+UseParallelGC \
+ -XX:InitialCodeCacheSize=256m \
+ -XX:ReservedCodeCacheSize=1G \
+ -XX:MetaspaceSize=256m \
+ -XX:TieredStopAtLevel=1 \
+ -XX:GCTimeRatio=4 \
+ -XX:CICompilerCount=4 \
+ -XX:+OptimizeStringConcat \
+ -XX:+UseStringDeduplication
diff --git a/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt b/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt
index 458d3da8..df13e8b1 100644
--- a/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt
+++ b/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClient.kt
@@ -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 OrbOkHttpClient private constructor() {
@@ -47,6 +48,10 @@ class OrbOkHttpClient 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) }
diff --git a/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt b/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt
index f8d51c09..b0396fb6 100644
--- a/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt
+++ b/orb-java-client-okhttp/src/main/kotlin/com/withorb/api/client/okhttp/OrbOkHttpClientAsync.kt
@@ -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 OrbOkHttpClientAsync private constructor() {
@@ -47,6 +48,10 @@ class OrbOkHttpClientAsync 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) }
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPager.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPager.kt
new file mode 100644
index 00000000..87762a7c
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPager.kt
@@ -0,0 +1,21 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.withorb.api.core
+
+import java.util.stream.Stream
+import java.util.stream.StreamSupport
+
+class AutoPager private constructor(private val firstPage: Page) : Iterable {
+
+ companion object {
+
+ fun from(firstPage: Page): AutoPager = AutoPager(firstPage)
+ }
+
+ override fun iterator(): Iterator =
+ generateSequence(firstPage) { if (it.hasNextPage()) it.nextPage() else null }
+ .flatMap { it.items() }
+ .iterator()
+
+ fun stream(): Stream = StreamSupport.stream(spliterator(), false)
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPagerAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPagerAsync.kt
new file mode 100644
index 00000000..beeade0a
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/AutoPagerAsync.kt
@@ -0,0 +1,88 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.withorb.api.core
+
+import com.withorb.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
+private constructor(private val firstPage: PageAsync, private val defaultExecutor: Executor) :
+ AsyncStreamResponse {
+
+ companion object {
+
+ fun from(firstPage: PageAsync, defaultExecutor: Executor): AutoPagerAsync =
+ AutoPagerAsync(firstPage, defaultExecutor)
+ }
+
+ private val onCompleteFuture = CompletableFuture()
+ private val state = AtomicReference(State.NEW)
+
+ override fun subscribe(handler: AsyncStreamResponse.Handler): AsyncStreamResponse =
+ subscribe(handler, defaultExecutor)
+
+ override fun subscribe(
+ handler: AsyncStreamResponse.Handler,
+ executor: Executor,
+ ): AsyncStreamResponse = 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.handle(): CompletableFuture {
+ 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 = 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,
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/Check.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/Check.kt
index df5773f1..a88b2cbb 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/core/Check.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/Check.kt
@@ -5,6 +5,9 @@ package com.withorb.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 checkRequired(name: String, value: T?): T =
checkNotNull(value) { "`$name` is required, but was not set" }
@@ -62,7 +65,7 @@ internal fun checkJacksonVersionCompatibility() {
}
check(incompatibleJacksonVersions.isEmpty()) {
"""
-This SDK depends on Jackson version $MINIMUM_JACKSON_VERSION, but the following incompatible Jackson versions were detected at runtime:
+This SDK requires a minimum Jackson version of $MINIMUM_JACKSON_VERSION, but the following incompatible Jackson versions were detected at runtime:
${incompatibleJacksonVersions.asSequence().map { (version, incompatibilityReason) ->
"- `${version.toFullString().replace("/", ":")}` ($incompatibilityReason)"
@@ -73,6 +76,8 @@ This can happen if you are either:
2. Depending on some library that depends on different Jackson versions, potentially transitively
Double-check that you are depending on compatible Jackson versions.
+
+See https://www.github.com/orbcorp/orb-java#jackson for more information.
"""
.trimIndent()
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/ClientOptions.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/ClientOptions.kt
index 33a56193..f1898f51 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/core/ClientOptions.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/ClientOptions.kt
@@ -10,6 +10,10 @@ import com.withorb.api.core.http.QueryParams
import com.withorb.api.core.http.RetryingHttpClient
import java.time.Clock
import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicLong
import kotlin.jvm.optionals.getOrNull
class ClientOptions
@@ -18,6 +22,7 @@ private constructor(
@get:JvmName("httpClient") val httpClient: HttpClient,
@get:JvmName("checkJacksonVersionCompatibility") val checkJacksonVersionCompatibility: Boolean,
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
+ @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
@get:JvmName("clock") val clock: Clock,
@get:JvmName("baseUrl") val baseUrl: String,
@get:JvmName("headers") val headers: Headers,
@@ -63,6 +68,7 @@ private constructor(
private var httpClient: HttpClient? = null
private var checkJacksonVersionCompatibility: Boolean = true
private var jsonMapper: JsonMapper = jsonMapper()
+ private var streamHandlerExecutor: Executor? = null
private var clock: Clock = Clock.systemUTC()
private var baseUrl: String = PRODUCTION_URL
private var headers: Headers.Builder = Headers.builder()
@@ -78,6 +84,7 @@ private constructor(
httpClient = clientOptions.originalHttpClient
checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
jsonMapper = clientOptions.jsonMapper
+ streamHandlerExecutor = clientOptions.streamHandlerExecutor
clock = clientOptions.clock
baseUrl = clientOptions.baseUrl
headers = clientOptions.headers.toBuilder()
@@ -97,6 +104,10 @@ private constructor(
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
fun clock(clock: Clock) = apply { this.clock = clock }
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
@@ -251,6 +262,20 @@ private constructor(
),
checkJacksonVersionCompatibility,
jsonMapper,
+ streamHandlerExecutor
+ ?: Executors.newCachedThreadPool(
+ object : ThreadFactory {
+
+ private val threadFactory: ThreadFactory =
+ Executors.defaultThreadFactory()
+ private val count = AtomicLong(0)
+
+ override fun newThread(runnable: Runnable): Thread =
+ threadFactory.newThread(runnable).also {
+ it.name = "orb-stream-handler-thread-${count.getAndIncrement()}"
+ }
+ }
+ ),
clock,
baseUrl,
headers.build(),
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/Page.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/Page.kt
new file mode 100644
index 00000000..ff2e880d
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/Page.kt
@@ -0,0 +1,33 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.withorb.api.core
+
+/**
+ * An interface representing a single page, with items of type [T], from a paginated endpoint
+ * response.
+ *
+ * Implementations of this interface are expected to request additional pages synchronously. For
+ * asynchronous pagination, see the [PageAsync] interface.
+ */
+interface Page {
+
+ /**
+ * Returns whether there's another page after this one.
+ *
+ * The method generally doesn't make requests so the result depends entirely on the data in this
+ * page. If a significant amount of time has passed between requesting this page and calling
+ * this method, then the result could be stale.
+ */
+ fun hasNextPage(): Boolean
+
+ /**
+ * Returns the page after this one by making another request.
+ *
+ * @throws IllegalStateException if it's impossible to get the next page. This exception is
+ * avoidable by calling [hasNextPage] first.
+ */
+ fun nextPage(): Page
+
+ /** Returns the items in this page. */
+ fun items(): List
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/PageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/PageAsync.kt
new file mode 100644
index 00000000..d97ddae0
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/PageAsync.kt
@@ -0,0 +1,35 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.withorb.api.core
+
+import java.util.concurrent.CompletableFuture
+
+/**
+ * An interface representing a single page, with items of type [T], from a paginated endpoint
+ * response.
+ *
+ * Implementations of this interface are expected to request additional pages asynchronously. For
+ * synchronous pagination, see the [Page] interface.
+ */
+interface PageAsync {
+
+ /**
+ * Returns whether there's another page after this one.
+ *
+ * The method generally doesn't make requests so the result depends entirely on the data in this
+ * page. If a significant amount of time has passed between requesting this page and calling
+ * this method, then the result could be stale.
+ */
+ fun hasNextPage(): Boolean
+
+ /**
+ * Returns the page after this one by making another request.
+ *
+ * @throws IllegalStateException if it's impossible to get the next page. This exception is
+ * avoidable by calling [hasNextPage] first.
+ */
+ fun nextPage(): CompletableFuture>
+
+ /** Returns the items in this page. */
+ fun items(): List
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/AsyncStreamResponse.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/AsyncStreamResponse.kt
new file mode 100644
index 00000000..8b58fd92
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/AsyncStreamResponse.kt
@@ -0,0 +1,157 @@
+package com.withorb.api.core.http
+
+import com.withorb.api.core.http.AsyncStreamResponse.Handler
+import java.util.Optional
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * A class providing access to an API response as an asynchronous stream of chunks of type [T],
+ * where each chunk can be individually processed as soon as it arrives instead of waiting on the
+ * full response.
+ */
+interface AsyncStreamResponse {
+
+ /**
+ * Registers [handler] to be called for events of this stream.
+ *
+ * [handler]'s methods will be called in the client's configured or default thread pool.
+ *
+ * @throws IllegalStateException if [subscribe] has already been called.
+ */
+ fun subscribe(handler: Handler): AsyncStreamResponse
+
+ /**
+ * Registers [handler] to be called for events of this stream.
+ *
+ * [handler]'s methods will be called in the given [executor].
+ *
+ * @throws IllegalStateException if [subscribe] has already been called.
+ */
+ fun subscribe(handler: Handler, executor: Executor): AsyncStreamResponse
+
+ /**
+ * Returns a future that completes when a stream is fully consumed, errors, or gets closed
+ * early.
+ */
+ fun onCompleteFuture(): CompletableFuture
+
+ /**
+ * Closes this resource, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because this response should not be
+ * synchronously closed via try-with-resources.
+ */
+ fun close()
+
+ /** A class for handling streaming events. */
+ fun interface Handler {
+
+ /** Called whenever a chunk is received. */
+ fun onNext(value: T)
+
+ /**
+ * Called when a stream is fully consumed, errors, or gets closed early.
+ *
+ * [onNext] will not be called once this method is called.
+ *
+ * @param error Non-empty if the stream completed due to an error.
+ */
+ fun onComplete(error: Optional) {}
+ }
+}
+
+@JvmSynthetic
+internal fun CompletableFuture>.toAsync(streamHandlerExecutor: Executor) =
+ PhantomReachableClosingAsyncStreamResponse(
+ object : AsyncStreamResponse {
+
+ private val onCompleteFuture = CompletableFuture()
+ private val state = AtomicReference(State.NEW)
+
+ init {
+ this@toAsync.whenComplete { _, error ->
+ // If an error occurs from the original future, then we should resolve the
+ // `onCompleteFuture` even if `subscribe` has not been called.
+ error?.let(onCompleteFuture::completeExceptionally)
+ }
+ }
+
+ override fun subscribe(handler: Handler): AsyncStreamResponse =
+ subscribe(handler, streamHandlerExecutor)
+
+ override fun subscribe(
+ handler: Handler,
+ executor: Executor,
+ ): AsyncStreamResponse = 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"
+ }
+
+ this@toAsync.whenCompleteAsync(
+ { streamResponse, futureError ->
+ if (state.get() == State.CLOSED) {
+ // Avoid doing any work if `close` was called before the future
+ // completed.
+ return@whenCompleteAsync
+ }
+
+ if (futureError != null) {
+ // An error occurred before we started passing chunks to the handler.
+ handler.onComplete(Optional.of(futureError))
+ return@whenCompleteAsync
+ }
+
+ var streamError: Throwable? = null
+ try {
+ streamResponse.stream().forEach(handler::onNext)
+ } catch (e: Throwable) {
+ streamError = e
+ }
+
+ try {
+ handler.onComplete(Optional.ofNullable(streamError))
+ } finally {
+ try {
+ // Notify completion via the `onCompleteFuture` as well. This is in
+ // a separate `try-finally` block so that we still complete the
+ // future if `handler.onComplete` throws.
+ if (streamError == null) {
+ onCompleteFuture.complete(null)
+ } else {
+ onCompleteFuture.completeExceptionally(streamError)
+ }
+ } finally {
+ close()
+ }
+ }
+ },
+ executor,
+ )
+ }
+
+ override fun onCompleteFuture(): CompletableFuture = onCompleteFuture
+
+ override fun close() {
+ val previousState = state.getAndSet(State.CLOSED)
+ if (previousState == State.CLOSED) {
+ return
+ }
+
+ this@toAsync.whenComplete { streamResponse, error -> streamResponse?.close() }
+ // 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,
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt
new file mode 100644
index 00000000..53a660b4
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt
@@ -0,0 +1,56 @@
+package com.withorb.api.core.http
+
+import com.withorb.api.core.closeWhenPhantomReachable
+import com.withorb.api.core.http.AsyncStreamResponse.Handler
+import java.util.Optional
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+
+/**
+ * A delegating wrapper around an `AsyncStreamResponse` that closes it once it's only phantom
+ * reachable.
+ *
+ * This class ensures the `AsyncStreamResponse` is closed even if the user forgets to close it.
+ */
+internal class PhantomReachableClosingAsyncStreamResponse(
+ private val asyncStreamResponse: AsyncStreamResponse
+) : AsyncStreamResponse {
+
+ /**
+ * An object used for keeping `asyncStreamResponse` open while the object is still reachable.
+ */
+ private val reachabilityTracker = Object()
+
+ init {
+ closeWhenPhantomReachable(reachabilityTracker, asyncStreamResponse::close)
+ }
+
+ override fun subscribe(handler: Handler): AsyncStreamResponse = apply {
+ asyncStreamResponse.subscribe(TrackedHandler(handler, reachabilityTracker))
+ }
+
+ override fun subscribe(handler: Handler, executor: Executor): AsyncStreamResponse =
+ apply {
+ asyncStreamResponse.subscribe(TrackedHandler(handler, reachabilityTracker), executor)
+ }
+
+ override fun onCompleteFuture(): CompletableFuture =
+ asyncStreamResponse.onCompleteFuture()
+
+ override fun close() = asyncStreamResponse.close()
+}
+
+/**
+ * A wrapper around a `Handler` that also references a `reachabilityTracker` object.
+ *
+ * Referencing the `reachabilityTracker` object prevents it from getting reclaimed while the handler
+ * is still reachable.
+ */
+private class TrackedHandler(
+ private val handler: Handler,
+ private val reachabilityTracker: Any,
+) : Handler {
+ override fun onNext(value: T) = handler.onNext(value)
+
+ override fun onComplete(error: Optional) = handler.onComplete(error)
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingStreamResponse.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingStreamResponse.kt
new file mode 100644
index 00000000..161638de
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/PhantomReachableClosingStreamResponse.kt
@@ -0,0 +1,21 @@
+package com.withorb.api.core.http
+
+import com.withorb.api.core.closeWhenPhantomReachable
+import java.util.stream.Stream
+
+/**
+ * A delegating wrapper around a `StreamResponse` that closes it once it's only phantom reachable.
+ *
+ * This class ensures the `StreamResponse` is closed even if the user forgets to close it.
+ */
+internal class PhantomReachableClosingStreamResponse(
+ private val streamResponse: StreamResponse
+) : StreamResponse {
+ init {
+ closeWhenPhantomReachable(this, streamResponse)
+ }
+
+ override fun stream(): Stream = streamResponse.stream()
+
+ override fun close() = streamResponse.close()
+}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/RetryingHttpClient.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/RetryingHttpClient.kt
index 93413092..cba6f15b 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/RetryingHttpClient.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/RetryingHttpClient.kt
@@ -23,6 +23,7 @@ import kotlin.math.pow
class RetryingHttpClient
private constructor(
private val httpClient: HttpClient,
+ private val sleeper: Sleeper,
private val clock: Clock,
private val maxRetries: Int,
private val idempotencyHeader: String?,
@@ -62,10 +63,10 @@ private constructor(
null
}
- val backoffMillis = getRetryBackoffMillis(retries, response)
+ val backoffDuration = getRetryBackoffDuration(retries, response)
// All responses must be closed, so close the failed one before retrying.
response?.close()
- Thread.sleep(backoffMillis.toMillis())
+ sleeper.sleep(backoffDuration)
}
}
@@ -111,10 +112,10 @@ private constructor(
}
}
- val backoffMillis = getRetryBackoffMillis(retries, response)
+ val backoffDuration = getRetryBackoffDuration(retries, response)
// All responses must be closed, so close the failed one before retrying.
response?.close()
- return sleepAsync(backoffMillis.toMillis()).thenCompose {
+ return sleeper.sleepAsync(backoffDuration).thenCompose {
executeWithRetries(requestWithRetryCount, requestOptions)
}
}
@@ -179,7 +180,7 @@ private constructor(
// retried.
throwable is IOException || throwable is OrbIoException
- private fun getRetryBackoffMillis(retries: Int, response: HttpResponse?): Duration {
+ private fun getRetryBackoffDuration(retries: Int, response: HttpResponse?): Duration {
// About the Retry-After header:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
response
@@ -226,33 +227,40 @@ private constructor(
companion object {
- private val TIMER = Timer("RetryingHttpClient", true)
-
- private fun sleepAsync(millis: Long): CompletableFuture {
- val future = CompletableFuture()
- TIMER.schedule(
- object : TimerTask() {
- override fun run() {
- future.complete(null)
- }
- },
- millis,
- )
- return future
- }
-
@JvmStatic fun builder() = Builder()
}
class Builder internal constructor() {
private var httpClient: HttpClient? = null
+ private var sleeper: Sleeper =
+ object : Sleeper {
+
+ private val timer = Timer("RetryingHttpClient", true)
+
+ override fun sleep(duration: Duration) = Thread.sleep(duration.toMillis())
+
+ override fun sleepAsync(duration: Duration): CompletableFuture {
+ val future = CompletableFuture()
+ timer.schedule(
+ object : TimerTask() {
+ override fun run() {
+ future.complete(null)
+ }
+ },
+ duration.toMillis(),
+ )
+ return future
+ }
+ }
private var clock: Clock = Clock.systemUTC()
private var maxRetries: Int = 2
private var idempotencyHeader: String? = null
fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
+ @JvmSynthetic internal fun sleeper(sleeper: Sleeper) = apply { this.sleeper = sleeper }
+
fun clock(clock: Clock) = apply { this.clock = clock }
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
@@ -262,9 +270,17 @@ private constructor(
fun build(): HttpClient =
RetryingHttpClient(
checkRequired("httpClient", httpClient),
+ sleeper,
clock,
maxRetries,
idempotencyHeader,
)
}
+
+ internal interface Sleeper {
+
+ fun sleep(duration: Duration)
+
+ fun sleepAsync(duration: Duration): CompletableFuture
+ }
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/core/http/StreamResponse.kt b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/StreamResponse.kt
new file mode 100644
index 00000000..9c6597ff
--- /dev/null
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/core/http/StreamResponse.kt
@@ -0,0 +1,19 @@
+package com.withorb.api.core.http
+
+import java.util.stream.Stream
+
+interface StreamResponse : AutoCloseable {
+
+ fun stream(): Stream
+
+ /** Overridden from [AutoCloseable] to not have a checked exception in its signature. */
+ override fun close()
+}
+
+@JvmSynthetic
+internal fun StreamResponse.map(transform: (T) -> R): StreamResponse =
+ object : StreamResponse {
+ override fun stream(): Stream = this@map.stream().map(transform)
+
+ override fun close() = this@map.close()
+ }
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForCustomerParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForCustomerParams.kt
index c05cc280..7e5df171 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForCustomerParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForCustomerParams.kt
@@ -33,13 +33,13 @@ import kotlin.jvm.optionals.getOrNull
*/
class AlertCreateForCustomerParams
private constructor(
- private val customerId: String,
+ private val customerId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun customerId(): String = customerId
+ fun customerId(): Optional = Optional.ofNullable(customerId)
/**
* The case sensitive currency or custom pricing unit to use for this alert.
@@ -101,7 +101,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .customerId()
* .currency()
* .type()
* ```
@@ -125,7 +124,10 @@ private constructor(
additionalQueryParams = alertCreateForCustomerParams.additionalQueryParams.toBuilder()
}
- fun customerId(customerId: String) = apply { this.customerId = customerId }
+ fun customerId(customerId: String?) = apply { this.customerId = customerId }
+
+ /** Alias for calling [Builder.customerId] with `customerId.orElse(null)`. */
+ fun customerId(customerId: Optional) = customerId(customerId.getOrNull())
/**
* Sets the entire request body.
@@ -308,7 +310,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .customerId()
* .currency()
* .type()
* ```
@@ -317,7 +318,7 @@ private constructor(
*/
fun build(): AlertCreateForCustomerParams =
AlertCreateForCustomerParams(
- checkRequired("customerId", customerId),
+ customerId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -328,7 +329,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> customerId
+ 0 -> customerId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForExternalCustomerParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForExternalCustomerParams.kt
index 73df3de4..d1787b4e 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForExternalCustomerParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForExternalCustomerParams.kt
@@ -33,13 +33,13 @@ import kotlin.jvm.optionals.getOrNull
*/
class AlertCreateForExternalCustomerParams
private constructor(
- private val externalCustomerId: String,
+ private val externalCustomerId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun externalCustomerId(): String = externalCustomerId
+ fun externalCustomerId(): Optional = Optional.ofNullable(externalCustomerId)
/**
* The case sensitive currency or custom pricing unit to use for this alert.
@@ -102,7 +102,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .externalCustomerId()
* .currency()
* .type()
* ```
@@ -129,10 +128,16 @@ private constructor(
alertCreateForExternalCustomerParams.additionalQueryParams.toBuilder()
}
- fun externalCustomerId(externalCustomerId: String) = apply {
+ fun externalCustomerId(externalCustomerId: String?) = apply {
this.externalCustomerId = externalCustomerId
}
+ /**
+ * Alias for calling [Builder.externalCustomerId] with `externalCustomerId.orElse(null)`.
+ */
+ fun externalCustomerId(externalCustomerId: Optional) =
+ externalCustomerId(externalCustomerId.getOrNull())
+
/**
* Sets the entire request body.
*
@@ -314,7 +319,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .externalCustomerId()
* .currency()
* .type()
* ```
@@ -323,7 +327,7 @@ private constructor(
*/
fun build(): AlertCreateForExternalCustomerParams =
AlertCreateForExternalCustomerParams(
- checkRequired("externalCustomerId", externalCustomerId),
+ externalCustomerId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -334,7 +338,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> externalCustomerId
+ 0 -> externalCustomerId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForSubscriptionParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForSubscriptionParams.kt
index 00d15efb..6cc5c393 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForSubscriptionParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertCreateForSubscriptionParams.kt
@@ -37,13 +37,13 @@ import kotlin.jvm.optionals.getOrNull
*/
class AlertCreateForSubscriptionParams
private constructor(
- private val subscriptionId: String,
+ private val subscriptionId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun subscriptionId(): String = subscriptionId
+ fun subscriptionId(): Optional = Optional.ofNullable(subscriptionId)
/**
* The thresholds that define the values at which the alert will be triggered.
@@ -106,7 +106,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .subscriptionId()
* .thresholds()
* .type()
* ```
@@ -132,7 +131,11 @@ private constructor(
alertCreateForSubscriptionParams.additionalQueryParams.toBuilder()
}
- fun subscriptionId(subscriptionId: String) = apply { this.subscriptionId = subscriptionId }
+ fun subscriptionId(subscriptionId: String?) = apply { this.subscriptionId = subscriptionId }
+
+ /** Alias for calling [Builder.subscriptionId] with `subscriptionId.orElse(null)`. */
+ fun subscriptionId(subscriptionId: Optional) =
+ subscriptionId(subscriptionId.getOrNull())
/**
* Sets the entire request body.
@@ -315,7 +318,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .subscriptionId()
* .thresholds()
* .type()
* ```
@@ -324,7 +326,7 @@ private constructor(
*/
fun build(): AlertCreateForSubscriptionParams =
AlertCreateForSubscriptionParams(
- checkRequired("subscriptionId", subscriptionId),
+ subscriptionId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -335,7 +337,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> subscriptionId
+ 0 -> subscriptionId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertDisableParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertDisableParams.kt
index f0fb5a14..0e1f15a1 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertDisableParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertDisableParams.kt
@@ -4,7 +4,6 @@ package com.withorb.api.models
import com.withorb.api.core.JsonValue
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import com.withorb.api.core.toImmutable
@@ -19,14 +18,14 @@ import kotlin.jvm.optionals.getOrNull
*/
class AlertDisableParams
private constructor(
- private val alertConfigurationId: String,
+ private val alertConfigurationId: String?,
private val subscriptionId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
- fun alertConfigurationId(): String = alertConfigurationId
+ fun alertConfigurationId(): Optional = Optional.ofNullable(alertConfigurationId)
/** Used to update the status of a plan alert scoped to this subscription_id */
fun subscriptionId(): Optional = Optional.ofNullable(subscriptionId)
@@ -41,14 +40,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AlertDisableParams].
- *
- * The following fields are required:
- * ```java
- * .alertConfigurationId()
- * ```
- */
+ @JvmStatic fun none(): AlertDisableParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AlertDisableParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -70,10 +64,17 @@ private constructor(
additionalBodyProperties = alertDisableParams.additionalBodyProperties.toMutableMap()
}
- fun alertConfigurationId(alertConfigurationId: String) = apply {
+ fun alertConfigurationId(alertConfigurationId: String?) = apply {
this.alertConfigurationId = alertConfigurationId
}
+ /**
+ * Alias for calling [Builder.alertConfigurationId] with
+ * `alertConfigurationId.orElse(null)`.
+ */
+ fun alertConfigurationId(alertConfigurationId: Optional) =
+ alertConfigurationId(alertConfigurationId.getOrNull())
+
/** Used to update the status of a plan alert scoped to this subscription_id */
fun subscriptionId(subscriptionId: String?) = apply { this.subscriptionId = subscriptionId }
@@ -205,17 +206,10 @@ private constructor(
* Returns an immutable instance of [AlertDisableParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .alertConfigurationId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AlertDisableParams =
AlertDisableParams(
- checkRequired("alertConfigurationId", alertConfigurationId),
+ alertConfigurationId,
subscriptionId,
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -228,7 +222,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> alertConfigurationId
+ 0 -> alertConfigurationId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertEnableParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertEnableParams.kt
index beb1c38c..726e00d0 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertEnableParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertEnableParams.kt
@@ -4,7 +4,6 @@ package com.withorb.api.models
import com.withorb.api.core.JsonValue
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import com.withorb.api.core.toImmutable
@@ -19,14 +18,14 @@ import kotlin.jvm.optionals.getOrNull
*/
class AlertEnableParams
private constructor(
- private val alertConfigurationId: String,
+ private val alertConfigurationId: String?,
private val subscriptionId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
- fun alertConfigurationId(): String = alertConfigurationId
+ fun alertConfigurationId(): Optional = Optional.ofNullable(alertConfigurationId)
/** Used to update the status of a plan alert scoped to this subscription_id */
fun subscriptionId(): Optional = Optional.ofNullable(subscriptionId)
@@ -41,14 +40,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AlertEnableParams].
- *
- * The following fields are required:
- * ```java
- * .alertConfigurationId()
- * ```
- */
+ @JvmStatic fun none(): AlertEnableParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AlertEnableParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -70,10 +64,17 @@ private constructor(
additionalBodyProperties = alertEnableParams.additionalBodyProperties.toMutableMap()
}
- fun alertConfigurationId(alertConfigurationId: String) = apply {
+ fun alertConfigurationId(alertConfigurationId: String?) = apply {
this.alertConfigurationId = alertConfigurationId
}
+ /**
+ * Alias for calling [Builder.alertConfigurationId] with
+ * `alertConfigurationId.orElse(null)`.
+ */
+ fun alertConfigurationId(alertConfigurationId: Optional) =
+ alertConfigurationId(alertConfigurationId.getOrNull())
+
/** Used to update the status of a plan alert scoped to this subscription_id */
fun subscriptionId(subscriptionId: String?) = apply { this.subscriptionId = subscriptionId }
@@ -205,17 +206,10 @@ private constructor(
* Returns an immutable instance of [AlertEnableParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .alertConfigurationId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AlertEnableParams =
AlertEnableParams(
- checkRequired("alertConfigurationId", alertConfigurationId),
+ alertConfigurationId,
subscriptionId,
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -228,7 +222,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> alertConfigurationId
+ 0 -> alertConfigurationId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPage.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPage.kt
index e34c1eaf..626b3e7e 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPage.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPage.kt
@@ -2,12 +2,12 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPager
+import com.withorb.api.core.Page
import com.withorb.api.core.checkRequired
import com.withorb.api.services.blocking.AlertService
import java.util.Objects
import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
/** @see [AlertService.list] */
@@ -16,7 +16,7 @@ private constructor(
private val service: AlertService,
private val params: AlertListParams,
private val response: AlertListPageResponse,
-) {
+) : Page {
/**
* Delegates to [AlertListPageResponse], but gracefully handles missing data.
@@ -33,30 +33,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): AlertListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): Optional = getNextPageParams().map { service.list(it) }
+ override fun nextPage(): AlertListPage = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPager = AutoPager.from(this)
/** The parameters that were used to request this page. */
fun params(): AlertListParams = params
@@ -125,25 +117,6 @@ private constructor(
)
}
- class AutoPager(private val firstPage: AlertListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.data().size) {
- yield(page.data()[index++])
- }
- page = page.getNextPage().getOrNull() ?: break
- index = 0
- }
- }
-
- fun stream(): Stream {
- return StreamSupport.stream(spliterator(), false)
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPageAsync.kt
index 8a37ce8d..ff9b1277 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPageAsync.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertListPageAsync.kt
@@ -2,22 +2,24 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPagerAsync
+import com.withorb.api.core.PageAsync
import com.withorb.api.core.checkRequired
import com.withorb.api.services.async.AlertServiceAsync
import java.util.Objects
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
-import java.util.function.Predicate
import kotlin.jvm.optionals.getOrNull
/** @see [AlertServiceAsync.list] */
class AlertListPageAsync
private constructor(
private val service: AlertServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: AlertListParams,
private val response: AlertListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [AlertListPageResponse], but gracefully handles missing data.
@@ -34,33 +36,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): AlertListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): CompletableFuture> =
- getNextPageParams()
- .map { service.list(it).thenApply { Optional.of(it) } }
- .orElseGet { CompletableFuture.completedFuture(Optional.empty()) }
+ override fun nextPage(): CompletableFuture = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPagerAsync = AutoPagerAsync.from(this, streamHandlerExecutor)
/** The parameters that were used to request this page. */
fun params(): AlertListParams = params
@@ -78,6 +69,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -89,18 +81,24 @@ private constructor(
class Builder internal constructor() {
private var service: AlertServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: AlertListParams? = null
private var response: AlertListPageResponse? = null
@JvmSynthetic
internal fun from(alertListPageAsync: AlertListPageAsync) = apply {
service = alertListPageAsync.service
+ streamHandlerExecutor = alertListPageAsync.streamHandlerExecutor
params = alertListPageAsync.params
response = alertListPageAsync.response
}
fun service(service: AlertServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: AlertListParams) = apply { this.params = params }
@@ -115,6 +113,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -124,47 +123,22 @@ private constructor(
fun build(): AlertListPageAsync =
AlertListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: AlertListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (Alert) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.data().all(action) }
- .map { it.getNextPage().forEach(action, executor) }
- .orElseGet { CompletableFuture.completedFuture(null) }
- },
- executor,
- )
- return CompletableFuture.completedFuture(Optional.of(firstPage))
- .forEach(action::test, executor)
- }
-
- fun toList(executor: Executor): CompletableFuture> {
- val values = mutableListOf()
- return forEach(values::add, executor).thenApply { values }
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is AlertListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return /* spotless:off */ other is AlertListPageAsync && service == other.service && streamHandlerExecutor == other.streamHandlerExecutor && params == other.params && response == other.response /* spotless:on */
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, streamHandlerExecutor, params, response) /* spotless:on */
override fun toString() =
- "AlertListPageAsync{service=$service, params=$params, response=$response}"
+ "AlertListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertRetrieveParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertRetrieveParams.kt
index 0f01a409..1f780152 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertRetrieveParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertRetrieveParams.kt
@@ -3,20 +3,21 @@
package com.withorb.api.models
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** This endpoint retrieves an alert by its ID. */
class AlertRetrieveParams
private constructor(
- private val alertId: String,
+ private val alertId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun alertId(): String = alertId
+ fun alertId(): Optional = Optional.ofNullable(alertId)
fun _additionalHeaders(): Headers = additionalHeaders
@@ -26,14 +27,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AlertRetrieveParams].
- *
- * The following fields are required:
- * ```java
- * .alertId()
- * ```
- */
+ @JvmStatic fun none(): AlertRetrieveParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AlertRetrieveParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -51,7 +47,10 @@ private constructor(
additionalQueryParams = alertRetrieveParams.additionalQueryParams.toBuilder()
}
- fun alertId(alertId: String) = apply { this.alertId = alertId }
+ fun alertId(alertId: String?) = apply { this.alertId = alertId }
+
+ /** Alias for calling [Builder.alertId] with `alertId.orElse(null)`. */
+ fun alertId(alertId: Optional) = alertId(alertId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -155,25 +154,14 @@ private constructor(
* Returns an immutable instance of [AlertRetrieveParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .alertId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AlertRetrieveParams =
- AlertRetrieveParams(
- checkRequired("alertId", alertId),
- additionalHeaders.build(),
- additionalQueryParams.build(),
- )
+ AlertRetrieveParams(alertId, additionalHeaders.build(), additionalQueryParams.build())
}
fun _pathParam(index: Int): String =
when (index) {
- 0 -> alertId
+ 0 -> alertId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertUpdateParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertUpdateParams.kt
index f61a35f6..6dda2bc8 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertUpdateParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/AlertUpdateParams.kt
@@ -19,18 +19,19 @@ import com.withorb.api.core.toImmutable
import com.withorb.api.errors.OrbInvalidDataException
import java.util.Collections
import java.util.Objects
+import java.util.Optional
import kotlin.jvm.optionals.getOrNull
/** This endpoint updates the thresholds of an alert. */
class AlertUpdateParams
private constructor(
- private val alertConfigurationId: String,
+ private val alertConfigurationId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun alertConfigurationId(): String = alertConfigurationId
+ fun alertConfigurationId(): Optional = Optional.ofNullable(alertConfigurationId)
/**
* The thresholds that define the values at which the alert will be triggered.
@@ -62,7 +63,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .alertConfigurationId()
* .thresholds()
* ```
*/
@@ -85,10 +85,17 @@ private constructor(
additionalQueryParams = alertUpdateParams.additionalQueryParams.toBuilder()
}
- fun alertConfigurationId(alertConfigurationId: String) = apply {
+ fun alertConfigurationId(alertConfigurationId: String?) = apply {
this.alertConfigurationId = alertConfigurationId
}
+ /**
+ * Alias for calling [Builder.alertConfigurationId] with
+ * `alertConfigurationId.orElse(null)`.
+ */
+ fun alertConfigurationId(alertConfigurationId: Optional) =
+ alertConfigurationId(alertConfigurationId.getOrNull())
+
/**
* Sets the entire request body.
*
@@ -243,7 +250,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .alertConfigurationId()
* .thresholds()
* ```
*
@@ -251,7 +257,7 @@ private constructor(
*/
fun build(): AlertUpdateParams =
AlertUpdateParams(
- checkRequired("alertConfigurationId", alertConfigurationId),
+ alertConfigurationId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -262,7 +268,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> alertConfigurationId
+ 0 -> alertConfigurationId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponArchiveParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponArchiveParams.kt
index c1dfe5f3..f8cff3dc 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponArchiveParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponArchiveParams.kt
@@ -4,12 +4,12 @@ package com.withorb.api.models
import com.withorb.api.core.JsonValue
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import com.withorb.api.core.toImmutable
import java.util.Objects
import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/**
* This endpoint allows a coupon to be archived. Archived coupons can no longer be redeemed, and
@@ -18,13 +18,13 @@ import java.util.Optional
*/
class CouponArchiveParams
private constructor(
- private val couponId: String,
+ private val couponId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
- fun couponId(): String = couponId
+ fun couponId(): Optional = Optional.ofNullable(couponId)
fun _additionalBodyProperties(): Map = additionalBodyProperties
@@ -36,14 +36,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [CouponArchiveParams].
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
- */
+ @JvmStatic fun none(): CouponArchiveParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [CouponArchiveParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -63,7 +58,10 @@ private constructor(
additionalBodyProperties = couponArchiveParams.additionalBodyProperties.toMutableMap()
}
- fun couponId(couponId: String) = apply { this.couponId = couponId }
+ fun couponId(couponId: String?) = apply { this.couponId = couponId }
+
+ /** Alias for calling [Builder.couponId] with `couponId.orElse(null)`. */
+ fun couponId(couponId: Optional) = couponId(couponId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -189,17 +187,10 @@ private constructor(
* Returns an immutable instance of [CouponArchiveParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): CouponArchiveParams =
CouponArchiveParams(
- checkRequired("couponId", couponId),
+ couponId,
additionalHeaders.build(),
additionalQueryParams.build(),
additionalBodyProperties.toImmutable(),
@@ -211,7 +202,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> couponId
+ 0 -> couponId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponCreateParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponCreateParams.kt
index 90f95717..2c7b8bf3 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponCreateParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponCreateParams.kt
@@ -15,7 +15,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.withorb.api.core.BaseDeserializer
import com.withorb.api.core.BaseSerializer
-import com.withorb.api.core.Enum
import com.withorb.api.core.ExcludeMissing
import com.withorb.api.core.JsonField
import com.withorb.api.core.JsonMissing
@@ -162,43 +161,33 @@ private constructor(
*/
fun discount(discount: JsonField) = apply { body.discount(discount) }
- /**
- * Alias for calling [discount] with `Discount.ofNewCouponPercentage(newCouponPercentage)`.
- */
- fun discount(newCouponPercentage: Discount.NewCouponPercentageDiscount) = apply {
- body.discount(newCouponPercentage)
- }
+ /** Alias for calling [discount] with `Discount.ofPercentage(percentage)`. */
+ fun discount(percentage: Discount.Percentage) = apply { body.discount(percentage) }
/**
* Alias for calling [discount] with the following:
* ```java
- * Discount.NewCouponPercentageDiscount.builder()
- * .discountType(CouponCreateParams.Discount.NewCouponPercentageDiscount.DiscountType.PERCENTAGE)
+ * Discount.Percentage.builder()
* .percentageDiscount(percentageDiscount)
* .build()
* ```
*/
- fun newCouponPercentageDiscount(percentageDiscount: Double) = apply {
- body.newCouponPercentageDiscount(percentageDiscount)
+ fun percentageDiscount(percentageDiscount: Double) = apply {
+ body.percentageDiscount(percentageDiscount)
}
- /** Alias for calling [discount] with `Discount.ofNewCouponAmount(newCouponAmount)`. */
- fun discount(newCouponAmount: Discount.NewCouponAmountDiscount) = apply {
- body.discount(newCouponAmount)
- }
+ /** Alias for calling [discount] with `Discount.ofAmount(amount)`. */
+ fun discount(amount: Discount.Amount) = apply { body.discount(amount) }
/**
* Alias for calling [discount] with the following:
* ```java
- * Discount.NewCouponAmountDiscount.builder()
- * .discountType(CouponCreateParams.Discount.NewCouponAmountDiscount.DiscountType.AMOUNT)
+ * Discount.Amount.builder()
* .amountDiscount(amountDiscount)
* .build()
* ```
*/
- fun newCouponAmountDiscount(amountDiscount: String) = apply {
- body.newCouponAmountDiscount(amountDiscount)
- }
+ fun amountDiscount(amountDiscount: String) = apply { body.amountDiscount(amountDiscount) }
/** This string can be used to redeem this coupon for a given subscription. */
fun redemptionCode(redemptionCode: String) = apply { body.redemptionCode(redemptionCode) }
@@ -565,55 +554,36 @@ private constructor(
*/
fun discount(discount: JsonField) = apply { this.discount = discount }
- /**
- * Alias for calling [discount] with
- * `Discount.ofNewCouponPercentage(newCouponPercentage)`.
- */
- fun discount(newCouponPercentage: Discount.NewCouponPercentageDiscount) =
- discount(Discount.ofNewCouponPercentage(newCouponPercentage))
+ /** Alias for calling [discount] with `Discount.ofPercentage(percentage)`. */
+ fun discount(percentage: Discount.Percentage) =
+ discount(Discount.ofPercentage(percentage))
/**
* Alias for calling [discount] with the following:
* ```java
- * Discount.NewCouponPercentageDiscount.builder()
- * .discountType(CouponCreateParams.Discount.NewCouponPercentageDiscount.DiscountType.PERCENTAGE)
+ * Discount.Percentage.builder()
* .percentageDiscount(percentageDiscount)
* .build()
* ```
*/
- fun newCouponPercentageDiscount(percentageDiscount: Double) =
+ fun percentageDiscount(percentageDiscount: Double) =
discount(
- Discount.NewCouponPercentageDiscount.builder()
- .discountType(
- CouponCreateParams.Discount.NewCouponPercentageDiscount.DiscountType
- .PERCENTAGE
- )
- .percentageDiscount(percentageDiscount)
- .build()
+ Discount.Percentage.builder().percentageDiscount(percentageDiscount).build()
)
- /** Alias for calling [discount] with `Discount.ofNewCouponAmount(newCouponAmount)`. */
- fun discount(newCouponAmount: Discount.NewCouponAmountDiscount) =
- discount(Discount.ofNewCouponAmount(newCouponAmount))
+ /** Alias for calling [discount] with `Discount.ofAmount(amount)`. */
+ fun discount(amount: Discount.Amount) = discount(Discount.ofAmount(amount))
/**
* Alias for calling [discount] with the following:
* ```java
- * Discount.NewCouponAmountDiscount.builder()
- * .discountType(CouponCreateParams.Discount.NewCouponAmountDiscount.DiscountType.AMOUNT)
+ * Discount.Amount.builder()
* .amountDiscount(amountDiscount)
* .build()
* ```
*/
- fun newCouponAmountDiscount(amountDiscount: String) =
- discount(
- Discount.NewCouponAmountDiscount.builder()
- .discountType(
- CouponCreateParams.Discount.NewCouponAmountDiscount.DiscountType.AMOUNT
- )
- .amountDiscount(amountDiscount)
- .build()
- )
+ fun amountDiscount(amountDiscount: String) =
+ discount(Discount.Amount.builder().amountDiscount(amountDiscount).build())
/** This string can be used to redeem this coupon for a given subscription. */
fun redemptionCode(redemptionCode: String) =
@@ -790,33 +760,29 @@ private constructor(
@JsonSerialize(using = Discount.Serializer::class)
class Discount
private constructor(
- private val newCouponPercentage: NewCouponPercentageDiscount? = null,
- private val newCouponAmount: NewCouponAmountDiscount? = null,
+ private val percentage: Percentage? = null,
+ private val amount: Amount? = null,
private val _json: JsonValue? = null,
) {
- fun newCouponPercentage(): Optional =
- Optional.ofNullable(newCouponPercentage)
+ fun percentage(): Optional = Optional.ofNullable(percentage)
- fun newCouponAmount(): Optional =
- Optional.ofNullable(newCouponAmount)
+ fun amount(): Optional = Optional.ofNullable(amount)
- fun isNewCouponPercentage(): Boolean = newCouponPercentage != null
+ fun isPercentage(): Boolean = percentage != null
- fun isNewCouponAmount(): Boolean = newCouponAmount != null
+ fun isAmount(): Boolean = amount != null
- fun asNewCouponPercentage(): NewCouponPercentageDiscount =
- newCouponPercentage.getOrThrow("newCouponPercentage")
+ fun asPercentage(): Percentage = percentage.getOrThrow("percentage")
- fun asNewCouponAmount(): NewCouponAmountDiscount =
- newCouponAmount.getOrThrow("newCouponAmount")
+ fun asAmount(): Amount = amount.getOrThrow("amount")
fun _json(): Optional = Optional.ofNullable(_json)
fun accept(visitor: Visitor): T =
when {
- newCouponPercentage != null -> visitor.visitNewCouponPercentage(newCouponPercentage)
- newCouponAmount != null -> visitor.visitNewCouponAmount(newCouponAmount)
+ percentage != null -> visitor.visitPercentage(percentage)
+ amount != null -> visitor.visitAmount(amount)
else -> visitor.unknown(_json)
}
@@ -829,14 +795,12 @@ private constructor(
accept(
object : Visitor {
- override fun visitNewCouponPercentage(
- newCouponPercentage: NewCouponPercentageDiscount
- ) {
- newCouponPercentage.validate()
+ override fun visitPercentage(percentage: Percentage) {
+ percentage.validate()
}
- override fun visitNewCouponAmount(newCouponAmount: NewCouponAmountDiscount) {
- newCouponAmount.validate()
+ override fun visitAmount(amount: Amount) {
+ amount.validate()
}
}
)
@@ -861,12 +825,9 @@ private constructor(
internal fun validity(): Int =
accept(
object : Visitor {
- override fun visitNewCouponPercentage(
- newCouponPercentage: NewCouponPercentageDiscount
- ) = newCouponPercentage.validity()
+ override fun visitPercentage(percentage: Percentage) = percentage.validity()
- override fun visitNewCouponAmount(newCouponAmount: NewCouponAmountDiscount) =
- newCouponAmount.validity()
+ override fun visitAmount(amount: Amount) = amount.validity()
override fun unknown(json: JsonValue?) = 0
}
@@ -877,28 +838,24 @@ private constructor(
return true
}
- return /* spotless:off */ other is Discount && newCouponPercentage == other.newCouponPercentage && newCouponAmount == other.newCouponAmount /* spotless:on */
+ return /* spotless:off */ other is Discount && percentage == other.percentage && amount == other.amount /* spotless:on */
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(newCouponPercentage, newCouponAmount) /* spotless:on */
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(percentage, amount) /* spotless:on */
override fun toString(): String =
when {
- newCouponPercentage != null -> "Discount{newCouponPercentage=$newCouponPercentage}"
- newCouponAmount != null -> "Discount{newCouponAmount=$newCouponAmount}"
+ percentage != null -> "Discount{percentage=$percentage}"
+ amount != null -> "Discount{amount=$amount}"
_json != null -> "Discount{_unknown=$_json}"
else -> throw IllegalStateException("Invalid Discount")
}
companion object {
- @JvmStatic
- fun ofNewCouponPercentage(newCouponPercentage: NewCouponPercentageDiscount) =
- Discount(newCouponPercentage = newCouponPercentage)
+ @JvmStatic fun ofPercentage(percentage: Percentage) = Discount(percentage = percentage)
- @JvmStatic
- fun ofNewCouponAmount(newCouponAmount: NewCouponAmountDiscount) =
- Discount(newCouponAmount = newCouponAmount)
+ @JvmStatic fun ofAmount(amount: Amount) = Discount(amount = amount)
}
/**
@@ -906,9 +863,9 @@ private constructor(
*/
interface Visitor {
- fun visitNewCouponPercentage(newCouponPercentage: NewCouponPercentageDiscount): T
+ fun visitPercentage(percentage: Percentage): T
- fun visitNewCouponAmount(newCouponAmount: NewCouponAmountDiscount): T
+ fun visitAmount(amount: Amount): T
/**
* Maps an unknown variant of [Discount] to a value of type [T].
@@ -934,14 +891,14 @@ private constructor(
when (discountType) {
"percentage" -> {
- return tryDeserialize(node, jacksonTypeRef())
- ?.let { Discount(newCouponPercentage = it, _json = json) }
- ?: Discount(_json = json)
+ return tryDeserialize(node, jacksonTypeRef())?.let {
+ Discount(percentage = it, _json = json)
+ } ?: Discount(_json = json)
}
"amount" -> {
- return tryDeserialize(node, jacksonTypeRef())
- ?.let { Discount(newCouponAmount = it, _json = json) }
- ?: Discount(_json = json)
+ return tryDeserialize(node, jacksonTypeRef())?.let {
+ Discount(amount = it, _json = json)
+ } ?: Discount(_json = json)
}
}
@@ -957,18 +914,17 @@ private constructor(
provider: SerializerProvider,
) {
when {
- value.newCouponPercentage != null ->
- generator.writeObject(value.newCouponPercentage)
- value.newCouponAmount != null -> generator.writeObject(value.newCouponAmount)
+ value.percentage != null -> generator.writeObject(value.percentage)
+ value.amount != null -> generator.writeObject(value.amount)
value._json != null -> generator.writeObject(value._json)
else -> throw IllegalStateException("Invalid Discount")
}
}
}
- class NewCouponPercentageDiscount
+ class Percentage
private constructor(
- private val discountType: JsonField,
+ private val discountType: JsonValue,
private val percentageDiscount: JsonField,
private val additionalProperties: MutableMap,
) {
@@ -977,18 +933,24 @@ private constructor(
private constructor(
@JsonProperty("discount_type")
@ExcludeMissing
- discountType: JsonField = JsonMissing.of(),
+ discountType: JsonValue = JsonMissing.of(),
@JsonProperty("percentage_discount")
@ExcludeMissing
percentageDiscount: JsonField = JsonMissing.of(),
) : this(discountType, percentageDiscount, mutableMapOf())
/**
- * @throws OrbInvalidDataException if the JSON field has an unexpected type or is
- * unexpectedly missing or null (e.g. if the server responded with an unexpected
- * value).
+ * Expected to always return the following:
+ * ```java
+ * JsonValue.from("percentage")
+ * ```
+ *
+ * However, this method can be useful for debugging and logging (e.g. if the server
+ * responded with an unexpected value).
*/
- fun discountType(): DiscountType = discountType.getRequired("discount_type")
+ @JsonProperty("discount_type")
+ @ExcludeMissing
+ fun _discountType(): JsonValue = discountType
/**
* @throws OrbInvalidDataException if the JSON field has an unexpected type or is
@@ -997,16 +959,6 @@ private constructor(
*/
fun percentageDiscount(): Double = percentageDiscount.getRequired("percentage_discount")
- /**
- * Returns the raw JSON value of [discountType].
- *
- * Unlike [discountType], this method doesn't throw if the JSON field has an unexpected
- * type.
- */
- @JsonProperty("discount_type")
- @ExcludeMissing
- fun _discountType(): JsonField = discountType
-
/**
* Returns the raw JSON value of [percentageDiscount].
*
@@ -1032,45 +984,43 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of
- * [NewCouponPercentageDiscount].
+ * Returns a mutable builder for constructing an instance of [Percentage].
*
* The following fields are required:
* ```java
- * .discountType()
* .percentageDiscount()
* ```
*/
@JvmStatic fun builder() = Builder()
}
- /** A builder for [NewCouponPercentageDiscount]. */
+ /** A builder for [Percentage]. */
class Builder internal constructor() {
- private var discountType: JsonField? = null
+ private var discountType: JsonValue = JsonValue.from("percentage")
private var percentageDiscount: JsonField? = null
private var additionalProperties: MutableMap = mutableMapOf()
@JvmSynthetic
- internal fun from(newCouponPercentageDiscount: NewCouponPercentageDiscount) =
- apply {
- discountType = newCouponPercentageDiscount.discountType
- percentageDiscount = newCouponPercentageDiscount.percentageDiscount
- additionalProperties =
- newCouponPercentageDiscount.additionalProperties.toMutableMap()
- }
-
- fun discountType(discountType: DiscountType) =
- discountType(JsonField.of(discountType))
+ internal fun from(percentage: Percentage) = apply {
+ discountType = percentage.discountType
+ percentageDiscount = percentage.percentageDiscount
+ additionalProperties = percentage.additionalProperties.toMutableMap()
+ }
/**
- * Sets [Builder.discountType] to an arbitrary JSON value.
+ * Sets the field to an arbitrary JSON value.
*
- * You should usually call [Builder.discountType] with a well-typed [DiscountType]
- * value instead. This method is primarily for setting the field to an undocumented
- * or not yet supported value.
+ * It is usually unnecessary to call this method because the field defaults to the
+ * following:
+ * ```java
+ * JsonValue.from("percentage")
+ * ```
+ *
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
*/
- fun discountType(discountType: JsonField) = apply {
+ fun discountType(discountType: JsonValue) = apply {
this.discountType = discountType
}
@@ -1111,21 +1061,20 @@ private constructor(
}
/**
- * Returns an immutable instance of [NewCouponPercentageDiscount].
+ * Returns an immutable instance of [Percentage].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
* The following fields are required:
* ```java
- * .discountType()
* .percentageDiscount()
* ```
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): NewCouponPercentageDiscount =
- NewCouponPercentageDiscount(
- checkRequired("discountType", discountType),
+ fun build(): Percentage =
+ Percentage(
+ discountType,
checkRequired("percentageDiscount", percentageDiscount),
additionalProperties.toMutableMap(),
)
@@ -1133,12 +1082,16 @@ private constructor(
private var validated: Boolean = false
- fun validate(): NewCouponPercentageDiscount = apply {
+ fun validate(): Percentage = apply {
if (validated) {
return@apply
}
- discountType().validate()
+ _discountType().let {
+ if (it != JsonValue.from("percentage")) {
+ throw OrbInvalidDataException("'discountType' is invalid, received $it")
+ }
+ }
percentageDiscount()
validated = true
}
@@ -1159,142 +1112,15 @@ private constructor(
*/
@JvmSynthetic
internal fun validity(): Int =
- (discountType.asKnown().getOrNull()?.validity() ?: 0) +
+ discountType.let { if (it == JsonValue.from("percentage")) 1 else 0 } +
(if (percentageDiscount.asKnown().isPresent) 1 else 0)
- class DiscountType
- @JsonCreator
- private constructor(private val value: JsonField) : Enum {
-
- /**
- * Returns this class instance's raw value.
- *
- * This is usually only useful if this instance was deserialized from data that
- * doesn't match any known member, and you want to know that value. For example, if
- * the SDK is on an older version than the API, then the API may respond with new
- * members that the SDK is unaware of.
- */
- @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
-
- companion object {
-
- @JvmField val PERCENTAGE = of("percentage")
-
- @JvmStatic fun of(value: String) = DiscountType(JsonField.of(value))
- }
-
- /** An enum containing [DiscountType]'s known values. */
- enum class Known {
- PERCENTAGE
- }
-
- /**
- * An enum containing [DiscountType]'s known values, as well as an [_UNKNOWN]
- * member.
- *
- * An instance of [DiscountType] can contain an unknown value in a couple of cases:
- * - It was deserialized from data that doesn't match any known member. For example,
- * if the SDK is on an older version than the API, then the API may respond with
- * new members that the SDK is unaware of.
- * - It was constructed with an arbitrary value using the [of] method.
- */
- enum class Value {
- PERCENTAGE,
- /**
- * An enum member indicating that [DiscountType] was instantiated with an
- * unknown value.
- */
- _UNKNOWN,
- }
-
- /**
- * Returns an enum member corresponding to this class instance's value, or
- * [Value._UNKNOWN] if the class was instantiated with an unknown value.
- *
- * Use the [known] method instead if you're certain the value is always known or if
- * you want to throw for the unknown case.
- */
- fun value(): Value =
- when (this) {
- PERCENTAGE -> Value.PERCENTAGE
- else -> Value._UNKNOWN
- }
-
- /**
- * Returns an enum member corresponding to this class instance's value.
- *
- * Use the [value] method instead if you're uncertain the value is always known and
- * don't want to throw for the unknown case.
- *
- * @throws OrbInvalidDataException if this class instance's value is a not a known
- * member.
- */
- fun known(): Known =
- when (this) {
- PERCENTAGE -> Known.PERCENTAGE
- else -> throw OrbInvalidDataException("Unknown DiscountType: $value")
- }
-
- /**
- * Returns this class instance's primitive wire representation.
- *
- * This differs from the [toString] method because that method is primarily for
- * debugging and generally doesn't throw.
- *
- * @throws OrbInvalidDataException if this class instance's value does not have the
- * expected primitive type.
- */
- fun asString(): String =
- _value().asString().orElseThrow {
- OrbInvalidDataException("Value is not a String")
- }
-
- private var validated: Boolean = false
-
- fun validate(): DiscountType = apply {
- if (validated) {
- return@apply
- }
-
- known()
- validated = true
- }
-
- fun isValid(): Boolean =
- try {
- validate()
- true
- } catch (e: OrbInvalidDataException) {
- false
- }
-
- /**
- * Returns a score indicating how many valid values are contained in this object
- * recursively.
- *
- * Used for best match union deserialization.
- */
- @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
-
- override fun equals(other: Any?): Boolean {
- if (this === other) {
- return true
- }
-
- return /* spotless:off */ other is DiscountType && value == other.value /* spotless:on */
- }
-
- override fun hashCode() = value.hashCode()
-
- override fun toString() = value.toString()
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is NewCouponPercentageDiscount && discountType == other.discountType && percentageDiscount == other.percentageDiscount && additionalProperties == other.additionalProperties /* spotless:on */
+ return /* spotless:off */ other is Percentage && discountType == other.discountType && percentageDiscount == other.percentageDiscount && additionalProperties == other.additionalProperties /* spotless:on */
}
/* spotless:off */
@@ -1304,13 +1130,13 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "NewCouponPercentageDiscount{discountType=$discountType, percentageDiscount=$percentageDiscount, additionalProperties=$additionalProperties}"
+ "Percentage{discountType=$discountType, percentageDiscount=$percentageDiscount, additionalProperties=$additionalProperties}"
}
- class NewCouponAmountDiscount
+ class Amount
private constructor(
private val amountDiscount: JsonField,
- private val discountType: JsonField,
+ private val discountType: JsonValue,
private val additionalProperties: MutableMap,
) {
@@ -1321,7 +1147,7 @@ private constructor(
amountDiscount: JsonField = JsonMissing.of(),
@JsonProperty("discount_type")
@ExcludeMissing
- discountType: JsonField = JsonMissing.of(),
+ discountType: JsonValue = JsonMissing.of(),
) : this(amountDiscount, discountType, mutableMapOf())
/**
@@ -1332,11 +1158,17 @@ private constructor(
fun amountDiscount(): String = amountDiscount.getRequired("amount_discount")
/**
- * @throws OrbInvalidDataException if the JSON field has an unexpected type or is
- * unexpectedly missing or null (e.g. if the server responded with an unexpected
- * value).
+ * Expected to always return the following:
+ * ```java
+ * JsonValue.from("amount")
+ * ```
+ *
+ * However, this method can be useful for debugging and logging (e.g. if the server
+ * responded with an unexpected value).
*/
- fun discountType(): DiscountType = discountType.getRequired("discount_type")
+ @JsonProperty("discount_type")
+ @ExcludeMissing
+ fun _discountType(): JsonValue = discountType
/**
* Returns the raw JSON value of [amountDiscount].
@@ -1348,16 +1180,6 @@ private constructor(
@ExcludeMissing
fun _amountDiscount(): JsonField = amountDiscount
- /**
- * Returns the raw JSON value of [discountType].
- *
- * Unlike [discountType], this method doesn't throw if the JSON field has an unexpected
- * type.
- */
- @JsonProperty("discount_type")
- @ExcludeMissing
- fun _discountType(): JsonField = discountType
-
@JsonAnySetter
private fun putAdditionalProperty(key: String, value: JsonValue) {
additionalProperties.put(key, value)
@@ -1373,31 +1195,28 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of
- * [NewCouponAmountDiscount].
+ * Returns a mutable builder for constructing an instance of [Amount].
*
* The following fields are required:
* ```java
* .amountDiscount()
- * .discountType()
* ```
*/
@JvmStatic fun builder() = Builder()
}
- /** A builder for [NewCouponAmountDiscount]. */
+ /** A builder for [Amount]. */
class Builder internal constructor() {
private var amountDiscount: JsonField? = null
- private var discountType: JsonField? = null
+ private var discountType: JsonValue = JsonValue.from("amount")
private var additionalProperties: MutableMap = mutableMapOf()
@JvmSynthetic
- internal fun from(newCouponAmountDiscount: NewCouponAmountDiscount) = apply {
- amountDiscount = newCouponAmountDiscount.amountDiscount
- discountType = newCouponAmountDiscount.discountType
- additionalProperties =
- newCouponAmountDiscount.additionalProperties.toMutableMap()
+ internal fun from(amount: Amount) = apply {
+ amountDiscount = amount.amountDiscount
+ discountType = amount.discountType
+ additionalProperties = amount.additionalProperties.toMutableMap()
}
fun amountDiscount(amountDiscount: String) =
@@ -1414,17 +1233,19 @@ private constructor(
this.amountDiscount = amountDiscount
}
- fun discountType(discountType: DiscountType) =
- discountType(JsonField.of(discountType))
-
/**
- * Sets [Builder.discountType] to an arbitrary JSON value.
+ * Sets the field to an arbitrary JSON value.
*
- * You should usually call [Builder.discountType] with a well-typed [DiscountType]
- * value instead. This method is primarily for setting the field to an undocumented
- * or not yet supported value.
+ * It is usually unnecessary to call this method because the field defaults to the
+ * following:
+ * ```java
+ * JsonValue.from("amount")
+ * ```
+ *
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
*/
- fun discountType(discountType: JsonField) = apply {
+ fun discountType(discountType: JsonValue) = apply {
this.discountType = discountType
}
@@ -1451,35 +1272,38 @@ private constructor(
}
/**
- * Returns an immutable instance of [NewCouponAmountDiscount].
+ * Returns an immutable instance of [Amount].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
* The following fields are required:
* ```java
* .amountDiscount()
- * .discountType()
* ```
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): NewCouponAmountDiscount =
- NewCouponAmountDiscount(
+ fun build(): Amount =
+ Amount(
checkRequired("amountDiscount", amountDiscount),
- checkRequired("discountType", discountType),
+ discountType,
additionalProperties.toMutableMap(),
)
}
private var validated: Boolean = false
- fun validate(): NewCouponAmountDiscount = apply {
+ fun validate(): Amount = apply {
if (validated) {
return@apply
}
amountDiscount()
- discountType().validate()
+ _discountType().let {
+ if (it != JsonValue.from("amount")) {
+ throw OrbInvalidDataException("'discountType' is invalid, received $it")
+ }
+ }
validated = true
}
@@ -1500,141 +1324,14 @@ private constructor(
@JvmSynthetic
internal fun validity(): Int =
(if (amountDiscount.asKnown().isPresent) 1 else 0) +
- (discountType.asKnown().getOrNull()?.validity() ?: 0)
-
- class DiscountType
- @JsonCreator
- private constructor(private val value: JsonField) : Enum {
-
- /**
- * Returns this class instance's raw value.
- *
- * This is usually only useful if this instance was deserialized from data that
- * doesn't match any known member, and you want to know that value. For example, if
- * the SDK is on an older version than the API, then the API may respond with new
- * members that the SDK is unaware of.
- */
- @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
-
- companion object {
-
- @JvmField val AMOUNT = of("amount")
-
- @JvmStatic fun of(value: String) = DiscountType(JsonField.of(value))
- }
-
- /** An enum containing [DiscountType]'s known values. */
- enum class Known {
- AMOUNT
- }
-
- /**
- * An enum containing [DiscountType]'s known values, as well as an [_UNKNOWN]
- * member.
- *
- * An instance of [DiscountType] can contain an unknown value in a couple of cases:
- * - It was deserialized from data that doesn't match any known member. For example,
- * if the SDK is on an older version than the API, then the API may respond with
- * new members that the SDK is unaware of.
- * - It was constructed with an arbitrary value using the [of] method.
- */
- enum class Value {
- AMOUNT,
- /**
- * An enum member indicating that [DiscountType] was instantiated with an
- * unknown value.
- */
- _UNKNOWN,
- }
-
- /**
- * Returns an enum member corresponding to this class instance's value, or
- * [Value._UNKNOWN] if the class was instantiated with an unknown value.
- *
- * Use the [known] method instead if you're certain the value is always known or if
- * you want to throw for the unknown case.
- */
- fun value(): Value =
- when (this) {
- AMOUNT -> Value.AMOUNT
- else -> Value._UNKNOWN
- }
-
- /**
- * Returns an enum member corresponding to this class instance's value.
- *
- * Use the [value] method instead if you're uncertain the value is always known and
- * don't want to throw for the unknown case.
- *
- * @throws OrbInvalidDataException if this class instance's value is a not a known
- * member.
- */
- fun known(): Known =
- when (this) {
- AMOUNT -> Known.AMOUNT
- else -> throw OrbInvalidDataException("Unknown DiscountType: $value")
- }
-
- /**
- * Returns this class instance's primitive wire representation.
- *
- * This differs from the [toString] method because that method is primarily for
- * debugging and generally doesn't throw.
- *
- * @throws OrbInvalidDataException if this class instance's value does not have the
- * expected primitive type.
- */
- fun asString(): String =
- _value().asString().orElseThrow {
- OrbInvalidDataException("Value is not a String")
- }
-
- private var validated: Boolean = false
-
- fun validate(): DiscountType = apply {
- if (validated) {
- return@apply
- }
-
- known()
- validated = true
- }
-
- fun isValid(): Boolean =
- try {
- validate()
- true
- } catch (e: OrbInvalidDataException) {
- false
- }
-
- /**
- * Returns a score indicating how many valid values are contained in this object
- * recursively.
- *
- * Used for best match union deserialization.
- */
- @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
-
- override fun equals(other: Any?): Boolean {
- if (this === other) {
- return true
- }
-
- return /* spotless:off */ other is DiscountType && value == other.value /* spotless:on */
- }
-
- override fun hashCode() = value.hashCode()
-
- override fun toString() = value.toString()
- }
+ discountType.let { if (it == JsonValue.from("amount")) 1 else 0 }
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is NewCouponAmountDiscount && amountDiscount == other.amountDiscount && discountType == other.discountType && additionalProperties == other.additionalProperties /* spotless:on */
+ return /* spotless:off */ other is Amount && amountDiscount == other.amountDiscount && discountType == other.discountType && additionalProperties == other.additionalProperties /* spotless:on */
}
/* spotless:off */
@@ -1644,7 +1341,7 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "NewCouponAmountDiscount{amountDiscount=$amountDiscount, discountType=$discountType, additionalProperties=$additionalProperties}"
+ "Amount{amountDiscount=$amountDiscount, discountType=$discountType, additionalProperties=$additionalProperties}"
}
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponFetchParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponFetchParams.kt
index afb3aebc..9222e419 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponFetchParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponFetchParams.kt
@@ -3,10 +3,11 @@
package com.withorb.api.models
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/**
* This endpoint retrieves a coupon by its ID. To fetch coupons by their redemption code, use the
@@ -14,12 +15,12 @@ import java.util.Objects
*/
class CouponFetchParams
private constructor(
- private val couponId: String,
+ private val couponId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun couponId(): String = couponId
+ fun couponId(): Optional = Optional.ofNullable(couponId)
fun _additionalHeaders(): Headers = additionalHeaders
@@ -29,14 +30,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [CouponFetchParams].
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
- */
+ @JvmStatic fun none(): CouponFetchParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [CouponFetchParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -54,7 +50,10 @@ private constructor(
additionalQueryParams = couponFetchParams.additionalQueryParams.toBuilder()
}
- fun couponId(couponId: String) = apply { this.couponId = couponId }
+ fun couponId(couponId: String?) = apply { this.couponId = couponId }
+
+ /** Alias for calling [Builder.couponId] with `couponId.orElse(null)`. */
+ fun couponId(couponId: Optional) = couponId(couponId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -158,25 +157,14 @@ private constructor(
* Returns an immutable instance of [CouponFetchParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): CouponFetchParams =
- CouponFetchParams(
- checkRequired("couponId", couponId),
- additionalHeaders.build(),
- additionalQueryParams.build(),
- )
+ CouponFetchParams(couponId, additionalHeaders.build(), additionalQueryParams.build())
}
fun _pathParam(index: Int): String =
when (index) {
- 0 -> couponId
+ 0 -> couponId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPage.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPage.kt
index 8fd79bed..54fa128c 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPage.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPage.kt
@@ -2,12 +2,12 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPager
+import com.withorb.api.core.Page
import com.withorb.api.core.checkRequired
import com.withorb.api.services.blocking.CouponService
import java.util.Objects
import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
/** @see [CouponService.list] */
@@ -16,7 +16,7 @@ private constructor(
private val service: CouponService,
private val params: CouponListParams,
private val response: CouponListPageResponse,
-) {
+) : Page {
/**
* Delegates to [CouponListPageResponse], but gracefully handles missing data.
@@ -33,30 +33,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CouponListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): Optional = getNextPageParams().map { service.list(it) }
+ override fun nextPage(): CouponListPage = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPager = AutoPager.from(this)
/** The parameters that were used to request this page. */
fun params(): CouponListParams = params
@@ -125,25 +117,6 @@ private constructor(
)
}
- class AutoPager(private val firstPage: CouponListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.data().size) {
- yield(page.data()[index++])
- }
- page = page.getNextPage().getOrNull() ?: break
- index = 0
- }
- }
-
- fun stream(): Stream {
- return StreamSupport.stream(spliterator(), false)
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPageAsync.kt
index 6486b6da..884d7b62 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPageAsync.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponListPageAsync.kt
@@ -2,22 +2,24 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPagerAsync
+import com.withorb.api.core.PageAsync
import com.withorb.api.core.checkRequired
import com.withorb.api.services.async.CouponServiceAsync
import java.util.Objects
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
-import java.util.function.Predicate
import kotlin.jvm.optionals.getOrNull
/** @see [CouponServiceAsync.list] */
class CouponListPageAsync
private constructor(
private val service: CouponServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: CouponListParams,
private val response: CouponListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [CouponListPageResponse], but gracefully handles missing data.
@@ -34,33 +36,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CouponListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): CompletableFuture> =
- getNextPageParams()
- .map { service.list(it).thenApply { Optional.of(it) } }
- .orElseGet { CompletableFuture.completedFuture(Optional.empty()) }
+ override fun nextPage(): CompletableFuture = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPagerAsync = AutoPagerAsync.from(this, streamHandlerExecutor)
/** The parameters that were used to request this page. */
fun params(): CouponListParams = params
@@ -78,6 +69,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -89,18 +81,24 @@ private constructor(
class Builder internal constructor() {
private var service: CouponServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: CouponListParams? = null
private var response: CouponListPageResponse? = null
@JvmSynthetic
internal fun from(couponListPageAsync: CouponListPageAsync) = apply {
service = couponListPageAsync.service
+ streamHandlerExecutor = couponListPageAsync.streamHandlerExecutor
params = couponListPageAsync.params
response = couponListPageAsync.response
}
fun service(service: CouponServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: CouponListParams) = apply { this.params = params }
@@ -115,6 +113,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -124,47 +123,22 @@ private constructor(
fun build(): CouponListPageAsync =
CouponListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: CouponListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (Coupon) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.data().all(action) }
- .map { it.getNextPage().forEach(action, executor) }
- .orElseGet { CompletableFuture.completedFuture(null) }
- },
- executor,
- )
- return CompletableFuture.completedFuture(Optional.of(firstPage))
- .forEach(action::test, executor)
- }
-
- fun toList(executor: Executor): CompletableFuture> {
- val values = mutableListOf()
- return forEach(values::add, executor).thenApply { values }
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is CouponListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return /* spotless:off */ other is CouponListPageAsync && service == other.service && streamHandlerExecutor == other.streamHandlerExecutor && params == other.params && response == other.response /* spotless:on */
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, streamHandlerExecutor, params, response) /* spotless:on */
override fun toString() =
- "CouponListPageAsync{service=$service, params=$params, response=$response}"
+ "CouponListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPage.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPage.kt
index 77f17471..d81a7bb7 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPage.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPage.kt
@@ -2,12 +2,12 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPager
+import com.withorb.api.core.Page
import com.withorb.api.core.checkRequired
import com.withorb.api.services.blocking.coupons.SubscriptionService
import java.util.Objects
import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
/** @see [SubscriptionService.list] */
@@ -16,7 +16,7 @@ private constructor(
private val service: SubscriptionService,
private val params: CouponSubscriptionListParams,
private val response: Subscriptions,
-) {
+) : Page {
/**
* Delegates to [Subscriptions], but gracefully handles missing data.
@@ -33,31 +33,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CouponSubscriptionListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): Optional =
- getNextPageParams().map { service.list(it) }
+ override fun nextPage(): CouponSubscriptionListPage = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPager = AutoPager.from(this)
/** The parameters that were used to request this page. */
fun params(): CouponSubscriptionListParams = params
@@ -126,25 +117,6 @@ private constructor(
)
}
- class AutoPager(private val firstPage: CouponSubscriptionListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.data().size) {
- yield(page.data()[index++])
- }
- page = page.getNextPage().getOrNull() ?: break
- index = 0
- }
- }
-
- fun stream(): Stream {
- return StreamSupport.stream(spliterator(), false)
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPageAsync.kt
index 47417e21..afc24358 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPageAsync.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListPageAsync.kt
@@ -2,22 +2,24 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPagerAsync
+import com.withorb.api.core.PageAsync
import com.withorb.api.core.checkRequired
import com.withorb.api.services.async.coupons.SubscriptionServiceAsync
import java.util.Objects
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
-import java.util.function.Predicate
import kotlin.jvm.optionals.getOrNull
/** @see [SubscriptionServiceAsync.list] */
class CouponSubscriptionListPageAsync
private constructor(
private val service: SubscriptionServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: CouponSubscriptionListParams,
private val response: Subscriptions,
-) {
+) : PageAsync {
/**
* Delegates to [Subscriptions], but gracefully handles missing data.
@@ -34,33 +36,23 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CouponSubscriptionListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): CompletableFuture> =
- getNextPageParams()
- .map { service.list(it).thenApply { Optional.of(it) } }
- .orElseGet { CompletableFuture.completedFuture(Optional.empty()) }
+ override fun nextPage(): CompletableFuture =
+ service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPagerAsync = AutoPagerAsync.from(this, streamHandlerExecutor)
/** The parameters that were used to request this page. */
fun params(): CouponSubscriptionListParams = params
@@ -79,6 +71,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -90,6 +83,7 @@ private constructor(
class Builder internal constructor() {
private var service: SubscriptionServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: CouponSubscriptionListParams? = null
private var response: Subscriptions? = null
@@ -97,12 +91,17 @@ private constructor(
internal fun from(couponSubscriptionListPageAsync: CouponSubscriptionListPageAsync) =
apply {
service = couponSubscriptionListPageAsync.service
+ streamHandlerExecutor = couponSubscriptionListPageAsync.streamHandlerExecutor
params = couponSubscriptionListPageAsync.params
response = couponSubscriptionListPageAsync.response
}
fun service(service: SubscriptionServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: CouponSubscriptionListParams) = apply { this.params = params }
@@ -117,6 +116,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -126,47 +126,22 @@ private constructor(
fun build(): CouponSubscriptionListPageAsync =
CouponSubscriptionListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: CouponSubscriptionListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (Subscription) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.data().all(action) }
- .map { it.getNextPage().forEach(action, executor) }
- .orElseGet { CompletableFuture.completedFuture(null) }
- },
- executor,
- )
- return CompletableFuture.completedFuture(Optional.of(firstPage))
- .forEach(action::test, executor)
- }
-
- fun toList(executor: Executor): CompletableFuture> {
- val values = mutableListOf()
- return forEach(values::add, executor).thenApply { values }
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is CouponSubscriptionListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return /* spotless:off */ other is CouponSubscriptionListPageAsync && service == other.service && streamHandlerExecutor == other.streamHandlerExecutor && params == other.params && response == other.response /* spotless:on */
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, streamHandlerExecutor, params, response) /* spotless:on */
override fun toString() =
- "CouponSubscriptionListPageAsync{service=$service, params=$params, response=$response}"
+ "CouponSubscriptionListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListParams.kt
index a3b3be8d..6ed44a4f 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CouponSubscriptionListParams.kt
@@ -3,7 +3,6 @@
package com.withorb.api.models
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import java.util.Objects
@@ -18,14 +17,14 @@ import kotlin.jvm.optionals.getOrNull
*/
class CouponSubscriptionListParams
private constructor(
- private val couponId: String,
+ private val couponId: String?,
private val cursor: String?,
private val limit: Long?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun couponId(): String = couponId
+ fun couponId(): Optional = Optional.ofNullable(couponId)
/**
* Cursor for pagination. This can be populated by the `next_cursor` value returned from the
@@ -44,13 +43,10 @@ private constructor(
companion object {
+ @JvmStatic fun none(): CouponSubscriptionListParams = builder().build()
+
/**
* Returns a mutable builder for constructing an instance of [CouponSubscriptionListParams].
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
*/
@JvmStatic fun builder() = Builder()
}
@@ -73,7 +69,10 @@ private constructor(
additionalQueryParams = couponSubscriptionListParams.additionalQueryParams.toBuilder()
}
- fun couponId(couponId: String) = apply { this.couponId = couponId }
+ fun couponId(couponId: String?) = apply { this.couponId = couponId }
+
+ /** Alias for calling [Builder.couponId] with `couponId.orElse(null)`. */
+ fun couponId(couponId: Optional) = couponId(couponId.getOrNull())
/**
* Cursor for pagination. This can be populated by the `next_cursor` value returned from the
@@ -199,17 +198,10 @@ private constructor(
* Returns an immutable instance of [CouponSubscriptionListParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .couponId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): CouponSubscriptionListParams =
CouponSubscriptionListParams(
- checkRequired("couponId", couponId),
+ couponId,
cursor,
limit,
additionalHeaders.build(),
@@ -219,7 +211,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> couponId
+ 0 -> couponId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteFetchParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteFetchParams.kt
index 04c0d080..827792b4 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteFetchParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteFetchParams.kt
@@ -3,10 +3,11 @@
package com.withorb.api.models
import com.withorb.api.core.Params
-import com.withorb.api.core.checkRequired
import com.withorb.api.core.http.Headers
import com.withorb.api.core.http.QueryParams
import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/**
* This endpoint is used to fetch a single [`Credit Note`](/invoicing/credit-notes) given an
@@ -14,12 +15,12 @@ import java.util.Objects
*/
class CreditNoteFetchParams
private constructor(
- private val creditNoteId: String,
+ private val creditNoteId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun creditNoteId(): String = creditNoteId
+ fun creditNoteId(): Optional = Optional.ofNullable(creditNoteId)
fun _additionalHeaders(): Headers = additionalHeaders
@@ -29,14 +30,9 @@ private constructor(
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [CreditNoteFetchParams].
- *
- * The following fields are required:
- * ```java
- * .creditNoteId()
- * ```
- */
+ @JvmStatic fun none(): CreditNoteFetchParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [CreditNoteFetchParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -54,7 +50,10 @@ private constructor(
additionalQueryParams = creditNoteFetchParams.additionalQueryParams.toBuilder()
}
- fun creditNoteId(creditNoteId: String) = apply { this.creditNoteId = creditNoteId }
+ fun creditNoteId(creditNoteId: String?) = apply { this.creditNoteId = creditNoteId }
+
+ /** Alias for calling [Builder.creditNoteId] with `creditNoteId.orElse(null)`. */
+ fun creditNoteId(creditNoteId: Optional) = creditNoteId(creditNoteId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -158,17 +157,10 @@ private constructor(
* Returns an immutable instance of [CreditNoteFetchParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .creditNoteId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): CreditNoteFetchParams =
CreditNoteFetchParams(
- checkRequired("creditNoteId", creditNoteId),
+ creditNoteId,
additionalHeaders.build(),
additionalQueryParams.build(),
)
@@ -176,7 +168,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> creditNoteId
+ 0 -> creditNoteId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPage.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPage.kt
index cbbd4f40..a2337e3b 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPage.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPage.kt
@@ -2,12 +2,12 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPager
+import com.withorb.api.core.Page
import com.withorb.api.core.checkRequired
import com.withorb.api.services.blocking.CreditNoteService
import java.util.Objects
import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
/** @see [CreditNoteService.list] */
@@ -16,7 +16,7 @@ private constructor(
private val service: CreditNoteService,
private val params: CreditNoteListParams,
private val response: CreditNoteListPageResponse,
-) {
+) : Page {
/**
* Delegates to [CreditNoteListPageResponse], but gracefully handles missing data.
@@ -33,30 +33,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CreditNoteListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): Optional = getNextPageParams().map { service.list(it) }
+ override fun nextPage(): CreditNoteListPage = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPager = AutoPager.from(this)
/** The parameters that were used to request this page. */
fun params(): CreditNoteListParams = params
@@ -125,25 +117,6 @@ private constructor(
)
}
- class AutoPager(private val firstPage: CreditNoteListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.data().size) {
- yield(page.data()[index++])
- }
- page = page.getNextPage().getOrNull() ?: break
- index = 0
- }
- }
-
- fun stream(): Stream {
- return StreamSupport.stream(spliterator(), false)
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPageAsync.kt
index 104920cc..3710fe7e 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPageAsync.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CreditNoteListPageAsync.kt
@@ -2,22 +2,24 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPagerAsync
+import com.withorb.api.core.PageAsync
import com.withorb.api.core.checkRequired
import com.withorb.api.services.async.CreditNoteServiceAsync
import java.util.Objects
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
-import java.util.function.Predicate
import kotlin.jvm.optionals.getOrNull
/** @see [CreditNoteServiceAsync.list] */
class CreditNoteListPageAsync
private constructor(
private val service: CreditNoteServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: CreditNoteListParams,
private val response: CreditNoteListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [CreditNoteListPageResponse], but gracefully handles missing data.
@@ -34,33 +36,23 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CreditNoteListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): CompletableFuture> =
- getNextPageParams()
- .map { service.list(it).thenApply { Optional.of(it) } }
- .orElseGet { CompletableFuture.completedFuture(Optional.empty()) }
+ override fun nextPage(): CompletableFuture =
+ service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPagerAsync = AutoPagerAsync.from(this, streamHandlerExecutor)
/** The parameters that were used to request this page. */
fun params(): CreditNoteListParams = params
@@ -78,6 +70,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -89,18 +82,24 @@ private constructor(
class Builder internal constructor() {
private var service: CreditNoteServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: CreditNoteListParams? = null
private var response: CreditNoteListPageResponse? = null
@JvmSynthetic
internal fun from(creditNoteListPageAsync: CreditNoteListPageAsync) = apply {
service = creditNoteListPageAsync.service
+ streamHandlerExecutor = creditNoteListPageAsync.streamHandlerExecutor
params = creditNoteListPageAsync.params
response = creditNoteListPageAsync.response
}
fun service(service: CreditNoteServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: CreditNoteListParams) = apply { this.params = params }
@@ -115,6 +114,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -124,47 +124,22 @@ private constructor(
fun build(): CreditNoteListPageAsync =
CreditNoteListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: CreditNoteListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (CreditNote) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.data().all(action) }
- .map { it.getNextPage().forEach(action, executor) }
- .orElseGet { CompletableFuture.completedFuture(null) }
- },
- executor,
- )
- return CompletableFuture.completedFuture(Optional.of(firstPage))
- .forEach(action::test, executor)
- }
-
- fun toList(executor: Executor): CompletableFuture> {
- val values = mutableListOf()
- return forEach(values::add, executor).thenApply { values }
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return /* spotless:off */ other is CreditNoteListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return /* spotless:off */ other is CreditNoteListPageAsync && service == other.service && streamHandlerExecutor == other.streamHandlerExecutor && params == other.params && response == other.response /* spotless:on */
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, streamHandlerExecutor, params, response) /* spotless:on */
override fun toString() =
- "CreditNoteListPageAsync{service=$service, params=$params, response=$response}"
+ "CreditNoteListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionCreateParams.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionCreateParams.kt
index e2658926..31b73445 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionCreateParams.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionCreateParams.kt
@@ -27,13 +27,13 @@ import kotlin.jvm.optionals.getOrNull
*/
class CustomerBalanceTransactionCreateParams
private constructor(
- private val customerId: String,
+ private val customerId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun customerId(): String = customerId
+ fun customerId(): Optional = Optional.ofNullable(customerId)
/**
* @throws OrbInvalidDataException if the JSON field has an unexpected type or is unexpectedly
@@ -92,7 +92,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .customerId()
* .amount()
* .type()
* ```
@@ -119,7 +118,10 @@ private constructor(
customerBalanceTransactionCreateParams.additionalQueryParams.toBuilder()
}
- fun customerId(customerId: String) = apply { this.customerId = customerId }
+ fun customerId(customerId: String?) = apply { this.customerId = customerId }
+
+ /** Alias for calling [Builder.customerId] with `customerId.orElse(null)`. */
+ fun customerId(customerId: Optional) = customerId(customerId.getOrNull())
/**
* Sets the entire request body.
@@ -291,7 +293,6 @@ private constructor(
*
* The following fields are required:
* ```java
- * .customerId()
* .amount()
* .type()
* ```
@@ -300,7 +301,7 @@ private constructor(
*/
fun build(): CustomerBalanceTransactionCreateParams =
CustomerBalanceTransactionCreateParams(
- checkRequired("customerId", customerId),
+ customerId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -311,7 +312,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> customerId
+ 0 -> customerId ?: ""
else -> ""
}
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPage.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPage.kt
index a313acd6..73d8e3bc 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPage.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPage.kt
@@ -2,12 +2,12 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPager
+import com.withorb.api.core.Page
import com.withorb.api.core.checkRequired
import com.withorb.api.services.blocking.customers.BalanceTransactionService
import java.util.Objects
import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
/** @see [BalanceTransactionService.list] */
@@ -16,7 +16,7 @@ private constructor(
private val service: BalanceTransactionService,
private val params: CustomerBalanceTransactionListParams,
private val response: CustomerBalanceTransactionListPageResponse,
-) {
+) : Page {
/**
* Delegates to [CustomerBalanceTransactionListPageResponse], but gracefully handles missing
@@ -36,31 +36,22 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CustomerBalanceTransactionListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): Optional =
- getNextPageParams().map { service.list(it) }
+ override fun nextPage(): CustomerBalanceTransactionListPage = service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPager = AutoPager.from(this)
/** The parameters that were used to request this page. */
fun params(): CustomerBalanceTransactionListParams = params
@@ -133,26 +124,6 @@ private constructor(
)
}
- class AutoPager(private val firstPage: CustomerBalanceTransactionListPage) :
- Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.data().size) {
- yield(page.data()[index++])
- }
- page = page.getNextPage().getOrNull() ?: break
- index = 0
- }
- }
-
- fun stream(): Stream {
- return StreamSupport.stream(spliterator(), false)
- }
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
diff --git a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPageAsync.kt b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPageAsync.kt
index 6214987f..56c745f8 100644
--- a/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPageAsync.kt
+++ b/orb-java-core/src/main/kotlin/com/withorb/api/models/CustomerBalanceTransactionListPageAsync.kt
@@ -2,22 +2,24 @@
package com.withorb.api.models
+import com.withorb.api.core.AutoPagerAsync
+import com.withorb.api.core.PageAsync
import com.withorb.api.core.checkRequired
import com.withorb.api.services.async.customers.BalanceTransactionServiceAsync
import java.util.Objects
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
-import java.util.function.Predicate
import kotlin.jvm.optionals.getOrNull
/** @see [BalanceTransactionServiceAsync.list] */
class CustomerBalanceTransactionListPageAsync
private constructor(
private val service: BalanceTransactionServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: CustomerBalanceTransactionListParams,
private val response: CustomerBalanceTransactionListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [CustomerBalanceTransactionListPageResponse], but gracefully handles missing
@@ -37,33 +39,24 @@ private constructor(
fun paginationMetadata(): Optional =
response._paginationMetadata().getOptional("pagination_metadata")
- fun hasNextPage(): Boolean =
- data().isNotEmpty() &&
- paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
+ override fun items(): List = data()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean =
+ items().isNotEmpty() &&
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.isPresent
- return Optional.of(
- params
- .toBuilder()
- .apply {
- paginationMetadata()
- .flatMap { it._nextCursor().getOptional("next_cursor") }
- .ifPresent { cursor(it) }
- }
- .build()
- )
+ fun nextPageParams(): CustomerBalanceTransactionListParams {
+ val nextCursor =
+ paginationMetadata().flatMap { it._nextCursor().getOptional("next_cursor") }.getOrNull()
+ ?: throw IllegalStateException("Cannot construct next page params")
+ return params.toBuilder().cursor(nextCursor).build()
}
- fun getNextPage(): CompletableFuture> =
- getNextPageParams()
- .map { service.list(it).thenApply { Optional.of(it) } }
- .orElseGet { CompletableFuture.completedFuture(Optional.empty()) }
+ override fun nextPage(): CompletableFuture =
+ service.list(nextPageParams())
- fun autoPager(): AutoPager = AutoPager(this)
+ fun autoPager(): AutoPagerAsync