diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9051fdd2..42805473 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,24 @@
name: CI
on:
push:
- branches:
- - main
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
jobs:
lint:
- timeout-minutes: 10
+ timeout-minutes: 15
name: lint
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'stainless-sdks/braintrust-sdk-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+
steps:
- uses: actions/checkout@v4
@@ -22,7 +28,7 @@ jobs:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
@@ -30,10 +36,36 @@ jobs:
- name: Run lints
run: ./scripts/lint
+
+ build:
+ timeout-minutes: 15
+ name: build
+ runs-on: ${{ github.repository == 'stainless-sdks/braintrust-sdk-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: |
+ 8
+ 21
+ cache: gradle
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Build SDK
+ run: ./scripts/build
+
test:
- timeout-minutes: 10
+ timeout-minutes: 15
name: test
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'stainless-sdks/braintrust-sdk-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
@@ -43,7 +75,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 bab4aea6..d914ae66 100755
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -17,12 +17,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Java
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
diff --git a/.gitignore b/.gitignore
index 4e81838d..b1346e6d 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,6 @@
.gradle
.idea
.kotlin
-build
+build/
codegen.log
kls_database.db
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 6d78745c..091cfb12 100755
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.9.0"
+ ".": "0.10.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 91f6c416..2e43a77b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 110
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/manugoyal%2Fbraintrust-sdk-f0d64ce0e0efde75f9c171f7f3c3d4a72f00a77abb3bc5a7d65b7be1e715689b.yml
openapi_spec_hash: a027e48cc6aea2fab3cbdd38f4081119
-config_hash: 1ea10787f4def6c54a53f0e6f776f0a5
+config_hash: dca6e2cafd0764aa5fa3e78987e8b07c
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d47d1d9..3e8d2bf3 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,103 @@
# Changelog
+## 0.10.0 (2025-12-03)
+
+Full Changelog: [v0.9.0...v0.10.0](https://github.com/braintrustdata/braintrust-java/compare/v0.9.0...v0.10.0)
+
+### ⚠ BREAKING CHANGES
+
+* **client:** extract auto pagination to shared classes
+* **client:** **Migration:** - If you were referencing the `AutoPager` class on a specific `*Page` or `*PageAsync` type, then you should instead reference the shared `AutoPager` and `AutoPagerAsync` types, under the `core` package
+ - `AutoPagerAsync` now has different usage. You can call `.subscribe(...)` on the returned object instead to get called back each page item. You can also call `onCompleteFuture()` to get a future that completes when all items have been processed. Finally, you can call `.close()` on the returned object to stop auto-paginating early
+ - If you were referencing `getNextPage` or `getNextPageParams`:
+ - Swap to `nextPage()` and `nextPageParams()`
+ - Note that these both now return non-optional types (use `hasNextPage()` before calling these, since they will throw if it's impossible to get another page)
+
+### Features
+
+* add retryable exception ([ad8114f](https://github.com/braintrustdata/braintrust-java/commit/ad8114f1410bdf0b8de7d97803ee1567256beb11))
+* **client:** add `{QueryParams,Headers}#put(String, JsonValue)` methods ([0d82daf](https://github.com/braintrustdata/braintrust-java/commit/0d82dafca840d9bab25d431f90bf624161f657d5))
+* **client:** add a `withOptions` method ([1da1cfa](https://github.com/braintrustdata/braintrust-java/commit/1da1cfac2d57ac0c4619888357cad8bd8ad8b55a))
+* **client:** add https config options ([0ae6f5d](https://github.com/braintrustdata/braintrust-java/commit/0ae6f5d5c5d0236d95f801afe0f648021c9f4c27))
+* **client:** allow configuring env via system properties ([bbca12c](https://github.com/braintrustdata/braintrust-java/commit/bbca12cb7c4c9a3b4914c31c1e3767ee944c9f6b))
+* **client:** allow providing some params positionally ([5d14bd0](https://github.com/braintrustdata/braintrust-java/commit/5d14bd085513672de3d1c064e2db919b5f0bffa2))
+* **client:** ensure compat with proguard ([cf76b62](https://github.com/braintrustdata/braintrust-java/commit/cf76b62a5d08699560aada21ebbe26d809a2a87e))
+* **client:** expose sleeper option ([0cf2fec](https://github.com/braintrustdata/braintrust-java/commit/0cf2fec7e371018483fa266379d854fc0a461c05))
+* **client:** extract auto pagination to shared classes ([42ec597](https://github.com/braintrustdata/braintrust-java/commit/42ec597072a9392a0a8fadf99bdf228960a66e37))
+* **client:** implement per-endpoint base URL support ([4610a9c](https://github.com/braintrustdata/braintrust-java/commit/4610a9cfca70a2b0567c952044434739beb591cd))
+
+
+### Bug Fixes
+
+* **ci:** release-doctor — report correct token name ([755910d](https://github.com/braintrustdata/braintrust-java/commit/755910da108b3010293d0d84bfc0e2696796448a))
+* **ci:** use java-version 21 for publish step ([f69d0fe](https://github.com/braintrustdata/braintrust-java/commit/f69d0fed4cf2ae0119e0bcf84503782208de89a0))
+* **client:** accidental mutability of some classes ([641c2dc](https://github.com/braintrustdata/braintrust-java/commit/641c2dc41ddbc04b225e944033fedb5f2efa7d02))
+* **client:** bump max requests per host to max requests (5 -> 64) ([e817e93](https://github.com/braintrustdata/braintrust-java/commit/e817e93b721d4bdbc4fcbf50c5e6dd019e1d6d1d))
+* **client:** cancel okhttp call when future cancelled ([4e9004a](https://github.com/braintrustdata/braintrust-java/commit/4e9004a568ad7c4309f30995df9bb7514d12ded7))
+* **client:** deserialization of empty objects ([599e59c](https://github.com/braintrustdata/braintrust-java/commit/599e59c37526f17fb2813776f35bf10ed7d176f9))
+* **client:** don't close client on `withOptions` usage when original is gc'd ([76d0366](https://github.com/braintrustdata/braintrust-java/commit/76d0366cca717ebf63a425cd6674b933b31519c9))
+* **client:** ensure error handling always occurs ([7f3b3e0](https://github.com/braintrustdata/braintrust-java/commit/7f3b3e052acb0e5053e3d470b9a67e226252e488))
+* **client:** ensure single timer is created per client ([0cf2fec](https://github.com/braintrustdata/braintrust-java/commit/0cf2fec7e371018483fa266379d854fc0a461c05))
+* **client:** incorrect `getPackageVersion` impl ([48913a4](https://github.com/braintrustdata/braintrust-java/commit/48913a4f26fae541609b186470f5e4d21a5892b3))
+* **client:** multi-value header serialization ([84f72e2](https://github.com/braintrustdata/braintrust-java/commit/84f72e20dea2803431834f698337574545c1ab29))
+* **client:** r8 support ([4796a86](https://github.com/braintrustdata/braintrust-java/commit/4796a86bef99bc10c8db95c0f8eae469542dbdcb))
+* **client:** remove `@MustBeClosed` for future returning methods ([fd77b27](https://github.com/braintrustdata/braintrust-java/commit/fd77b2799b7c290451a1fdc2cb3bde9970c8ccc8))
+* fix casing issue ([89a22c7](https://github.com/braintrustdata/braintrust-java/commit/89a22c7ada67d638f3c3a95b9b5dc83a57d6436c))
+* update singularization rules ([ed3c3b2](https://github.com/braintrustdata/braintrust-java/commit/ed3c3b22c536f4fdc470eca6ed3129af7e4176c7))
+
+
+### Performance Improvements
+
+* **internal:** make formatting faster ([12585ed](https://github.com/braintrustdata/braintrust-java/commit/12585ed0aa09ac429ba618d7efb82d18c556bdc0))
+
+
+### Chores
+
+* **ci:** add build job ([72ca2fc](https://github.com/braintrustdata/braintrust-java/commit/72ca2fc6ecb8aeca739eb3c7e2c8364bf9b13431))
+* **ci:** bump `actions/setup-java` to v4 ([a5b0e85](https://github.com/braintrustdata/braintrust-java/commit/a5b0e85efaf25619a2f027bc6ba26fded780c78d))
+* **ci:** enable for pull requests ([40d994c](https://github.com/braintrustdata/braintrust-java/commit/40d994c4a4deb433bb789f085a77a3e98bab9357))
+* **ci:** ensure docs generation always succeeds ([067e7c8](https://github.com/braintrustdata/braintrust-java/commit/067e7c85b66f03fe03432bd8f56f5b9f752568d4))
+* **ci:** only run for pushes and fork pull requests ([ed7f8d9](https://github.com/braintrustdata/braintrust-java/commit/ed7f8d9b135466d13af8d2a504446d9116e87df4))
+* **ci:** only use depot for staging repos ([e4dbfbc](https://github.com/braintrustdata/braintrust-java/commit/e4dbfbc0c747acdef7625c3a9f5c8a90411ad8c5))
+* **ci:** reduce log noise ([57adf59](https://github.com/braintrustdata/braintrust-java/commit/57adf598e9d61a5d2358cedb6153fdeb1cc4d1ff))
+* **client:** refactor closing / shutdown ([d72b3c9](https://github.com/braintrustdata/braintrust-java/commit/d72b3c9e7b16775bfad37b6041b1fc2ba8fc4de7))
+* **docs:** grammar improvements ([bac039c](https://github.com/braintrustdata/braintrust-java/commit/bac039cbe2257071de867bd0111c9c1979dedc25))
+* **example:** fix run example comment ([3f434c4](https://github.com/braintrustdata/braintrust-java/commit/3f434c47346d6a3eeee86bc55fa516d9f4e73d49))
+* improve formatter performance ([7e03e85](https://github.com/braintrustdata/braintrust-java/commit/7e03e8533c820200ddfe9d4d39ba0d9bc533f4b8))
+* improve formatter performance ([b6e41d0](https://github.com/braintrustdata/braintrust-java/commit/b6e41d05764a88105cee99bd26d17eb60f80352b))
+* increase max gradle JVM heap to 8GB ([e88d096](https://github.com/braintrustdata/braintrust-java/commit/e88d096c45bf5a8b192aa25290bcefa1b98105c6))
+* **internal:** add async lock helper ([62dce85](https://github.com/braintrustdata/braintrust-java/commit/62dce854fa19a3c1a53f881e95f4d625f7cbaa54))
+* **internal:** allow running specific example from cli ([ff998ed](https://github.com/braintrustdata/braintrust-java/commit/ff998ed099c02f1340558bd274685a7d6273873c))
+* **internal:** bump ci test timeout ([c823880](https://github.com/braintrustdata/braintrust-java/commit/c8238804faed172d1a6ab5678287e840631b4bd2))
+* **internal:** codegen related update ([cd321c9](https://github.com/braintrustdata/braintrust-java/commit/cd321c953fd40a9ad9b4acd2c6394ed08afc78eb))
+* **internal:** codegen related update ([2ed652b](https://github.com/braintrustdata/braintrust-java/commit/2ed652b7ce471d4a551e391a14c97bf5a10e4a2b))
+* **internal:** codegen related update ([1e2fa56](https://github.com/braintrustdata/braintrust-java/commit/1e2fa560ec11e9e31e2d2328cd8dffa027c20f4a))
+* **internal:** codegen related update ([f28bc60](https://github.com/braintrustdata/braintrust-java/commit/f28bc603c8009ea98477ede33c8a87ea12b8bb93))
+* **internal:** dynamically determine included projects ([9fcff68](https://github.com/braintrustdata/braintrust-java/commit/9fcff686b6ca947d06f80eeaff88174ac4aa6bda))
+* **internal:** java 17 -> 21 on ci ([ce64b45](https://github.com/braintrustdata/braintrust-java/commit/ce64b45664323ae4d427aadae817c2178be94b5a))
+* **internal:** reduce proguard ci logging ([4d23bb8](https://github.com/braintrustdata/braintrust-java/commit/4d23bb89a38926be3620b70f048526ce30a05742))
+* **internal:** refactor delegating from client to options ([8b05411](https://github.com/braintrustdata/braintrust-java/commit/8b054110bd40910f22f52ec943cedc2e6dc5ecb8))
+* **internal:** remove flaky `-Xbackend-threads=0` option ([3b5c63c](https://github.com/braintrustdata/braintrust-java/commit/3b5c63c53cf8b742f151bbb8429322b36ac84a6a))
+* **internal:** remove unnecessary `[...]` in `[@see](https://github.com/see)` ([bbd6ab6](https://github.com/braintrustdata/braintrust-java/commit/bbd6ab68df9cdce2607548f7c8b2e6a288ed94a8))
+* **internal:** support passing arguments to test script ([26ca502](https://github.com/braintrustdata/braintrust-java/commit/26ca502a788e989316c314a8a204a9d5531d7942))
+* **internal:** support running formatters directly ([1bfd53a](https://github.com/braintrustdata/braintrust-java/commit/1bfd53a5fe245df0acb8ebc0b39b88ff146e8961))
+* **internal:** update comment in script ([fb33197](https://github.com/braintrustdata/braintrust-java/commit/fb331975009e27f40c13e448d90ec23b0f090669))
+* **internal:** update java toolchain ([f9f4254](https://github.com/braintrustdata/braintrust-java/commit/f9f4254689cae85a734c79c0cf25f283163b6382))
+* remove memory upper bound from publishing step ([d8ca7a9](https://github.com/braintrustdata/braintrust-java/commit/d8ca7a9e64d50078c399d9d7552a3d2c14df8a40))
+* update @stainless-api/prism-cli to v5.15.0 ([7138c31](https://github.com/braintrustdata/braintrust-java/commit/7138c31970ba385cf1548e63af3746c61872a256))
+
+
+### Documentation
+
+* fix missing readme comment ([d1346c2](https://github.com/braintrustdata/braintrust-java/commit/d1346c2f1c8be9157d5e77d7db8a20b746b933e3))
+* more code comments ([cba7926](https://github.com/braintrustdata/braintrust-java/commit/cba79267f1c5e2a82b728fec6e44709d9387bd92))
+* remove `$` for better copy-pasteabality ([c162005](https://github.com/braintrustdata/braintrust-java/commit/c162005e4957239975573760d8e07cdb0e0b851f))
+
+
+### Refactors
+
+* **internal:** minor `ClientOptionsTest` change ([1dc2b4d](https://github.com/braintrustdata/braintrust-java/commit/1dc2b4d4f57bab9408f18407c877569540a6e7f0))
+
## 0.9.0 (2025-04-23)
Full Changelog: [v0.8.0...v0.9.0](https://github.com/braintrustdata/braintrust-java/compare/v0.8.0...v0.9.0)
diff --git a/README.md b/README.md
index f494ee60..11fa79f2 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ NOTE: This repo is a Java client for the Braintrust REST API. If you wish to tra
-[](https://central.sonatype.com/artifact/com.braintrustdata.api/braintrust-java/0.9.0)
-[](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.9.0)
+[](https://central.sonatype.com/artifact/com.braintrustdata.api/braintrust-java/0.10.0)
+[](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.10.0)
@@ -17,7 +17,7 @@ It is generated with [Stainless](https://www.stainless.com/).
-The REST API documentation can be found on [www.braintrustdata.com](https://www.braintrustdata.com/docs/api/spec). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.9.0).
+The REST API documentation can be found on [www.braintrustdata.com](https://www.braintrustdata.com/docs/api/spec). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.10.0).
@@ -28,7 +28,7 @@ The REST API documentation can be found on [www.braintrustdata.com](https://www.
### Gradle
```kotlin
-implementation("com.braintrustdata.api:braintrust-java:0.9.0")
+implementation("com.braintrustdata.api:braintrust-java:0.10.0")
```
### Maven
@@ -37,7 +37,7 @@ implementation("com.braintrustdata.api:braintrust-java:0.9.0")
com.braintrustdata.api
braintrust-java
- 0.9.0
+ 0.10.0
```
@@ -55,7 +55,8 @@ import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
import com.braintrustdata.api.models.Project;
import com.braintrustdata.api.models.ProjectCreateParams;
-// Configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
+// Configures using the `braintrust.apiKey` and `braintrust.baseUrl` system properties
+// Or configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
BraintrustClient client = BraintrustOkHttpClient.fromEnv();
ProjectCreateParams params = ProjectCreateParams.builder()
@@ -66,13 +67,14 @@ Project project = client.projects().create(params);
## Client configuration
-Configure the client using environment variables:
+Configure the client using system properties or environment variables:
```java
import com.braintrustdata.api.client.BraintrustClient;
import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
-// Configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
+// Configures using the `braintrust.apiKey` and `braintrust.baseUrl` system properties
+// Or configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
BraintrustClient client = BraintrustOkHttpClient.fromEnv();
```
@@ -94,7 +96,8 @@ import com.braintrustdata.api.client.BraintrustClient;
import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
BraintrustClient client = BraintrustOkHttpClient.builder()
- // Configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
+ // Configures using the `braintrust.apiKey` and `braintrust.baseUrl` system properties
+ // Or configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
.fromEnv()
.apiKey("My API Key")
.build();
@@ -102,15 +105,32 @@ BraintrustClient client = BraintrustOkHttpClient.builder()
See this table for the available options:
-| Setter | Environment variable | Required | Default value |
-| --------- | --------------------- | -------- | ------------------------------ |
-| `apiKey` | `BRAINTRUST_API_KEY` | false | - |
-| `baseUrl` | `BRAINTRUST_BASE_URL` | true | `"https://api.braintrust.dev"` |
+| Setter | System property | Environment variable | Required | Default value |
+| --------- | -------------------- | --------------------- | -------- | ------------------------------ |
+| `apiKey` | `braintrust.apiKey` | `BRAINTRUST_API_KEY` | false | - |
+| `baseUrl` | `braintrust.baseUrl` | `BRAINTRUST_BASE_URL` | true | `"https://api.braintrust.dev"` |
+
+System properties take precedence over environment variables.
> [!TIP]
> Don't create more than one client in the same application. Each client has a connection pool and
> thread pools, which are more efficient to share between requests.
+### Modifying configuration
+
+To temporarily use a modified client configuration, while reusing the same connection and thread pools, call `withOptions()` on any client or service:
+
+```java
+import com.braintrustdata.api.client.BraintrustClient;
+
+BraintrustClient clientWithOptions = client.withOptions(optionsBuilder -> {
+ optionsBuilder.baseUrl("https://example.com");
+ optionsBuilder.maxRetries(42);
+});
+```
+
+The `withOptions()` method does not affect the original client or service.
+
## Requests and responses
To send a request to the Braintrust API, build an instance of some `Params` class and pass it to the corresponding client method. When the response is received, it will be deserialized into an instance of a Java class.
@@ -136,7 +156,8 @@ import com.braintrustdata.api.models.Project;
import com.braintrustdata.api.models.ProjectCreateParams;
import java.util.concurrent.CompletableFuture;
-// Configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
+// Configures using the `braintrust.apiKey` and `braintrust.baseUrl` system properties
+// Or configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
BraintrustClient client = BraintrustOkHttpClient.fromEnv();
ProjectCreateParams params = ProjectCreateParams.builder()
@@ -154,7 +175,8 @@ import com.braintrustdata.api.models.Project;
import com.braintrustdata.api.models.ProjectCreateParams;
import java.util.concurrent.CompletableFuture;
-// Configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
+// Configures using the `braintrust.apiKey` and `braintrust.baseUrl` system properties
+// Or configures using the `BRAINTRUST_API_KEY` and `BRAINTRUST_BASE_URL` environment variables
BraintrustClientAsync client = BraintrustOkHttpClientAsync.fromEnv();
ProjectCreateParams params = ProjectCreateParams.builder()
@@ -213,59 +235,109 @@ The SDK throws custom unchecked exception types:
- [`BraintrustIoException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustIoException.kt): I/O networking errors.
+- [`BraintrustRetryableException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustRetryableException.kt): Generic error indicating a failure that could be retried by the client.
+
- [`BraintrustInvalidDataException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response.
- [`BraintrustException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class.
## 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.braintrustdata.api.models.Project;
import com.braintrustdata.api.models.ProjectListPage;
-// As an Iterable:
-ProjectListPage page = client.projects().list(params);
+ProjectListPage page = client.projects().list();
+
+// Process as an Iterable
for (Project project : page.autoPager()) {
System.out.println(project);
-};
+}
-// As a Stream:
-client.projects().list(params).autoPager().stream()
+// Process as a Stream
+page.autoPager()
+ .stream()
.limit(50)
.forEach(project -> System.out.println(project));
```
-### Asynchronous
+When using the asynchronous client, the method returns an [`AsyncStreamResponse`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/AsyncStreamResponse.kt):
```java
-// Using forEach, which returns CompletableFuture:
-asyncClient.projects().list(params).autoPager()
- .forEach(project -> System.out.println(project), executor);
+import com.braintrustdata.api.core.http.AsyncStreamResponse;
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectListPageAsync;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+CompletableFuture pageFuture = client.async().projects().list();
+
+pageFuture.thenRun(page -> page.autoPager().subscribe(project -> {
+ System.out.println(project);
+}));
+
+// If you need to handle errors or completion of the stream
+pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Handler<>() {
+ @Override
+ public void onNext(Project project) {
+ System.out.println(project);
+ }
+
+ @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(project -> {
+ System.out.println(project);
+ })
+ .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.braintrustdata.api.models.Project;
import com.braintrustdata.api.models.ProjectListPage;
-ProjectListPage page = client.projects().list(params);
-while (page != null) {
- for (Project project : page.objects()) {
+ProjectListPage page = client.projects().list();
+while (true) {
+ for (Project project : page.items()) {
System.out.println(project);
}
- page = page.getNextPage().orElse(null);
+ if (!page.hasNextPage()) {
+ break;
+ }
+
+ page = page.nextPage();
}
```
@@ -276,15 +348,21 @@ The SDK uses the standard [OkHttp logging interceptor](https://github.com/square
Enable logging by setting the `BRAINTRUST_LOG` environment variable to `info`:
```sh
-$ export BRAINTRUST_LOG=info
+export BRAINTRUST_LOG=info
```
Or to `debug` for more verbose logging:
```sh
-$ export BRAINTRUST_LOG=debug
+export BRAINTRUST_LOG=debug
```
+## ProGuard and R8
+
+Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `braintrust-java-core` is published with a [configuration file](braintrust-java-core/src/main/resources/META-INF/proguard/braintrust-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).
+
+ProGuard and R8 should automatically detect and use the published rules, but you can also manually copy the keep rules if necessary.
+
## 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.
@@ -300,7 +378,7 @@ If the SDK threw an exception, but you're _certain_ the version is compatible, t
### Retries
-The SDK automatically retries 2 times by default, with a short exponential backoff.
+The SDK automatically retries 2 times by default, with a short exponential backoff between requests.
Only the following error types are retried:
@@ -310,7 +388,7 @@ Only the following error types are retried:
- 429 Rate Limit
- 5xx Internal
-The API may also explicitly instruct the SDK to retry or not retry a response.
+The API may also explicitly instruct the SDK to retry or not retry a request.
To set a custom number of retries, configure the client using the `maxRetries` method:
@@ -332,7 +410,6 @@ To set a custom timeout, configure the method call using the `timeout` method:
```java
import com.braintrustdata.api.models.Project;
-import com.braintrustdata.api.models.ProjectCreateParams;
Project project = client.projects().create(
params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
@@ -372,6 +449,27 @@ BraintrustClient client = BraintrustOkHttpClient.builder()
.build();
```
+### HTTPS
+
+> [!NOTE]
+> Most applications should not call these methods, and instead use the system defaults. The defaults include
+> special optimizations that can be lost if the implementations are modified.
+
+To configure how HTTPS connections are secured, configure the client using the `sslSocketFactory`, `trustManager`, and `hostnameVerifier` methods:
+
+```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+
+BraintrustClient client = BraintrustOkHttpClient.builder()
+ .fromEnv()
+ // If `sslSocketFactory` is set, then `trustManager` must be set, and vice versa.
+ .sslSocketFactory(yourSSLSocketFactory)
+ .trustManager(yourTrustManager)
+ .hostnameVerifier(yourHostnameVerifier)
+ .build();
+```
+
### Custom HTTP client
The SDK consists of three artifacts:
@@ -580,7 +678,6 @@ Or configure the method call to validate the response using the `responseValidat
```java
import com.braintrustdata.api.models.Project;
-import com.braintrustdata.api.models.ProjectCreateParams;
Project project = client.projects().create(
params, RequestOptions.builder().responseValidation(true).build()
diff --git a/SECURITY.md b/SECURITY.md
index 0923aae0..a7f4f3b4 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 Braintrust please follow the respective company's security reporting guidelines.
+or products provided by Braintrust, please follow the respective company's security reporting guidelines.
### Braintrust Terms and Policies
-Please contact info@braintrustdata.com for any questions or concerns regarding security of our services.
+Please contact info@braintrustdata.com for any questions or concerns regarding the security of our services.
---
diff --git a/bin/check-release-environment b/bin/check-release-environment
index cc9c2cd7..3a6a7b4a 100755
--- a/bin/check-release-environment
+++ b/bin/check-release-environment
@@ -3,19 +3,19 @@
errors=()
if [ -z "${SONATYPE_USERNAME}" ]; then
- errors+=("The BRAINTRUST_SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets")
+ errors+=("The SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${SONATYPE_PASSWORD}" ]; then
- errors+=("The BRAINTRUST_SONATYPE_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
+ errors+=("The SONATYPE_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${GPG_SIGNING_KEY}" ]; then
- errors+=("The BRAINTRUST_SONATYPE_GPG_SIGNING_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets")
+ errors+=("The GPG_SIGNING_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${GPG_SIGNING_PASSWORD}" ]; then
- errors+=("The BRAINTRUST_SONATYPE_GPG_SIGNING_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
+ errors+=("The GPG_SIGNING_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
lenErrors=${#errors[@]}
diff --git a/braintrust-java-client-okhttp/build.gradle.kts b/braintrust-java-client-okhttp/build.gradle.kts
index 8408ea37..16bbfd8e 100755
--- a/braintrust-java-client-okhttp/build.gradle.kts
+++ b/braintrust-java-client-okhttp/build.gradle.kts
@@ -11,4 +11,5 @@ dependencies {
testImplementation(kotlin("test"))
testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
}
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
index aa4f40a4..e91ce488 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
@@ -5,23 +5,40 @@ package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.client.BraintrustClient
import com.braintrustdata.api.client.BraintrustClientImpl
import com.braintrustdata.api.core.ClientOptions
+import com.braintrustdata.api.core.Sleeper
import com.braintrustdata.api.core.Timeout
+import com.braintrustdata.api.core.http.AsyncStreamResponse
import com.braintrustdata.api.core.http.Headers
+import com.braintrustdata.api.core.http.HttpClient
import com.braintrustdata.api.core.http.QueryParams
+import com.braintrustdata.api.core.jsonMapper
import com.fasterxml.jackson.databind.json.JsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
import kotlin.jvm.optionals.getOrNull
+/**
+ * A class that allows building an instance of [BraintrustClient] with [OkHttpClient] as the
+ * underlying [HttpClient].
+ */
class BraintrustOkHttpClient private constructor() {
companion object {
- /** Returns a mutable builder for constructing an instance of [BraintrustOkHttpClient]. */
+ /** Returns a mutable builder for constructing an instance of [BraintrustClient]. */
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns a client configured using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): BraintrustClient = builder().fromEnv().build()
}
@@ -29,10 +46,63 @@ class BraintrustOkHttpClient private constructor() {
class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
- private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
+
+ fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
- fun baseUrl(baseUrl: String) = apply { clientOptions.baseUrl(baseUrl) }
+ /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
+ fun proxy(proxy: Optional) = proxy(proxy.getOrNull())
+
+ /**
+ * The socket factory used to secure HTTPS connections.
+ *
+ * If this is set, then [trustManager] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
+ fun sslSocketFactory(sslSocketFactory: Optional) =
+ sslSocketFactory(sslSocketFactory.getOrNull())
+
+ /**
+ * The trust manager used to secure HTTPS connections.
+ *
+ * If this is set, then [sslSocketFactory] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
+
+ /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
+ fun trustManager(trustManager: Optional) =
+ trustManager(trustManager.getOrNull())
+
+ /**
+ * The verifier used to confirm that response certificates apply to requested hostnames for
+ * HTTPS connections.
+ *
+ * If unset, then a default hostname verifier is used.
+ */
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
+ }
+
+ /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
+ fun hostnameVerifier(hostnameVerifier: Optional) =
+ hostnameVerifier(hostnameVerifier.getOrNull())
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
@@ -45,10 +115,104 @@ class BraintrustOkHttpClient private constructor() {
clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
}
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [com.braintrustdata.api.core.jsonMapper]. The default is usually sufficient
+ * and rarely needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.braintrust.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
+ fun responseValidation(responseValidation: Boolean) = apply {
+ clientOptions.responseValidation(responseValidation)
+ }
+
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
+ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+
+ fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
+
+ /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
+ fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
+
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map>) = apply {
@@ -129,33 +293,11 @@ class BraintrustOkHttpClient private constructor() {
clientOptions.removeAllQueryParams(keys)
}
- fun timeout(timeout: Timeout) = apply {
- clientOptions.timeout(timeout)
- this.timeout = timeout
- }
-
/**
- * Sets the maximum time allowed for a complete HTTP call, not including retries.
- *
- * See [Timeout.request] for more details.
+ * Updates configuration using system properties and environment variables.
*
- * For fine-grained control, pass a [Timeout] object.
+ * @see ClientOptions.Builder.fromEnv
*/
- fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
-
- fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
-
- fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
-
- fun responseValidation(responseValidation: Boolean) = apply {
- clientOptions.responseValidation(responseValidation)
- }
-
- fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
-
- /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
- fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
-
fun fromEnv() = apply { clientOptions.fromEnv() }
/**
@@ -168,9 +310,11 @@ class BraintrustOkHttpClient private constructor() {
clientOptions
.httpClient(
OkHttpClient.builder()
- .baseUrl(clientOptions.baseUrl())
- .timeout(timeout)
+ .timeout(clientOptions.timeout())
.proxy(proxy)
+ .sslSocketFactory(sslSocketFactory)
+ .trustManager(trustManager)
+ .hostnameVerifier(hostnameVerifier)
.build()
)
.build()
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
index c0d37976..ae462cb7 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
@@ -5,25 +5,40 @@ package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.client.BraintrustClientAsync
import com.braintrustdata.api.client.BraintrustClientAsyncImpl
import com.braintrustdata.api.core.ClientOptions
+import com.braintrustdata.api.core.Sleeper
import com.braintrustdata.api.core.Timeout
+import com.braintrustdata.api.core.http.AsyncStreamResponse
import com.braintrustdata.api.core.http.Headers
+import com.braintrustdata.api.core.http.HttpClient
import com.braintrustdata.api.core.http.QueryParams
+import com.braintrustdata.api.core.jsonMapper
import com.fasterxml.jackson.databind.json.JsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
import kotlin.jvm.optionals.getOrNull
+/**
+ * A class that allows building an instance of [BraintrustClientAsync] with [OkHttpClient] as the
+ * underlying [HttpClient].
+ */
class BraintrustOkHttpClientAsync private constructor() {
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [BraintrustOkHttpClientAsync].
- */
+ /** Returns a mutable builder for constructing an instance of [BraintrustClientAsync]. */
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns a client configured using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): BraintrustClientAsync = builder().fromEnv().build()
}
@@ -31,10 +46,63 @@ class BraintrustOkHttpClientAsync private constructor() {
class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
- private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
+
+ fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+
+ /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
+ fun proxy(proxy: Optional) = proxy(proxy.getOrNull())
+
+ /**
+ * The socket factory used to secure HTTPS connections.
+ *
+ * If this is set, then [trustManager] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
+ fun sslSocketFactory(sslSocketFactory: Optional) =
+ sslSocketFactory(sslSocketFactory.getOrNull())
+
+ /**
+ * The trust manager used to secure HTTPS connections.
+ *
+ * If this is set, then [sslSocketFactory] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
+
+ /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
+ fun trustManager(trustManager: Optional) =
+ trustManager(trustManager.getOrNull())
+
+ /**
+ * The verifier used to confirm that response certificates apply to requested hostnames for
+ * HTTPS connections.
+ *
+ * If unset, then a default hostname verifier is used.
+ */
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
+ }
- fun baseUrl(baseUrl: String) = apply { clientOptions.baseUrl(baseUrl) }
+ /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
+ fun hostnameVerifier(hostnameVerifier: Optional) =
+ hostnameVerifier(hostnameVerifier.getOrNull())
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
@@ -47,10 +115,104 @@ class BraintrustOkHttpClientAsync private constructor() {
clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
}
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [com.braintrustdata.api.core.jsonMapper]. The default is usually sufficient
+ * and rarely needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.braintrust.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
+ fun responseValidation(responseValidation: Boolean) = apply {
+ clientOptions.responseValidation(responseValidation)
+ }
+
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
+ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+
+ fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
+
+ /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
+ fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
+
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map>) = apply {
@@ -131,33 +293,11 @@ class BraintrustOkHttpClientAsync private constructor() {
clientOptions.removeAllQueryParams(keys)
}
- fun timeout(timeout: Timeout) = apply {
- clientOptions.timeout(timeout)
- this.timeout = timeout
- }
-
/**
- * Sets the maximum time allowed for a complete HTTP call, not including retries.
- *
- * See [Timeout.request] for more details.
+ * Updates configuration using system properties and environment variables.
*
- * For fine-grained control, pass a [Timeout] object.
+ * @see ClientOptions.Builder.fromEnv
*/
- fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
-
- fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
-
- fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
-
- fun responseValidation(responseValidation: Boolean) = apply {
- clientOptions.responseValidation(responseValidation)
- }
-
- fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
-
- /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
- fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
-
fun fromEnv() = apply { clientOptions.fromEnv() }
/**
@@ -170,9 +310,11 @@ class BraintrustOkHttpClientAsync private constructor() {
clientOptions
.httpClient(
OkHttpClient.builder()
- .baseUrl(clientOptions.baseUrl())
- .timeout(timeout)
+ .timeout(clientOptions.timeout())
.proxy(proxy)
+ .sslSocketFactory(sslSocketFactory)
+ .trustManager(trustManager)
+ .hostnameVerifier(hostnameVerifier)
.build()
)
.build()
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
index 49a5df68..2ffdf3d7 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
@@ -2,7 +2,6 @@ package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.core.RequestOptions
import com.braintrustdata.api.core.Timeout
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.HttpClient
import com.braintrustdata.api.core.http.HttpMethod
@@ -14,10 +13,13 @@ import java.io.IOException
import java.io.InputStream
import java.net.Proxy
import java.time.Duration
+import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
import okhttp3.Call
import okhttp3.Callback
-import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
@@ -29,8 +31,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import okio.BufferedSink
class OkHttpClient
-private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
- HttpClient {
+private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient {
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
val call = newCall(request, requestOptions)
@@ -50,20 +51,25 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
): CompletableFuture {
val future = CompletableFuture()
- request.body?.run { future.whenComplete { _, _ -> close() } }
-
- newCall(request, requestOptions)
- .enqueue(
- object : Callback {
- override fun onResponse(call: Call, response: Response) {
- future.complete(response.toResponse())
- }
+ val call = newCall(request, requestOptions)
+ call.enqueue(
+ object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ future.complete(response.toResponse())
+ }
- override fun onFailure(call: Call, e: IOException) {
- future.completeExceptionally(BraintrustIoException("Request failed", e))
- }
+ override fun onFailure(call: Call, e: IOException) {
+ future.completeExceptionally(BraintrustIoException("Request failed", e))
}
- )
+ }
+ )
+
+ future.whenComplete { _, e ->
+ if (e is CancellationException) {
+ call.cancel()
+ }
+ request.body?.close()
+ }
return future
}
@@ -109,19 +115,19 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
val builder = Request.Builder().url(toUrl()).method(method.name, body)
headers.names().forEach { name ->
- headers.values(name).forEach { builder.header(name, it) }
+ headers.values(name).forEach { builder.addHeader(name, it) }
}
if (
!headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
) {
- builder.header(
+ builder.addHeader(
"X-Stainless-Read-Timeout",
Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
)
}
if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
- builder.header(
+ builder.addHeader(
"X-Stainless-Timeout",
Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
)
@@ -140,11 +146,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
}
private fun HttpRequest.toUrl(): String {
- url?.let {
- return it
- }
-
- val builder = baseUrl.newBuilder()
+ val builder = baseUrl.toHttpUrl().newBuilder()
pathSegments.forEach(builder::addPathSegment)
queryParams.keys().forEach { key ->
queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
@@ -194,11 +196,11 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
class Builder internal constructor() {
- private var baseUrl: HttpUrl? = null
private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
-
- fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl.toHttpUrl() }
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
@@ -206,6 +208,18 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
+
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
+ }
+
fun build(): OkHttpClient =
OkHttpClient(
okhttp3.OkHttpClient.Builder()
@@ -214,8 +228,25 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
.writeTimeout(timeout.write())
.callTimeout(timeout.request())
.proxy(proxy)
- .build(),
- checkRequired("baseUrl", baseUrl),
+ .apply {
+ val sslSocketFactory = sslSocketFactory
+ val trustManager = trustManager
+ if (sslSocketFactory != null && trustManager != null) {
+ sslSocketFactory(sslSocketFactory, trustManager)
+ } else {
+ check((sslSocketFactory != null) == (trustManager != null)) {
+ "Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set"
+ }
+ }
+
+ hostnameVerifier?.let(::hostnameVerifier)
+ }
+ .build()
+ .apply {
+ // We usually make all our requests to the same host so it makes sense to
+ // raise the per-host limit to the overall limit.
+ dispatcher.maxRequestsPerHost = dispatcher.maxRequests
+ }
)
}
}
diff --git a/braintrust-java-client-okhttp/src/test/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClientTest.kt b/braintrust-java-client-okhttp/src/test/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClientTest.kt
new file mode 100644
index 00000000..ed9f0458
--- /dev/null
+++ b/braintrust-java-client-okhttp/src/test/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClientTest.kt
@@ -0,0 +1,44 @@
+package com.braintrustdata.api.client.okhttp
+
+import com.braintrustdata.api.core.http.HttpMethod
+import com.braintrustdata.api.core.http.HttpRequest
+import com.github.tomakehurst.wiremock.client.WireMock.*
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
+import com.github.tomakehurst.wiremock.junit5.WireMockTest
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.parallel.ResourceLock
+
+@WireMockTest
+@ResourceLock("https://github.com/wiremock/wiremock/issues/169")
+internal class OkHttpClientTest {
+
+ private lateinit var baseUrl: String
+ private lateinit var httpClient: OkHttpClient
+
+ @BeforeEach
+ fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) {
+ baseUrl = wmRuntimeInfo.httpBaseUrl
+ httpClient = OkHttpClient.builder().build()
+ }
+
+ @Test
+ fun executeAsync_whenFutureCancelled_cancelsUnderlyingCall() {
+ stubFor(post(urlPathEqualTo("/something")).willReturn(ok()))
+ val responseFuture =
+ httpClient.executeAsync(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build()
+ )
+ val call = httpClient.okHttpClient.dispatcher.runningCalls().single()
+
+ responseFuture.cancel(false)
+
+ // Should have cancelled the underlying call
+ assertThat(call.isCanceled()).isTrue()
+ }
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
index bc327480..7881923e 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
@@ -2,6 +2,7 @@
package com.braintrustdata.api.client
+import com.braintrustdata.api.core.ClientOptions
import com.braintrustdata.api.services.blocking.AclService
import com.braintrustdata.api.services.blocking.AiSecretService
import com.braintrustdata.api.services.blocking.ApiKeyService
@@ -21,6 +22,7 @@ import com.braintrustdata.api.services.blocking.SpanIframeService
import com.braintrustdata.api.services.blocking.TopLevelService
import com.braintrustdata.api.services.blocking.UserService
import com.braintrustdata.api.services.blocking.ViewService
+import java.util.function.Consumer
/**
* A client for interacting with the Braintrust REST API synchronously. You can also switch to
@@ -51,6 +53,13 @@ interface BraintrustClient {
*/
fun withRawResponse(): WithRawResponse
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): BraintrustClient
+
fun topLevel(): TopLevelService
fun projects(): ProjectService
@@ -105,6 +114,13 @@ interface BraintrustClient {
/** A view of [BraintrustClient] that provides access to raw HTTP responses for each method. */
interface WithRawResponse {
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): BraintrustClient.WithRawResponse
+
fun topLevel(): TopLevelService.WithRawResponse
fun projects(): ProjectService.WithRawResponse
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
index 24ed3368..c6edb032 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
@@ -2,6 +2,7 @@
package com.braintrustdata.api.client
+import com.braintrustdata.api.core.ClientOptions
import com.braintrustdata.api.services.async.AclServiceAsync
import com.braintrustdata.api.services.async.AiSecretServiceAsync
import com.braintrustdata.api.services.async.ApiKeyServiceAsync
@@ -21,6 +22,7 @@ import com.braintrustdata.api.services.async.SpanIframeServiceAsync
import com.braintrustdata.api.services.async.TopLevelServiceAsync
import com.braintrustdata.api.services.async.UserServiceAsync
import com.braintrustdata.api.services.async.ViewServiceAsync
+import java.util.function.Consumer
/**
* A client for interacting with the Braintrust REST API asynchronously. You can also switch to
@@ -51,6 +53,13 @@ interface BraintrustClientAsync {
*/
fun withRawResponse(): WithRawResponse
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): BraintrustClientAsync
+
fun topLevel(): TopLevelServiceAsync
fun projects(): ProjectServiceAsync
@@ -107,6 +116,15 @@ interface BraintrustClientAsync {
*/
interface WithRawResponse {
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(
+ modifier: Consumer
+ ): BraintrustClientAsync.WithRawResponse
+
fun topLevel(): TopLevelServiceAsync.WithRawResponse
fun projects(): ProjectServiceAsync.WithRawResponse
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
index e945466b..2d94732b 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
@@ -42,6 +42,7 @@ import com.braintrustdata.api.services.async.UserServiceAsync
import com.braintrustdata.api.services.async.UserServiceAsyncImpl
import com.braintrustdata.api.services.async.ViewServiceAsync
import com.braintrustdata.api.services.async.ViewServiceAsyncImpl
+import java.util.function.Consumer
class BraintrustClientAsyncImpl(private val clientOptions: ClientOptions) : BraintrustClientAsync {
@@ -130,6 +131,9 @@ class BraintrustClientAsyncImpl(private val clientOptions: ClientOptions) : Brai
override fun withRawResponse(): BraintrustClientAsync.WithRawResponse = withRawResponse
+ override fun withOptions(modifier: Consumer): BraintrustClientAsync =
+ BraintrustClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+
override fun topLevel(): TopLevelServiceAsync = topLevel
override fun projects(): ProjectServiceAsync = projects
@@ -168,7 +172,7 @@ class BraintrustClientAsyncImpl(private val clientOptions: ClientOptions) : Brai
override fun evals(): EvalServiceAsync = evals
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
BraintrustClientAsync.WithRawResponse {
@@ -249,6 +253,13 @@ class BraintrustClientAsyncImpl(private val clientOptions: ClientOptions) : Brai
EvalServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
+ override fun withOptions(
+ modifier: Consumer
+ ): BraintrustClientAsync.WithRawResponse =
+ BraintrustClientAsyncImpl.WithRawResponseImpl(
+ clientOptions.toBuilder().apply(modifier::accept).build()
+ )
+
override fun topLevel(): TopLevelServiceAsync.WithRawResponse = topLevel
override fun projects(): ProjectServiceAsync.WithRawResponse = projects
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
index 3eecd3cb..c205ae48 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
@@ -42,6 +42,7 @@ import com.braintrustdata.api.services.blocking.UserService
import com.braintrustdata.api.services.blocking.UserServiceImpl
import com.braintrustdata.api.services.blocking.ViewService
import com.braintrustdata.api.services.blocking.ViewServiceImpl
+import java.util.function.Consumer
class BraintrustClientImpl(private val clientOptions: ClientOptions) : BraintrustClient {
@@ -118,6 +119,9 @@ class BraintrustClientImpl(private val clientOptions: ClientOptions) : Braintrus
override fun withRawResponse(): BraintrustClient.WithRawResponse = withRawResponse
+ override fun withOptions(modifier: Consumer): BraintrustClient =
+ BraintrustClientImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+
override fun topLevel(): TopLevelService = topLevel
override fun projects(): ProjectService = projects
@@ -156,7 +160,7 @@ class BraintrustClientImpl(private val clientOptions: ClientOptions) : Braintrus
override fun evals(): EvalService = evals
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
BraintrustClient.WithRawResponse {
@@ -237,6 +241,13 @@ class BraintrustClientImpl(private val clientOptions: ClientOptions) : Braintrus
EvalServiceImpl.WithRawResponseImpl(clientOptions)
}
+ override fun withOptions(
+ modifier: Consumer
+ ): BraintrustClient.WithRawResponse =
+ BraintrustClientImpl.WithRawResponseImpl(
+ clientOptions.toBuilder().apply(modifier::accept).build()
+ )
+
override fun topLevel(): TopLevelService.WithRawResponse = topLevel
override fun projects(): ProjectService.WithRawResponse = projects
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPager.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPager.kt
new file mode 100644
index 00000000..d208a9f8
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPager.kt
@@ -0,0 +1,21 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPagerAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPagerAsync.kt
new file mode 100644
index 00000000..7ff270b1
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/AutoPagerAsync.kt
@@ -0,0 +1,88 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.braintrustdata.api.core
+
+import com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt
index 7b05d4da..254bc88e 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt
@@ -5,6 +5,9 @@ package com.braintrustdata.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" }
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
index 9841ea6e..e77958a6 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
@@ -2,6 +2,7 @@
package com.braintrustdata.api.core
+import com.braintrustdata.api.core.http.AsyncStreamResponse
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.HttpClient
import com.braintrustdata.api.core.http.PhantomReachableClosingHttpClient
@@ -9,21 +10,102 @@ import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.http.RetryingHttpClient
import com.fasterxml.jackson.databind.json.JsonMapper
import java.time.Clock
+import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicLong
import kotlin.jvm.optionals.getOrNull
+/** A class representing the SDK client configuration. */
class ClientOptions
private constructor(
private val originalHttpClient: HttpClient,
+ /**
+ * The HTTP client to use in the SDK.
+ *
+ * Use the one published in `braintrust-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
+ */
@get:JvmName("httpClient") val httpClient: HttpClient,
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee that
+ * the SDK will work correctly when using an incompatible Jackson version.
+ */
@get:JvmName("checkJacksonVersionCompatibility") val checkJacksonVersionCompatibility: Boolean,
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [com.braintrustdata.api.core.jsonMapper]. The default is usually sufficient and
+ * rarely needs to be overridden.
+ */
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ @get:JvmName("sleeper") val sleeper: Sleeper,
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
@get:JvmName("clock") val clock: Clock,
- @get:JvmName("baseUrl") val baseUrl: String,
+ private val baseUrl: String?,
+ /** Headers to send with the request. */
@get:JvmName("headers") val headers: Headers,
+ /** Query params to send with the request. */
@get:JvmName("queryParams") val queryParams: QueryParams,
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
@get:JvmName("responseValidation") val responseValidation: Boolean,
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
@get:JvmName("timeout") val timeout: Timeout,
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
@get:JvmName("maxRetries") val maxRetries: Int,
private val apiKey: String?,
) {
@@ -34,6 +116,13 @@ private constructor(
}
}
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.braintrust.dev`.
+ */
+ fun baseUrl(): String = baseUrl ?: PRODUCTION_URL
+
fun apiKey(): Optional = Optional.ofNullable(apiKey)
fun toBuilder() = Builder().from(this)
@@ -52,6 +141,11 @@ private constructor(
*/
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns options configured using system properties and environment variables.
+ *
+ * @see Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): ClientOptions = builder().fromEnv().build()
}
@@ -61,8 +155,10 @@ private constructor(
private var httpClient: HttpClient? = null
private var checkJacksonVersionCompatibility: Boolean = true
private var jsonMapper: JsonMapper = jsonMapper()
+ private var streamHandlerExecutor: Executor? = null
+ private var sleeper: Sleeper? = null
private var clock: Clock = Clock.systemUTC()
- private var baseUrl: String = PRODUCTION_URL
+ private var baseUrl: String? = null
private var headers: Headers.Builder = Headers.builder()
private var queryParams: QueryParams.Builder = QueryParams.builder()
private var responseValidation: Boolean = false
@@ -75,6 +171,8 @@ private constructor(
httpClient = clientOptions.originalHttpClient
checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
jsonMapper = clientOptions.jsonMapper
+ streamHandlerExecutor = clientOptions.streamHandlerExecutor
+ sleeper = clientOptions.sleeper
clock = clientOptions.clock
baseUrl = clientOptions.baseUrl
headers = clientOptions.headers.toBuilder()
@@ -85,24 +183,122 @@ private constructor(
apiKey = clientOptions.apiKey
}
- fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
+ /**
+ * The HTTP client to use in the SDK.
+ *
+ * Use the one published in `braintrust-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
+ */
+ fun httpClient(httpClient: HttpClient) = apply {
+ this.httpClient = PhantomReachableClosingHttpClient(httpClient)
+ }
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee
+ * that the SDK will work correctly when using an incompatible Jackson version.
+ */
fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
this.checkJacksonVersionCompatibility = checkJacksonVersionCompatibility
}
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [com.braintrustdata.api.core.jsonMapper]. The default is usually sufficient
+ * and rarely needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor =
+ if (streamHandlerExecutor is ExecutorService)
+ PhantomReachableExecutorService(streamHandlerExecutor)
+ else streamHandlerExecutor
+ }
+
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { this.sleeper = PhantomReachableSleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
fun clock(clock: Clock) = apply { this.clock = clock }
- fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.braintrust.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { this.baseUrl = baseUrl }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
fun responseValidation(responseValidation: Boolean) = apply {
this.responseValidation = responseValidation
}
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
fun apiKey(apiKey: String?) = apply { this.apiKey = apiKey }
@@ -190,11 +386,26 @@ private constructor(
fun removeAllQueryParams(keys: Set) = apply { queryParams.removeAll(keys) }
- fun baseUrl(): String = baseUrl
+ fun timeout(): Timeout = timeout
+ /**
+ * Updates configuration using system properties and environment variables.
+ *
+ * See this table for the available options:
+ *
+ * |Setter |System property |Environment variable |Required|Default value |
+ * |---------|--------------------|---------------------|--------|------------------------------|
+ * |`apiKey` |`braintrust.apiKey` |`BRAINTRUST_API_KEY` |false |- |
+ * |`baseUrl`|`braintrust.baseUrl`|`BRAINTRUST_BASE_URL`|true |`"https://api.braintrust.dev"`|
+ *
+ * System properties take precedence over environment variables.
+ */
fun fromEnv() = apply {
- System.getenv("BRAINTRUST_BASE_URL")?.let { baseUrl(it) }
- System.getenv("BRAINTRUST_API_KEY")?.let { apiKey(it) }
+ (System.getProperty("braintrust.baseUrl") ?: System.getenv("BRAINTRUST_BASE_URL"))
+ ?.let { baseUrl(it) }
+ (System.getProperty("braintrust.apiKey") ?: System.getenv("BRAINTRUST_API_KEY"))?.let {
+ apiKey(it)
+ }
}
/**
@@ -211,6 +422,25 @@ private constructor(
*/
fun build(): ClientOptions {
val httpClient = checkRequired("httpClient", httpClient)
+ val streamHandlerExecutor =
+ streamHandlerExecutor
+ ?: PhantomReachableExecutorService(
+ 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 =
+ "braintrust-stream-handler-thread-${count.getAndIncrement()}"
+ }
+ }
+ )
+ )
+ val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper())
val headers = Headers.builder()
val queryParams = QueryParams.builder()
@@ -231,15 +461,16 @@ private constructor(
return ClientOptions(
httpClient,
- PhantomReachableClosingHttpClient(
- RetryingHttpClient.builder()
- .httpClient(httpClient)
- .clock(clock)
- .maxRetries(maxRetries)
- .build()
- ),
+ RetryingHttpClient.builder()
+ .httpClient(httpClient)
+ .sleeper(sleeper)
+ .clock(clock)
+ .maxRetries(maxRetries)
+ .build(),
checkJacksonVersionCompatibility,
jsonMapper,
+ streamHandlerExecutor,
+ sleeper,
clock,
baseUrl,
headers.build(),
@@ -251,4 +482,20 @@ private constructor(
)
}
}
+
+ /**
+ * Closes these client options, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because the client options are
+ * long-lived and usually should not be synchronously closed via try-with-resources.
+ *
+ * It's also usually not necessary to call this method at all. the default client automatically
+ * releases threads and connections if they remain idle, but if you are writing an application
+ * that needs to aggressively release unused resources, then you may call this method.
+ */
+ fun close() {
+ httpClient.close()
+ (streamHandlerExecutor as? ExecutorService)?.shutdown()
+ sleeper.close()
+ }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/DefaultSleeper.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/DefaultSleeper.kt
new file mode 100644
index 00000000..32ece057
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/DefaultSleeper.kt
@@ -0,0 +1,28 @@
+package com.braintrustdata.api.core
+
+import java.time.Duration
+import java.util.Timer
+import java.util.TimerTask
+import java.util.concurrent.CompletableFuture
+
+class DefaultSleeper : Sleeper {
+
+ private val timer = Timer("DefaultSleeper", 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
+ }
+
+ override fun close() = timer.cancel()
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Page.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Page.kt
new file mode 100644
index 00000000..5d5afd20
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Page.kt
@@ -0,0 +1,33 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PageAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PageAsync.kt
new file mode 100644
index 00000000..10b3b47a
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PageAsync.kt
@@ -0,0 +1,35 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableExecutorService.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableExecutorService.kt
new file mode 100644
index 00000000..0538d523
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableExecutorService.kt
@@ -0,0 +1,58 @@
+package com.braintrustdata.api.core
+
+import java.util.concurrent.Callable
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+
+/**
+ * A delegating wrapper around an [ExecutorService] that shuts it down once it's only phantom
+ * reachable.
+ *
+ * This class ensures the [ExecutorService] is shut down even if the user forgets to do it.
+ */
+internal class PhantomReachableExecutorService(private val executorService: ExecutorService) :
+ ExecutorService {
+ init {
+ closeWhenPhantomReachable(this) { executorService.shutdown() }
+ }
+
+ override fun execute(command: Runnable) = executorService.execute(command)
+
+ override fun shutdown() = executorService.shutdown()
+
+ override fun shutdownNow(): MutableList = executorService.shutdownNow()
+
+ override fun isShutdown(): Boolean = executorService.isShutdown
+
+ override fun isTerminated(): Boolean = executorService.isTerminated
+
+ override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean =
+ executorService.awaitTermination(timeout, unit)
+
+ override fun submit(task: Callable): Future = executorService.submit(task)
+
+ override fun submit(task: Runnable, result: T): Future =
+ executorService.submit(task, result)
+
+ override fun submit(task: Runnable): Future<*> = executorService.submit(task)
+
+ override fun invokeAll(
+ tasks: MutableCollection>
+ ): MutableList> = executorService.invokeAll(tasks)
+
+ override fun invokeAll(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): MutableList> = executorService.invokeAll(tasks, timeout, unit)
+
+ override fun invokeAny(tasks: MutableCollection>): T =
+ executorService.invokeAny(tasks)
+
+ override fun invokeAny(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): T = executorService.invokeAny(tasks, timeout, unit)
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableSleeper.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableSleeper.kt
new file mode 100644
index 00000000..8c293f53
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachableSleeper.kt
@@ -0,0 +1,23 @@
+package com.braintrustdata.api.core
+
+import java.time.Duration
+import java.util.concurrent.CompletableFuture
+
+/**
+ * A delegating wrapper around a [Sleeper] that closes it once it's only phantom reachable.
+ *
+ * This class ensures the [Sleeper] is closed even if the user forgets to do it.
+ */
+internal class PhantomReachableSleeper(private val sleeper: Sleeper) : Sleeper {
+
+ init {
+ closeWhenPhantomReachable(this, sleeper)
+ }
+
+ override fun sleep(duration: Duration) = sleeper.sleep(duration)
+
+ override fun sleepAsync(duration: Duration): CompletableFuture =
+ sleeper.sleepAsync(duration)
+
+ override fun close() = sleeper.close()
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
index a0924603..5130de1b 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
@@ -2,7 +2,7 @@
package com.braintrustdata.api.core
-import java.util.Properties
+import com.braintrustdata.api.client.BraintrustClient
fun getOsArch(): String {
val osArch = System.getProperty("os.arch")
@@ -16,7 +16,7 @@ fun getOsArch(): String {
"x86_64" -> "x64"
"arm" -> "arm"
"aarch64" -> "arm64"
- else -> "other:${osArch}"
+ else -> "other:$osArch"
}
}
@@ -30,13 +30,13 @@ fun getOsName(): String {
osName.startsWith("Linux") -> "Linux"
osName.startsWith("Mac OS") -> "MacOS"
osName.startsWith("Windows") -> "Windows"
- else -> "Other:${osName}"
+ else -> "Other:$osName"
}
}
fun getOsVersion(): String = System.getProperty("os.version", "unknown")
fun getPackageVersion(): String =
- Properties::class.java.`package`.implementationVersion ?: "unknown"
+ BraintrustClient::class.java.`package`.implementationVersion ?: "unknown"
fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Sleeper.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Sleeper.kt
new file mode 100644
index 00000000..856b4009
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Sleeper.kt
@@ -0,0 +1,21 @@
+package com.braintrustdata.api.core
+
+import java.time.Duration
+import java.util.concurrent.CompletableFuture
+
+/**
+ * An interface for delaying execution for a specified amount of time.
+ *
+ * Useful for testing and cleaning up resources.
+ */
+interface Sleeper : AutoCloseable {
+
+ /** Synchronously pauses execution for the given [duration]. */
+ fun sleep(duration: Duration)
+
+ /** Asynchronously pauses execution for the given [duration]. */
+ fun sleepAsync(duration: Duration): CompletableFuture
+
+ /** Overridden from [AutoCloseable] to not have a checked exception in its signature. */
+ override fun close()
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt
index 06ee42fa..b2320ac8 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt
@@ -157,10 +157,14 @@ private constructor(
return true
}
- return /* spotless:off */ other is Timeout && connect == other.connect && read == other.read && write == other.write && request == other.request /* spotless:on */
+ return other is Timeout &&
+ connect == other.connect &&
+ read == other.read &&
+ write == other.write &&
+ request == other.request
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(connect, read, write, request) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(connect, read, write, request)
override fun toString() =
"Timeout{connect=$connect, read=$read, write=$write, request=$request}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
index 6f9fb155..a1657ff3 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
@@ -5,6 +5,8 @@ package com.braintrustdata.api.core
import com.braintrustdata.api.errors.BraintrustInvalidDataException
import java.util.Collections
import java.util.SortedMap
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.locks.Lock
@JvmSynthetic
internal fun T?.getOrThrow(name: String): T =
@@ -90,3 +92,24 @@ internal fun Any?.contentToString(): String {
}
internal interface Enum
+
+/**
+ * Executes the given [action] while holding the lock, returning a [CompletableFuture] with the
+ * result.
+ *
+ * @param action The asynchronous action to execute while holding the lock
+ * @return A [CompletableFuture] that completes with the result of the action
+ */
+@JvmSynthetic
+internal fun Lock.withLockAsync(action: () -> CompletableFuture): CompletableFuture {
+ lock()
+ val future =
+ try {
+ action()
+ } catch (e: Throwable) {
+ unlock()
+ throw e
+ }
+ future.whenComplete { _, _ -> unlock() }
+ return future
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/handlers/ErrorHandler.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/handlers/ErrorHandler.kt
index f3b14814..67559b98 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/handlers/ErrorHandler.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/handlers/ErrorHandler.kt
@@ -19,7 +19,7 @@ import com.braintrustdata.api.errors.UnprocessableEntityException
import com.fasterxml.jackson.databind.json.JsonMapper
@JvmSynthetic
-internal fun errorHandler(jsonMapper: JsonMapper): Handler {
+internal fun errorBodyHandler(jsonMapper: JsonMapper): Handler {
val handler = jsonHandler(jsonMapper)
return object : Handler {
@@ -33,52 +33,52 @@ internal fun errorHandler(jsonMapper: JsonMapper): Handler {
}
@JvmSynthetic
-internal fun Handler.withErrorHandler(errorHandler: Handler): Handler =
- object : Handler {
- override fun handle(response: HttpResponse): T =
+internal fun errorHandler(errorBodyHandler: Handler): Handler =
+ object : Handler {
+ override fun handle(response: HttpResponse): HttpResponse =
when (val statusCode = response.statusCode()) {
- in 200..299 -> this@withErrorHandler.handle(response)
+ in 200..299 -> response
400 ->
throw BadRequestException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
401 ->
throw UnauthorizedException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
403 ->
throw PermissionDeniedException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
404 ->
throw NotFoundException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
422 ->
throw UnprocessableEntityException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
429 ->
throw RateLimitException.builder()
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
in 500..599 ->
throw InternalServerException.builder()
.statusCode(statusCode)
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
else ->
throw UnexpectedStatusCodeException.builder()
.statusCode(statusCode)
.headers(response.headers())
- .body(errorHandler.handle(response))
+ .body(errorBodyHandler.handle(response))
.build()
}
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/AsyncStreamResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/AsyncStreamResponse.kt
new file mode 100644
index 00000000..93dff9ec
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/AsyncStreamResponse.kt
@@ -0,0 +1,157 @@
+package com.braintrustdata.api.core.http
+
+import com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/Headers.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/Headers.kt
index 6d37e804..774088ec 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/Headers.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/Headers.kt
@@ -1,5 +1,15 @@
+// File generated from our OpenAPI spec by Stainless.
+
package com.braintrustdata.api.core.http
+import com.braintrustdata.api.core.JsonArray
+import com.braintrustdata.api.core.JsonBoolean
+import com.braintrustdata.api.core.JsonMissing
+import com.braintrustdata.api.core.JsonNull
+import com.braintrustdata.api.core.JsonNumber
+import com.braintrustdata.api.core.JsonObject
+import com.braintrustdata.api.core.JsonString
+import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.toImmutable
import java.util.TreeMap
@@ -28,6 +38,19 @@ private constructor(
TreeMap(String.CASE_INSENSITIVE_ORDER)
private var size: Int = 0
+ fun put(name: String, value: JsonValue): Builder = apply {
+ when (value) {
+ is JsonMissing,
+ is JsonNull -> {}
+ is JsonBoolean -> put(name, value.value.toString())
+ is JsonNumber -> put(name, value.value.toString())
+ is JsonString -> put(name, value.value)
+ is JsonArray -> value.values.forEach { put(name, it) }
+ is JsonObject ->
+ value.values.forEach { (nestedName, value) -> put("$name.$nestedName", value) }
+ }
+ }
+
fun put(name: String, value: String) = apply {
map.getOrPut(name) { mutableListOf() }.add(value)
size++
@@ -41,15 +64,6 @@ private constructor(
headers.names().forEach { put(it, headers.values(it)) }
}
- fun remove(name: String) = apply { size -= map.remove(name).orEmpty().size }
-
- fun removeAll(names: Set) = apply { names.forEach(::remove) }
-
- fun clear() = apply {
- map.clear()
- size = 0
- }
-
fun replace(name: String, value: String) = apply {
remove(name)
put(name, value)
@@ -68,6 +82,15 @@ private constructor(
headers.names().forEach { replace(it, headers.values(it)) }
}
+ fun remove(name: String) = apply { size -= map.remove(name).orEmpty().size }
+
+ fun removeAll(names: Set) = apply { names.forEach(::remove) }
+
+ fun clear() = apply {
+ map.clear()
+ size = 0
+ }
+
fun build() =
Headers(
map.mapValuesTo(TreeMap(String.CASE_INSENSITIVE_ORDER)) { (_, values) ->
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/HttpRequest.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/HttpRequest.kt
index 1105f7d4..f3c2522c 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/HttpRequest.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/HttpRequest.kt
@@ -6,7 +6,7 @@ import com.braintrustdata.api.core.toImmutable
class HttpRequest
private constructor(
@get:JvmName("method") val method: HttpMethod,
- @get:JvmName("url") val url: String?,
+ @get:JvmName("baseUrl") val baseUrl: String,
@get:JvmName("pathSegments") val pathSegments: List,
@get:JvmName("headers") val headers: Headers,
@get:JvmName("queryParams") val queryParams: QueryParams,
@@ -16,7 +16,7 @@ private constructor(
fun toBuilder(): Builder = Builder().from(this)
override fun toString(): String =
- "HttpRequest{method=$method, url=$url, pathSegments=$pathSegments, headers=$headers, queryParams=$queryParams, body=$body}"
+ "HttpRequest{method=$method, baseUrl=$baseUrl, pathSegments=$pathSegments, headers=$headers, queryParams=$queryParams, body=$body}"
companion object {
@JvmStatic fun builder() = Builder()
@@ -25,7 +25,7 @@ private constructor(
class Builder internal constructor() {
private var method: HttpMethod? = null
- private var url: String? = null
+ private var baseUrl: String? = null
private var pathSegments: MutableList = mutableListOf()
private var headers: Headers.Builder = Headers.builder()
private var queryParams: QueryParams.Builder = QueryParams.builder()
@@ -34,7 +34,7 @@ private constructor(
@JvmSynthetic
internal fun from(request: HttpRequest) = apply {
method = request.method
- url = request.url
+ baseUrl = request.baseUrl
pathSegments = request.pathSegments.toMutableList()
headers = request.headers.toBuilder()
queryParams = request.queryParams.toBuilder()
@@ -43,7 +43,7 @@ private constructor(
fun method(method: HttpMethod) = apply { this.method = method }
- fun url(url: String) = apply { this.url = url }
+ fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
fun addPathSegment(pathSegment: String) = apply { pathSegments.add(pathSegment) }
@@ -136,7 +136,7 @@ private constructor(
fun build(): HttpRequest =
HttpRequest(
checkRequired("method", method),
- url,
+ checkRequired("baseUrl", baseUrl),
pathSegments.toImmutable(),
headers.build(),
queryParams.build(),
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt
new file mode 100644
index 00000000..6d826dd4
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingAsyncStreamResponse.kt
@@ -0,0 +1,56 @@
+package com.braintrustdata.api.core.http
+
+import com.braintrustdata.api.core.closeWhenPhantomReachable
+import com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingStreamResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingStreamResponse.kt
new file mode 100644
index 00000000..9c79ceb6
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingStreamResponse.kt
@@ -0,0 +1,21 @@
+package com.braintrustdata.api.core.http
+
+import com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/QueryParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/QueryParams.kt
index 03c719df..60103d82 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/QueryParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/QueryParams.kt
@@ -2,6 +2,14 @@
package com.braintrustdata.api.core.http
+import com.braintrustdata.api.core.JsonArray
+import com.braintrustdata.api.core.JsonBoolean
+import com.braintrustdata.api.core.JsonMissing
+import com.braintrustdata.api.core.JsonNull
+import com.braintrustdata.api.core.JsonNumber
+import com.braintrustdata.api.core.JsonObject
+import com.braintrustdata.api.core.JsonString
+import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.toImmutable
class QueryParams
@@ -28,6 +36,39 @@ private constructor(
private val map: MutableMap> = mutableMapOf()
private var size: Int = 0
+ fun put(key: String, value: JsonValue): Builder = apply {
+ when (value) {
+ is JsonMissing,
+ is JsonNull -> {}
+ is JsonBoolean -> put(key, value.value.toString())
+ is JsonNumber -> put(key, value.value.toString())
+ is JsonString -> put(key, value.value)
+ is JsonArray ->
+ put(
+ key,
+ value.values
+ .asSequence()
+ .mapNotNull {
+ when (it) {
+ is JsonMissing,
+ is JsonNull -> null
+ is JsonBoolean -> it.value.toString()
+ is JsonNumber -> it.value.toString()
+ is JsonString -> it.value
+ is JsonArray,
+ is JsonObject ->
+ throw IllegalArgumentException(
+ "Cannot comma separate non-primitives in query params"
+ )
+ }
+ }
+ .joinToString(","),
+ )
+ is JsonObject ->
+ value.values.forEach { (nestedKey, value) -> put("$key[$nestedKey]", value) }
+ }
+ }
+
fun put(key: String, value: String) = apply {
map.getOrPut(key) { mutableListOf() }.add(value)
size++
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/RetryingHttpClient.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/RetryingHttpClient.kt
index 830c55cc..5bdca25d 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/RetryingHttpClient.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/RetryingHttpClient.kt
@@ -1,8 +1,11 @@
package com.braintrustdata.api.core.http
+import com.braintrustdata.api.core.DefaultSleeper
import com.braintrustdata.api.core.RequestOptions
+import com.braintrustdata.api.core.Sleeper
import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.errors.BraintrustIoException
+import com.braintrustdata.api.errors.BraintrustRetryableException
import java.io.IOException
import java.time.Clock
import java.time.Duration
@@ -10,8 +13,6 @@ import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
-import java.util.Timer
-import java.util.TimerTask
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ThreadLocalRandom
@@ -129,7 +130,10 @@ private constructor(
return executeWithRetries(modifiedRequest, requestOptions)
}
- override fun close() = httpClient.close()
+ override fun close() {
+ httpClient.close()
+ sleeper.close()
+ }
private fun isRetryable(request: HttpRequest): Boolean =
// Some requests, such as when a request body is being streamed, cannot be retried because
@@ -176,9 +180,10 @@ private constructor(
}
private fun shouldRetry(throwable: Throwable): Boolean =
- // Only retry IOException and BraintrustIoException, other exceptions are not intended to be
- // retried.
- throwable is IOException || throwable is BraintrustIoException
+ // Only retry known retryable exceptions, other exceptions are not intended to be retried.
+ throwable is IOException ||
+ throwable is BraintrustIoException ||
+ throwable is BraintrustRetryableException
private fun getRetryBackoffDuration(retries: Int, response: HttpResponse?): Duration {
// About the Retry-After header:
@@ -233,33 +238,14 @@ private constructor(
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 sleeper: Sleeper? = null
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 sleeper(sleeper: Sleeper) = apply { this.sleeper = sleeper }
fun clock(clock: Clock) = apply { this.clock = clock }
@@ -270,17 +256,10 @@ private constructor(
fun build(): HttpClient =
RetryingHttpClient(
checkRequired("httpClient", httpClient),
- sleeper,
+ sleeper ?: DefaultSleeper(),
clock,
maxRetries,
idempotencyHeader,
)
}
-
- internal interface Sleeper {
-
- fun sleep(duration: Duration)
-
- fun sleepAsync(duration: Duration): CompletableFuture
- }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/StreamResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/StreamResponse.kt
new file mode 100644
index 00000000..3903400b
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/StreamResponse.kt
@@ -0,0 +1,19 @@
+package com.braintrustdata.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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustRetryableException.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustRetryableException.kt
new file mode 100644
index 00000000..e70380e9
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustRetryableException.kt
@@ -0,0 +1,15 @@
+package com.braintrustdata.api.errors
+
+/**
+ * Exception that indicates a transient error that can be retried.
+ *
+ * When this exception is thrown during an HTTP request, the SDK will automatically retry the
+ * request up to the maximum number of retries.
+ *
+ * @param message A descriptive error message
+ * @param cause The underlying cause of this exception, if any
+ */
+class BraintrustRetryableException
+@JvmOverloads
+constructor(message: String? = null, cause: Throwable? = null) :
+ BraintrustException(message, cause)
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AISecret.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AISecret.kt
index f5ad5a1b..a47ab007 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AISecret.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AISecret.kt
@@ -20,6 +20,7 @@ import java.util.Optional
import kotlin.jvm.optionals.getOrNull
class AISecret
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val id: JsonField,
private val name: JsonField,
@@ -507,12 +508,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Metadata && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Metadata && additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -524,12 +523,31 @@ private constructor(
return true
}
- return /* spotless:off */ other is AISecret && id == other.id && name == other.name && orgId == other.orgId && created == other.created && metadata == other.metadata && previewSecret == other.previewSecret && type == other.type && updatedAt == other.updatedAt && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is AISecret &&
+ id == other.id &&
+ name == other.name &&
+ orgId == other.orgId &&
+ created == other.created &&
+ metadata == other.metadata &&
+ previewSecret == other.previewSecret &&
+ type == other.type &&
+ updatedAt == other.updatedAt &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(id, name, orgId, created, metadata, previewSecret, type, updatedAt, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ id,
+ name,
+ orgId,
+ created,
+ metadata,
+ previewSecret,
+ type,
+ updatedAt,
+ additionalProperties,
+ )
+ }
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/Acl.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/Acl.kt
index 34941085..9175b110 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/Acl.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/Acl.kt
@@ -29,6 +29,7 @@ import kotlin.jvm.optionals.getOrNull
* ACL, as part of a direct permission grant or as part of a role.
*/
class Acl
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val id: JsonField,
private val _objectOrgId: JsonField,
@@ -553,12 +554,35 @@ private constructor(
return true
}
- return /* spotless:off */ other is Acl && id == other.id && _objectOrgId == other._objectOrgId && objectId == other.objectId && objectType == other.objectType && created == other.created && groupId == other.groupId && permission == other.permission && restrictObjectType == other.restrictObjectType && roleId == other.roleId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Acl &&
+ id == other.id &&
+ _objectOrgId == other._objectOrgId &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ created == other.created &&
+ groupId == other.groupId &&
+ permission == other.permission &&
+ restrictObjectType == other.restrictObjectType &&
+ roleId == other.roleId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(id, _objectOrgId, objectId, objectType, created, groupId, permission, restrictObjectType, roleId, userId, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ id,
+ _objectOrgId,
+ objectId,
+ objectType,
+ created,
+ groupId,
+ permission,
+ restrictObjectType,
+ roleId,
+ userId,
+ additionalProperties,
+ )
+ }
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateParams.kt
index 3276e25a..d8471677 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateParams.kt
@@ -79,8 +79,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -318,6 +320,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val addAcls: JsonField>,
private val removeAcls: JsonField>,
@@ -560,12 +563,15 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && addAcls == other.addAcls && removeAcls == other.removeAcls && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ addAcls == other.addAcls &&
+ removeAcls == other.removeAcls &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(addAcls, removeAcls, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(addAcls, removeAcls, additionalProperties)
+ }
override fun hashCode(): Int = hashCode
@@ -584,6 +590,7 @@ private constructor(
* ACL, as part of a direct permission grant or as part of a role.
*/
class AddAcl
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objectId: JsonField,
private val objectType: JsonField,
@@ -1004,12 +1011,29 @@ private constructor(
return true
}
- return /* spotless:off */ other is AddAcl && objectId == other.objectId && objectType == other.objectType && groupId == other.groupId && permission == other.permission && restrictObjectType == other.restrictObjectType && roleId == other.roleId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is AddAcl &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ groupId == other.groupId &&
+ permission == other.permission &&
+ restrictObjectType == other.restrictObjectType &&
+ roleId == other.roleId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(objectId, objectType, groupId, permission, restrictObjectType, roleId, userId, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ objectId,
+ objectType,
+ groupId,
+ permission,
+ restrictObjectType,
+ roleId,
+ userId,
+ additionalProperties,
+ )
+ }
override fun hashCode(): Int = hashCode
@@ -1028,6 +1052,7 @@ private constructor(
* ACL, as part of a direct permission grant or as part of a role.
*/
class RemoveAcl
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objectId: JsonField,
private val objectType: JsonField,
@@ -1448,12 +1473,29 @@ private constructor(
return true
}
- return /* spotless:off */ other is RemoveAcl && objectId == other.objectId && objectType == other.objectType && groupId == other.groupId && permission == other.permission && restrictObjectType == other.restrictObjectType && roleId == other.roleId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is RemoveAcl &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ groupId == other.groupId &&
+ permission == other.permission &&
+ restrictObjectType == other.restrictObjectType &&
+ roleId == other.roleId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(objectId, objectType, groupId, permission, restrictObjectType, roleId, userId, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ objectId,
+ objectType,
+ groupId,
+ permission,
+ restrictObjectType,
+ roleId,
+ userId,
+ additionalProperties,
+ )
+ }
override fun hashCode(): Int = hashCode
@@ -1466,10 +1508,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclBatchUpdateParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AclBatchUpdateParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AclBatchUpdateParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateResponse.kt
index 901575b1..a7429202 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateResponse.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclBatchUpdateResponse.kt
@@ -19,6 +19,7 @@ import java.util.Objects
import kotlin.jvm.optionals.getOrNull
class AclBatchUpdateResponse
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val addedAcls: JsonField>,
private val removedAcls: JsonField>,
@@ -266,12 +267,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclBatchUpdateResponse && addedAcls == other.addedAcls && removedAcls == other.removedAcls && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is AclBatchUpdateResponse &&
+ addedAcls == other.addedAcls &&
+ removedAcls == other.removedAcls &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(addedAcls, removedAcls, additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclCreateParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclCreateParams.kt
index 88196664..93d86eaf 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclCreateParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclCreateParams.kt
@@ -140,8 +140,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -452,6 +454,7 @@ private constructor(
* ACL, as part of a direct permission grant or as part of a role.
*/
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objectId: JsonField,
private val objectType: JsonField,
@@ -872,13 +875,30 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && objectId == other.objectId && objectType == other.objectType && groupId == other.groupId && permission == other.permission && restrictObjectType == other.restrictObjectType && roleId == other.roleId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ groupId == other.groupId &&
+ permission == other.permission &&
+ restrictObjectType == other.restrictObjectType &&
+ roleId == other.roleId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ objectId,
+ objectType,
+ groupId,
+ permission,
+ restrictObjectType,
+ roleId,
+ userId,
+ additionalProperties,
+ )
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(objectId, objectType, groupId, permission, restrictObjectType, roleId, userId, additionalProperties) }
- /* spotless:on */
-
override fun hashCode(): Int = hashCode
override fun toString() =
@@ -890,10 +910,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclCreateParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AclCreateParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AclCreateParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclDeleteParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclDeleteParams.kt
index 9b05873d..1e13b46a 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclDeleteParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclDeleteParams.kt
@@ -4,43 +4,41 @@ package com.braintrustdata.api.models
import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.toImmutable
import java.util.Objects
import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** Delete an acl object by its id */
class AclDeleteParams
private constructor(
- private val aclId: String,
+ private val aclId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
/** Acl id */
- fun aclId(): String = aclId
+ fun aclId(): Optional = Optional.ofNullable(aclId)
+ /** Additional body properties to send with the request. */
fun _additionalBodyProperties(): Map = additionalBodyProperties
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AclDeleteParams].
- *
- * The following fields are required:
- * ```java
- * .aclId()
- * ```
- */
+ @JvmStatic fun none(): AclDeleteParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AclDeleteParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -61,7 +59,10 @@ private constructor(
}
/** Acl id */
- fun aclId(aclId: String) = apply { this.aclId = aclId }
+ fun aclId(aclId: String?) = apply { this.aclId = aclId }
+
+ /** Alias for calling [Builder.aclId] with `aclId.orElse(null)`. */
+ fun aclId(aclId: Optional) = aclId(aclId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -187,17 +188,10 @@ private constructor(
* Returns an immutable instance of [AclDeleteParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .aclId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AclDeleteParams =
AclDeleteParams(
- checkRequired("aclId", aclId),
+ aclId,
additionalHeaders.build(),
additionalQueryParams.build(),
additionalBodyProperties.toImmutable(),
@@ -209,7 +203,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> aclId
+ 0 -> aclId ?: ""
else -> ""
}
@@ -222,10 +216,15 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclDeleteParams && aclId == other.aclId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams && additionalBodyProperties == other.additionalBodyProperties /* spotless:on */
+ return other is AclDeleteParams &&
+ aclId == other.aclId &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams &&
+ additionalBodyProperties == other.additionalBodyProperties
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aclId, additionalHeaders, additionalQueryParams, additionalBodyProperties) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(aclId, additionalHeaders, additionalQueryParams, additionalBodyProperties)
override fun toString() =
"AclDeleteParams{aclId=$aclId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclFindAndDeleteParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclFindAndDeleteParams.kt
index 05969c71..4241a5ff 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclFindAndDeleteParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclFindAndDeleteParams.kt
@@ -137,8 +137,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -453,6 +455,7 @@ private constructor(
* ACL, as part of a direct permission grant or as part of a role.
*/
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objectId: JsonField,
private val objectType: JsonField,
@@ -873,13 +876,30 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && objectId == other.objectId && objectType == other.objectType && groupId == other.groupId && permission == other.permission && restrictObjectType == other.restrictObjectType && roleId == other.roleId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ groupId == other.groupId &&
+ permission == other.permission &&
+ restrictObjectType == other.restrictObjectType &&
+ roleId == other.roleId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ objectId,
+ objectType,
+ groupId,
+ permission,
+ restrictObjectType,
+ roleId,
+ userId,
+ additionalProperties,
+ )
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(objectId, objectType, groupId, permission, restrictObjectType, roleId, userId, additionalProperties) }
- /* spotless:on */
-
override fun hashCode(): Int = hashCode
override fun toString() =
@@ -891,10 +911,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclFindAndDeleteParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AclFindAndDeleteParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AclFindAndDeleteParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPage.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPage.kt
index fd5ac29d..8938e4ed 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPage.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPage.kt
@@ -2,48 +2,42 @@
package com.braintrustdata.api.models
+import com.braintrustdata.api.core.AutoPager
+import com.braintrustdata.api.core.Page
import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.services.blocking.AclService
import java.util.Objects
-import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
-/** @see [AclService.list] */
+/** @see AclService.list */
class AclListPage
private constructor(
private val service: AclService,
private val params: AclListParams,
private val response: AclListPageResponse,
-) {
+) : Page {
/**
* Delegates to [AclListPageResponse], but gracefully handles missing data.
*
- * @see [AclListPageResponse.objects]
+ * @see AclListPageResponse.objects
*/
fun objects(): List = response._objects().getOptional("objects").getOrNull() ?: emptyList()
- fun hasNextPage(): Boolean = objects().isNotEmpty()
+ override fun items(): List = objects()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean = items().isNotEmpty()
- return Optional.of(
- if (params.endingBefore().isPresent) {
- params.toBuilder().endingBefore(objects().first()._id().getOptional("id")).build()
- } else {
- params.toBuilder().startingAfter(objects().last()._id().getOptional("id")).build()
- }
- )
- }
+ fun nextPageParams(): AclListParams =
+ if (params.endingBefore().isPresent) {
+ params.toBuilder().endingBefore(items().first()._id().getOptional("id")).build()
+ } else {
+ params.toBuilder().startingAfter(items().last()._id().getOptional("id")).build()
+ }
- fun getNextPage(): Optional = getNextPageParams().map { service.list(it) }
+ override fun nextPage(): AclListPage = 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(): AclListParams = params
@@ -112,34 +106,18 @@ private constructor(
)
}
- class AutoPager(private val firstPage: AclListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.objects().size) {
- yield(page.objects()[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
}
- return /* spotless:off */ other is AclListPage && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return other is AclListPage &&
+ service == other.service &&
+ params == other.params &&
+ response == other.response
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(service, params, response)
override fun toString() = "AclListPage{service=$service, params=$params, response=$response}"
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageAsync.kt
index 20160da8..def7bff5 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageAsync.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageAsync.kt
@@ -2,52 +2,45 @@
package com.braintrustdata.api.models
+import com.braintrustdata.api.core.AutoPagerAsync
+import com.braintrustdata.api.core.PageAsync
import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.services.async.AclServiceAsync
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 [AclServiceAsync.list] */
+/** @see AclServiceAsync.list */
class AclListPageAsync
private constructor(
private val service: AclServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: AclListParams,
private val response: AclListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [AclListPageResponse], but gracefully handles missing data.
*
- * @see [AclListPageResponse.objects]
+ * @see AclListPageResponse.objects
*/
fun objects(): List = response._objects().getOptional("objects").getOrNull() ?: emptyList()
- fun hasNextPage(): Boolean = objects().isNotEmpty()
+ override fun items(): List = objects()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean = items().isNotEmpty()
- return Optional.of(
- if (params.endingBefore().isPresent) {
- params.toBuilder().endingBefore(objects().first()._id().getOptional("id")).build()
- } else {
- params.toBuilder().startingAfter(objects().last()._id().getOptional("id")).build()
- }
- )
- }
+ fun nextPageParams(): AclListParams =
+ if (params.endingBefore().isPresent) {
+ params.toBuilder().endingBefore(items().first()._id().getOptional("id")).build()
+ } else {
+ params.toBuilder().startingAfter(items().last()._id().getOptional("id")).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(): AclListParams = params
@@ -65,6 +58,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -76,18 +70,24 @@ private constructor(
class Builder internal constructor() {
private var service: AclServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: AclListParams? = null
private var response: AclListPageResponse? = null
@JvmSynthetic
internal fun from(aclListPageAsync: AclListPageAsync) = apply {
service = aclListPageAsync.service
+ streamHandlerExecutor = aclListPageAsync.streamHandlerExecutor
params = aclListPageAsync.params
response = aclListPageAsync.response
}
fun service(service: AclServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: AclListParams) = apply { this.params = params }
@@ -102,6 +102,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -111,47 +112,26 @@ private constructor(
fun build(): AclListPageAsync =
AclListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: AclListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (Acl) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.objects().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 AclListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return other is AclListPageAsync &&
+ service == other.service &&
+ streamHandlerExecutor == other.streamHandlerExecutor &&
+ params == other.params &&
+ response == other.response
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(service, streamHandlerExecutor, params, response)
override fun toString() =
- "AclListPageAsync{service=$service, params=$params, response=$response}"
+ "AclListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageResponse.kt
index 63b51fd2..b1cc5192 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageResponse.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListPageResponse.kt
@@ -19,6 +19,7 @@ import java.util.Objects
import kotlin.jvm.optionals.getOrNull
class AclListPageResponse
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objects: JsonField>,
private val additionalProperties: MutableMap,
@@ -178,12 +179,12 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclListPageResponse && objects == other.objects && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is AclListPageResponse &&
+ objects == other.objects &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(objects, additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListParams.kt
index 238ae30f..d0fefc9b 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclListParams.kt
@@ -7,6 +7,7 @@ import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.getOrThrow
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
+import com.braintrustdata.api.core.toImmutable
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
@@ -60,8 +61,10 @@ private constructor(
*/
fun startingAfter(): Optional = Optional.ofNullable(startingAfter)
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -346,10 +349,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Ids && string == other.string && strings == other.strings /* spotless:on */
+ return other is Ids && string == other.string && strings == other.strings
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(string, strings) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(string, strings)
override fun toString(): String =
when {
@@ -362,7 +365,7 @@ private constructor(
@JvmStatic fun ofString(string: String) = Ids(string = string)
- @JvmStatic fun ofStrings(strings: List) = Ids(strings = strings)
+ @JvmStatic fun ofStrings(strings: List) = Ids(strings = strings.toImmutable())
}
/** An interface that defines how to map each variant of [Ids] to a value of type [T]. */
@@ -379,10 +382,28 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclListParams && objectId == other.objectId && objectType == other.objectType && endingBefore == other.endingBefore && ids == other.ids && limit == other.limit && startingAfter == other.startingAfter && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AclListParams &&
+ objectId == other.objectId &&
+ objectType == other.objectType &&
+ endingBefore == other.endingBefore &&
+ ids == other.ids &&
+ limit == other.limit &&
+ startingAfter == other.startingAfter &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(objectId, objectType, endingBefore, ids, limit, startingAfter, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(
+ objectId,
+ objectType,
+ endingBefore,
+ ids,
+ limit,
+ startingAfter,
+ additionalHeaders,
+ additionalQueryParams,
+ )
override fun toString() =
"AclListParams{objectId=$objectId, objectType=$objectType, endingBefore=$endingBefore, ids=$ids, limit=$limit, startingAfter=$startingAfter, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclObjectType.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclObjectType.kt
index 727accde..d70f1270 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclObjectType.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclObjectType.kt
@@ -179,7 +179,7 @@ class AclObjectType @JsonCreator private constructor(private val value: JsonFiel
return true
}
- return /* spotless:off */ other is AclObjectType && value == other.value /* spotless:on */
+ return other is AclObjectType && value == other.value
}
override fun hashCode() = value.hashCode()
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclRetrieveParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclRetrieveParams.kt
index ab43fccb..553c84c3 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclRetrieveParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AclRetrieveParams.kt
@@ -3,38 +3,36 @@
package com.braintrustdata.api.models
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** Get an acl object by its id */
class AclRetrieveParams
private constructor(
- private val aclId: String,
+ private val aclId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
/** Acl id */
- fun aclId(): String = aclId
+ fun aclId(): Optional = Optional.ofNullable(aclId)
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AclRetrieveParams].
- *
- * The following fields are required:
- * ```java
- * .aclId()
- * ```
- */
+ @JvmStatic fun none(): AclRetrieveParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AclRetrieveParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -53,7 +51,10 @@ private constructor(
}
/** Acl id */
- fun aclId(aclId: String) = apply { this.aclId = aclId }
+ fun aclId(aclId: String?) = apply { this.aclId = aclId }
+
+ /** Alias for calling [Builder.aclId] with `aclId.orElse(null)`. */
+ fun aclId(aclId: Optional) = aclId(aclId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -157,25 +158,14 @@ private constructor(
* Returns an immutable instance of [AclRetrieveParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .aclId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AclRetrieveParams =
- AclRetrieveParams(
- checkRequired("aclId", aclId),
- additionalHeaders.build(),
- additionalQueryParams.build(),
- )
+ AclRetrieveParams(aclId, additionalHeaders.build(), additionalQueryParams.build())
}
fun _pathParam(index: Int): String =
when (index) {
- 0 -> aclId
+ 0 -> aclId ?: ""
else -> ""
}
@@ -188,10 +178,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AclRetrieveParams && aclId == other.aclId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AclRetrieveParams &&
+ aclId == other.aclId &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aclId, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(aclId, additionalHeaders, additionalQueryParams)
override fun toString() =
"AclRetrieveParams{aclId=$aclId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretCreateParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretCreateParams.kt
index 6a4e98f4..0fa8596a 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretCreateParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretCreateParams.kt
@@ -108,8 +108,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -372,6 +374,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val name: JsonField,
private val metadata: JsonField,
@@ -672,12 +675,18 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && name == other.name && metadata == other.metadata && orgName == other.orgName && secret == other.secret && type == other.type && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ name == other.name &&
+ metadata == other.metadata &&
+ orgName == other.orgName &&
+ secret == other.secret &&
+ type == other.type &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(name, metadata, orgName, secret, type, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(name, metadata, orgName, secret, type, additionalProperties)
+ }
override fun hashCode(): Int = hashCode
@@ -774,12 +783,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Metadata && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Metadata && additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -791,10 +798,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretCreateParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretCreateParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AiSecretCreateParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretDeleteParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretDeleteParams.kt
index c0e7a074..61a67496 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretDeleteParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretDeleteParams.kt
@@ -4,43 +4,41 @@ package com.braintrustdata.api.models
import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.toImmutable
import java.util.Objects
import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** Delete an ai_secret object by its id */
class AiSecretDeleteParams
private constructor(
- private val aiSecretId: String,
+ private val aiSecretId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
/** AiSecret id */
- fun aiSecretId(): String = aiSecretId
+ fun aiSecretId(): Optional = Optional.ofNullable(aiSecretId)
+ /** Additional body properties to send with the request. */
fun _additionalBodyProperties(): Map = additionalBodyProperties
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AiSecretDeleteParams].
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- */
+ @JvmStatic fun none(): AiSecretDeleteParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AiSecretDeleteParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -61,7 +59,10 @@ private constructor(
}
/** AiSecret id */
- fun aiSecretId(aiSecretId: String) = apply { this.aiSecretId = aiSecretId }
+ fun aiSecretId(aiSecretId: String?) = apply { this.aiSecretId = aiSecretId }
+
+ /** Alias for calling [Builder.aiSecretId] with `aiSecretId.orElse(null)`. */
+ fun aiSecretId(aiSecretId: Optional) = aiSecretId(aiSecretId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -187,17 +188,10 @@ private constructor(
* Returns an immutable instance of [AiSecretDeleteParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AiSecretDeleteParams =
AiSecretDeleteParams(
- checkRequired("aiSecretId", aiSecretId),
+ aiSecretId,
additionalHeaders.build(),
additionalQueryParams.build(),
additionalBodyProperties.toImmutable(),
@@ -209,7 +203,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> aiSecretId
+ 0 -> aiSecretId ?: ""
else -> ""
}
@@ -222,10 +216,15 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretDeleteParams && aiSecretId == other.aiSecretId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams && additionalBodyProperties == other.additionalBodyProperties /* spotless:on */
+ return other is AiSecretDeleteParams &&
+ aiSecretId == other.aiSecretId &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams &&
+ additionalBodyProperties == other.additionalBodyProperties
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aiSecretId, additionalHeaders, additionalQueryParams, additionalBodyProperties) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(aiSecretId, additionalHeaders, additionalQueryParams, additionalBodyProperties)
override fun toString() =
"AiSecretDeleteParams{aiSecretId=$aiSecretId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretFindAndDeleteParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretFindAndDeleteParams.kt
index 83d28e3b..7e4aa36a 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretFindAndDeleteParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretFindAndDeleteParams.kt
@@ -62,8 +62,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -278,6 +280,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val name: JsonField,
private val orgName: JsonField,
@@ -462,12 +465,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && name == other.name && orgName == other.orgName && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ name == other.name &&
+ orgName == other.orgName &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(name, orgName, additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -480,10 +484,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretFindAndDeleteParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretFindAndDeleteParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AiSecretFindAndDeleteParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPage.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPage.kt
index 021f2341..b3de4d4f 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPage.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPage.kt
@@ -2,49 +2,43 @@
package com.braintrustdata.api.models
+import com.braintrustdata.api.core.AutoPager
+import com.braintrustdata.api.core.Page
import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.services.blocking.AiSecretService
import java.util.Objects
-import java.util.Optional
-import java.util.stream.Stream
-import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull
-/** @see [AiSecretService.list] */
+/** @see AiSecretService.list */
class AiSecretListPage
private constructor(
private val service: AiSecretService,
private val params: AiSecretListParams,
private val response: AiSecretListPageResponse,
-) {
+) : Page {
/**
* Delegates to [AiSecretListPageResponse], but gracefully handles missing data.
*
- * @see [AiSecretListPageResponse.objects]
+ * @see AiSecretListPageResponse.objects
*/
fun objects(): List =
response._objects().getOptional("objects").getOrNull() ?: emptyList()
- fun hasNextPage(): Boolean = objects().isNotEmpty()
+ override fun items(): List = objects()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean = items().isNotEmpty()
- return Optional.of(
- if (params.endingBefore().isPresent) {
- params.toBuilder().endingBefore(objects().first()._id().getOptional("id")).build()
- } else {
- params.toBuilder().startingAfter(objects().last()._id().getOptional("id")).build()
- }
- )
- }
+ fun nextPageParams(): AiSecretListParams =
+ if (params.endingBefore().isPresent) {
+ params.toBuilder().endingBefore(items().first()._id().getOptional("id")).build()
+ } else {
+ params.toBuilder().startingAfter(items().last()._id().getOptional("id")).build()
+ }
- fun getNextPage(): Optional = getNextPageParams().map { service.list(it) }
+ override fun nextPage(): AiSecretListPage = 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(): AiSecretListParams = params
@@ -113,34 +107,18 @@ private constructor(
)
}
- class AutoPager(private val firstPage: AiSecretListPage) : Iterable {
-
- override fun iterator(): Iterator = iterator {
- var page = firstPage
- var index = 0
- while (true) {
- while (index < page.objects().size) {
- yield(page.objects()[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
}
- return /* spotless:off */ other is AiSecretListPage && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return other is AiSecretListPage &&
+ service == other.service &&
+ params == other.params &&
+ response == other.response
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(service, params, response)
override fun toString() =
"AiSecretListPage{service=$service, params=$params, response=$response}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageAsync.kt
index 597dd9cd..535d9386 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageAsync.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageAsync.kt
@@ -2,53 +2,47 @@
package com.braintrustdata.api.models
+import com.braintrustdata.api.core.AutoPagerAsync
+import com.braintrustdata.api.core.PageAsync
import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.services.async.AiSecretServiceAsync
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 [AiSecretServiceAsync.list] */
+/** @see AiSecretServiceAsync.list */
class AiSecretListPageAsync
private constructor(
private val service: AiSecretServiceAsync,
+ private val streamHandlerExecutor: Executor,
private val params: AiSecretListParams,
private val response: AiSecretListPageResponse,
-) {
+) : PageAsync {
/**
* Delegates to [AiSecretListPageResponse], but gracefully handles missing data.
*
- * @see [AiSecretListPageResponse.objects]
+ * @see AiSecretListPageResponse.objects
*/
fun objects(): List =
response._objects().getOptional("objects").getOrNull() ?: emptyList()
- fun hasNextPage(): Boolean = objects().isNotEmpty()
+ override fun items(): List = objects()
- fun getNextPageParams(): Optional {
- if (!hasNextPage()) {
- return Optional.empty()
- }
+ override fun hasNextPage(): Boolean = items().isNotEmpty()
- return Optional.of(
- if (params.endingBefore().isPresent) {
- params.toBuilder().endingBefore(objects().first()._id().getOptional("id")).build()
- } else {
- params.toBuilder().startingAfter(objects().last()._id().getOptional("id")).build()
- }
- )
- }
+ fun nextPageParams(): AiSecretListParams =
+ if (params.endingBefore().isPresent) {
+ params.toBuilder().endingBefore(items().first()._id().getOptional("id")).build()
+ } else {
+ params.toBuilder().startingAfter(items().last()._id().getOptional("id")).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(): AiSecretListParams = params
@@ -66,6 +60,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -77,18 +72,24 @@ private constructor(
class Builder internal constructor() {
private var service: AiSecretServiceAsync? = null
+ private var streamHandlerExecutor: Executor? = null
private var params: AiSecretListParams? = null
private var response: AiSecretListPageResponse? = null
@JvmSynthetic
internal fun from(aiSecretListPageAsync: AiSecretListPageAsync) = apply {
service = aiSecretListPageAsync.service
+ streamHandlerExecutor = aiSecretListPageAsync.streamHandlerExecutor
params = aiSecretListPageAsync.params
response = aiSecretListPageAsync.response
}
fun service(service: AiSecretServiceAsync) = apply { this.service = service }
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor = streamHandlerExecutor
+ }
+
/** The parameters that were used to request this page. */
fun params(params: AiSecretListParams) = apply { this.params = params }
@@ -103,6 +104,7 @@ private constructor(
* The following fields are required:
* ```java
* .service()
+ * .streamHandlerExecutor()
* .params()
* .response()
* ```
@@ -112,47 +114,26 @@ private constructor(
fun build(): AiSecretListPageAsync =
AiSecretListPageAsync(
checkRequired("service", service),
+ checkRequired("streamHandlerExecutor", streamHandlerExecutor),
checkRequired("params", params),
checkRequired("response", response),
)
}
- class AutoPager(private val firstPage: AiSecretListPageAsync) {
-
- fun forEach(action: Predicate, executor: Executor): CompletableFuture {
- fun CompletableFuture>.forEach(
- action: (AISecret) -> Boolean,
- executor: Executor,
- ): CompletableFuture =
- thenComposeAsync(
- { page ->
- page
- .filter { it.objects().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 AiSecretListPageAsync && service == other.service && params == other.params && response == other.response /* spotless:on */
+ return other is AiSecretListPageAsync &&
+ service == other.service &&
+ streamHandlerExecutor == other.streamHandlerExecutor &&
+ params == other.params &&
+ response == other.response
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(service, params, response) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(service, streamHandlerExecutor, params, response)
override fun toString() =
- "AiSecretListPageAsync{service=$service, params=$params, response=$response}"
+ "AiSecretListPageAsync{service=$service, streamHandlerExecutor=$streamHandlerExecutor, params=$params, response=$response}"
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageResponse.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageResponse.kt
index 57a8dde7..5d469b49 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageResponse.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListPageResponse.kt
@@ -19,6 +19,7 @@ import java.util.Objects
import kotlin.jvm.optionals.getOrNull
class AiSecretListPageResponse
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val objects: JsonField>,
private val additionalProperties: MutableMap,
@@ -180,12 +181,12 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretListPageResponse && objects == other.objects && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is AiSecretListPageResponse &&
+ objects == other.objects &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(objects, additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListParams.kt
index dd7ef4bd..d57cd54f 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretListParams.kt
@@ -6,6 +6,7 @@ import com.braintrustdata.api.core.Params
import com.braintrustdata.api.core.getOrThrow
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
+import com.braintrustdata.api.core.toImmutable
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
@@ -62,8 +63,10 @@ private constructor(
*/
fun startingAfter(): Optional = Optional.ofNullable(startingAfter)
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -363,10 +366,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretType && string == other.string && strings == other.strings /* spotless:on */
+ return other is AiSecretType && string == other.string && strings == other.strings
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(string, strings) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(string, strings)
override fun toString(): String =
when {
@@ -379,7 +382,8 @@ private constructor(
@JvmStatic fun ofString(string: String) = AiSecretType(string = string)
- @JvmStatic fun ofStrings(strings: List) = AiSecretType(strings = strings)
+ @JvmStatic
+ fun ofStrings(strings: List) = AiSecretType(strings = strings.toImmutable())
}
/**
@@ -428,10 +432,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Ids && string == other.string && strings == other.strings /* spotless:on */
+ return other is Ids && string == other.string && strings == other.strings
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(string, strings) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(string, strings)
override fun toString(): String =
when {
@@ -444,7 +448,7 @@ private constructor(
@JvmStatic fun ofString(string: String) = Ids(string = string)
- @JvmStatic fun ofStrings(strings: List) = Ids(strings = strings)
+ @JvmStatic fun ofStrings(strings: List) = Ids(strings = strings.toImmutable())
}
/** An interface that defines how to map each variant of [Ids] to a value of type [T]. */
@@ -461,10 +465,30 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretListParams && aiSecretName == other.aiSecretName && aiSecretType == other.aiSecretType && endingBefore == other.endingBefore && ids == other.ids && limit == other.limit && orgName == other.orgName && startingAfter == other.startingAfter && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretListParams &&
+ aiSecretName == other.aiSecretName &&
+ aiSecretType == other.aiSecretType &&
+ endingBefore == other.endingBefore &&
+ ids == other.ids &&
+ limit == other.limit &&
+ orgName == other.orgName &&
+ startingAfter == other.startingAfter &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aiSecretName, aiSecretType, endingBefore, ids, limit, orgName, startingAfter, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(
+ aiSecretName,
+ aiSecretType,
+ endingBefore,
+ ids,
+ limit,
+ orgName,
+ startingAfter,
+ additionalHeaders,
+ additionalQueryParams,
+ )
override fun toString() =
"AiSecretListParams{aiSecretName=$aiSecretName, aiSecretType=$aiSecretType, endingBefore=$endingBefore, ids=$ids, limit=$limit, orgName=$orgName, startingAfter=$startingAfter, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretReplaceParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretReplaceParams.kt
index af588655..b73cb945 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretReplaceParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretReplaceParams.kt
@@ -108,8 +108,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -372,6 +374,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val name: JsonField,
private val metadata: JsonField,
@@ -672,12 +675,18 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && name == other.name && metadata == other.metadata && orgName == other.orgName && secret == other.secret && type == other.type && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ name == other.name &&
+ metadata == other.metadata &&
+ orgName == other.orgName &&
+ secret == other.secret &&
+ type == other.type &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(name, metadata, orgName, secret, type, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(name, metadata, orgName, secret, type, additionalProperties)
+ }
override fun hashCode(): Int = hashCode
@@ -774,12 +783,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Metadata && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Metadata && additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -791,10 +798,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretReplaceParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretReplaceParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AiSecretReplaceParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretRetrieveParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretRetrieveParams.kt
index 551f4d43..c52011db 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretRetrieveParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretRetrieveParams.kt
@@ -3,38 +3,36 @@
package com.braintrustdata.api.models
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** Get an ai_secret object by its id */
class AiSecretRetrieveParams
private constructor(
- private val aiSecretId: String,
+ private val aiSecretId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
/** AiSecret id */
- fun aiSecretId(): String = aiSecretId
+ fun aiSecretId(): Optional = Optional.ofNullable(aiSecretId)
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AiSecretRetrieveParams].
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- */
+ @JvmStatic fun none(): AiSecretRetrieveParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AiSecretRetrieveParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -53,7 +51,10 @@ private constructor(
}
/** AiSecret id */
- fun aiSecretId(aiSecretId: String) = apply { this.aiSecretId = aiSecretId }
+ fun aiSecretId(aiSecretId: String?) = apply { this.aiSecretId = aiSecretId }
+
+ /** Alias for calling [Builder.aiSecretId] with `aiSecretId.orElse(null)`. */
+ fun aiSecretId(aiSecretId: Optional) = aiSecretId(aiSecretId.getOrNull())
fun additionalHeaders(additionalHeaders: Headers) = apply {
this.additionalHeaders.clear()
@@ -157,17 +158,10 @@ private constructor(
* Returns an immutable instance of [AiSecretRetrieveParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AiSecretRetrieveParams =
AiSecretRetrieveParams(
- checkRequired("aiSecretId", aiSecretId),
+ aiSecretId,
additionalHeaders.build(),
additionalQueryParams.build(),
)
@@ -175,7 +169,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> aiSecretId
+ 0 -> aiSecretId ?: ""
else -> ""
}
@@ -188,10 +182,14 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretRetrieveParams && aiSecretId == other.aiSecretId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretRetrieveParams &&
+ aiSecretId == other.aiSecretId &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aiSecretId, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(aiSecretId, additionalHeaders, additionalQueryParams)
override fun toString() =
"AiSecretRetrieveParams{aiSecretId=$aiSecretId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretUpdateParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretUpdateParams.kt
index 4db720cf..6bd772e6 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretUpdateParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/AiSecretUpdateParams.kt
@@ -7,7 +7,6 @@ import com.braintrustdata.api.core.JsonField
import com.braintrustdata.api.core.JsonMissing
import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.toImmutable
@@ -28,14 +27,14 @@ import kotlin.jvm.optionals.getOrNull
*/
class AiSecretUpdateParams
private constructor(
- private val aiSecretId: String,
+ private val aiSecretId: String?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
/** AiSecret id */
- fun aiSecretId(): String = aiSecretId
+ fun aiSecretId(): Optional = Optional.ofNullable(aiSecretId)
/**
* @throws BraintrustInvalidDataException if the JSON field has an unexpected type (e.g. if the
@@ -93,22 +92,19 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
companion object {
- /**
- * Returns a mutable builder for constructing an instance of [AiSecretUpdateParams].
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- */
+ @JvmStatic fun none(): AiSecretUpdateParams = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [AiSecretUpdateParams]. */
@JvmStatic fun builder() = Builder()
}
@@ -129,7 +125,10 @@ private constructor(
}
/** AiSecret id */
- fun aiSecretId(aiSecretId: String) = apply { this.aiSecretId = aiSecretId }
+ fun aiSecretId(aiSecretId: String?) = apply { this.aiSecretId = aiSecretId }
+
+ /** Alias for calling [Builder.aiSecretId] with `aiSecretId.orElse(null)`. */
+ fun aiSecretId(aiSecretId: Optional) = aiSecretId(aiSecretId.getOrNull())
/**
* Sets the entire request body.
@@ -318,17 +317,10 @@ private constructor(
* Returns an immutable instance of [AiSecretUpdateParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
- *
- * The following fields are required:
- * ```java
- * .aiSecretId()
- * ```
- *
- * @throws IllegalStateException if any required field is unset.
*/
fun build(): AiSecretUpdateParams =
AiSecretUpdateParams(
- checkRequired("aiSecretId", aiSecretId),
+ aiSecretId,
body.build(),
additionalHeaders.build(),
additionalQueryParams.build(),
@@ -339,7 +331,7 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> aiSecretId
+ 0 -> aiSecretId ?: ""
else -> ""
}
@@ -348,6 +340,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val metadata: JsonField,
private val name: JsonField,
@@ -581,12 +574,17 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && metadata == other.metadata && name == other.name && secret == other.secret && type == other.type && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ metadata == other.metadata &&
+ name == other.name &&
+ secret == other.secret &&
+ type == other.type &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(metadata, name, secret, type, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(metadata, name, secret, type, additionalProperties)
+ }
override fun hashCode(): Int = hashCode
@@ -683,12 +681,10 @@ private constructor(
return true
}
- return /* spotless:off */ other is Metadata && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Metadata && additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -700,10 +696,15 @@ private constructor(
return true
}
- return /* spotless:off */ other is AiSecretUpdateParams && aiSecretId == other.aiSecretId && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is AiSecretUpdateParams &&
+ aiSecretId == other.aiSecretId &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(aiSecretId, body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int =
+ Objects.hash(aiSecretId, body, additionalHeaders, additionalQueryParams)
override fun toString() =
"AiSecretUpdateParams{aiSecretId=$aiSecretId, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKey.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKey.kt
index 17e11b99..4d05c260 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKey.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKey.kt
@@ -19,6 +19,7 @@ import java.util.Optional
import kotlin.jvm.optionals.getOrNull
class ApiKey
+@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val id: JsonField,
private val name: JsonField,
@@ -346,12 +347,19 @@ private constructor(
return true
}
- return /* spotless:off */ other is ApiKey && id == other.id && name == other.name && previewName == other.previewName && created == other.created && orgId == other.orgId && userId == other.userId && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is ApiKey &&
+ id == other.id &&
+ name == other.name &&
+ previewName == other.previewName &&
+ created == other.created &&
+ orgId == other.orgId &&
+ userId == other.userId &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
- private val hashCode: Int by lazy { Objects.hash(id, name, previewName, created, orgId, userId, additionalProperties) }
- /* spotless:on */
+ private val hashCode: Int by lazy {
+ Objects.hash(id, name, previewName, created, orgId, userId, additionalProperties)
+ }
override fun hashCode(): Int = hashCode
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyCreateParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyCreateParams.kt
index 236d29be..5d6c5394 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyCreateParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyCreateParams.kt
@@ -65,8 +65,10 @@ private constructor(
fun _additionalBodyProperties(): Map = body._additionalProperties()
+ /** Additional headers to send with the request. */
fun _additionalHeaders(): Headers = additionalHeaders
+ /** Additional query param to send with the request. */
fun _additionalQueryParams(): QueryParams = additionalQueryParams
fun toBuilder() = Builder().from(this)
@@ -281,6 +283,7 @@ private constructor(
override fun _queryParams(): QueryParams = additionalQueryParams
class Body
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val name: JsonField,
private val orgName: JsonField,
@@ -465,12 +468,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is Body && name == other.name && orgName == other.orgName && additionalProperties == other.additionalProperties /* spotless:on */
+ return other is Body &&
+ name == other.name &&
+ orgName == other.orgName &&
+ additionalProperties == other.additionalProperties
}
- /* spotless:off */
private val hashCode: Int by lazy { Objects.hash(name, orgName, additionalProperties) }
- /* spotless:on */
override fun hashCode(): Int = hashCode
@@ -483,10 +487,13 @@ private constructor(
return true
}
- return /* spotless:off */ other is ApiKeyCreateParams && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams /* spotless:on */
+ return other is ApiKeyCreateParams &&
+ body == other.body &&
+ additionalHeaders == other.additionalHeaders &&
+ additionalQueryParams == other.additionalQueryParams
}
- override fun hashCode(): Int = /* spotless:off */ Objects.hash(body, additionalHeaders, additionalQueryParams) /* spotless:on */
+ override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams)
override fun toString() =
"ApiKeyCreateParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyDeleteParams.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyDeleteParams.kt
index 908060c2..f3add49a 100644
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyDeleteParams.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/models/ApiKeyDeleteParams.kt
@@ -4,43 +4,41 @@ package com.braintrustdata.api.models
import com.braintrustdata.api.core.JsonValue
import com.braintrustdata.api.core.Params
-import com.braintrustdata.api.core.checkRequired
import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.toImmutable
import java.util.Objects
import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
/** Delete an api_key object by its id */
class ApiKeyDeleteParams
private constructor(
- private val apiKeyId: String,
+ private val apiKeyId: String?,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
private val additionalBodyProperties: Map,
) : Params {
/** ApiKey id */
- fun apiKeyId(): String = apiKeyId
+ fun apiKeyId(): Optional = Optional.ofNullable(apiKeyId)
+ /** Additional body properties to send with the request. */
fun _additionalBodyProperties(): Map