diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6f4da1620..15d08fb0c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,14 @@
name: CI
on:
push:
- branches-ignore:
- - 'generated'
- - 'codegen/**'
- - 'integrated/**'
- - 'stl-preview-head/**'
- - 'stl-preview-base/**'
+ branches:
+ - '**'
+ - '!integrated/**'
+ - '!stl-preview-head/**'
+ - '!stl-preview-base/**'
+ - '!generated'
+ - '!codegen/**'
+ - 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
@@ -17,13 +19,13 @@ jobs:
timeout-minutes: 15
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/finch-kotlin' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
- if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+ if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -32,7 +34,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/actions/setup-gradle@v4
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Run lints
run: ./scripts/lint
@@ -41,13 +43,13 @@ jobs:
timeout-minutes: 15
name: build
runs-on: ${{ github.repository == 'stainless-sdks/finch-kotlin' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
- if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+ if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -56,7 +58,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/actions/setup-gradle@v4
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Build SDK
run: ./scripts/build
@@ -67,10 +69,10 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/finch-kotlin' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -79,7 +81,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@v2
+ uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Run tests
run: ./scripts/test
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index 95784bb89..37536c060 100644
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -26,7 +26,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@v2
+ uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Publish to Sonatype
run: |-
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 6c579344d..6c37fc1ce 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'Finch-API/finch-api-kotlin' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check release environment
run: |
diff --git a/.gitignore b/.gitignore
index b1346e6d1..90b85e944 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.prism.log
+.stdy.log
.gradle
.idea
.kotlin
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index d4d002896..c1579bf95 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "8.5.0"
-}
\ No newline at end of file
+ ".": "8.6.0"
+}
diff --git a/.stats.yml b/.stats.yml
index 5157ee799..0092e6cf2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 45
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-46f433f34d440aa1dfcc48cc8d822c598571b68be2f723ec99e1b4fba6c13b1e.yml
-openapi_spec_hash: 5b5cd728776723ac773900f7e8a32c05
-config_hash: ccdf6a5b4aaa2a0897c89ac8685d8eb0
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch/finch-f17b0106d5004cf7339990dd9a9d60dc183ed38051a862db3855fb9d953fbbf8.yml
+openapi_spec_hash: a4ca94b3405fc83934c949068943e16c
+config_hash: ae896fec93ebd7c3bc1a6c4d6d9880ff
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e3e79c72..46999a256 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,85 +1,48 @@
# Changelog
-## 8.5.0 (2026-01-26)
-
-Full Changelog: [v8.4.0...v8.5.0](https://github.com/Finch-API/finch-api-kotlin/compare/v8.4.0...v8.5.0)
-
-### Features
-
-* **api:** add per endpoint security ([bab4226](https://github.com/Finch-API/finch-api-kotlin/commit/bab4226f0320f7eb27e01755df8986b1ccb97a21))
-* **api:** api update ([1ee94b3](https://github.com/Finch-API/finch-api-kotlin/commit/1ee94b33a7468ecfc84de190aa7b8c1be29edb59))
-* **api:** api update ([431f019](https://github.com/Finch-API/finch-api-kotlin/commit/431f019dde55dc5627c161c3cc70d6d14de72948))
-* **api:** api update ([3b4474b](https://github.com/Finch-API/finch-api-kotlin/commit/3b4474b7eff1be05ccacf8ece21ea4b34acd467f))
-* **api:** api update ([e5ba731](https://github.com/Finch-API/finch-api-kotlin/commit/e5ba731613d08e8894ce42f2e99d5653f496c630))
-* **api:** api update ([36ecf7a](https://github.com/Finch-API/finch-api-kotlin/commit/36ecf7a6b2789d6bcd0a0e6bb313221fd38b8fcd))
-* **api:** api update ([c92c774](https://github.com/Finch-API/finch-api-kotlin/commit/c92c774b934a5ab6db27a906e1840de9f151f9b1))
-* **api:** api update ([aa03039](https://github.com/Finch-API/finch-api-kotlin/commit/aa030391bf8a9b74f0c76ad87f4334656b7055dd))
-* **api:** api update ([f1e18b8](https://github.com/Finch-API/finch-api-kotlin/commit/f1e18b8d13b096434e1cdf7f5624c251492b4f11))
-* **api:** api update ([285aa30](https://github.com/Finch-API/finch-api-kotlin/commit/285aa306aecee27dff1b0c8d1a55a5779f9be342))
-* **api:** api update ([ff22d83](https://github.com/Finch-API/finch-api-kotlin/commit/ff22d835809078666e8e0fb50298869925a06279))
-* **api:** api update ([fa48e8f](https://github.com/Finch-API/finch-api-kotlin/commit/fa48e8f5a940d34d4ad951ff7ba62902d0b88568))
-* **api:** api update ([c1d8819](https://github.com/Finch-API/finch-api-kotlin/commit/c1d881999d64f0b925d923c2011ce77ae59bd09d))
-* **api:** api update ([271ad83](https://github.com/Finch-API/finch-api-kotlin/commit/271ad835c0e5094987968000fd8e88a948359d00))
-* **api:** api update ([71b5b03](https://github.com/Finch-API/finch-api-kotlin/commit/71b5b03244c1b7c93780b68d118be363fa124ddb))
-* **api:** api update ([3a162ea](https://github.com/Finch-API/finch-api-kotlin/commit/3a162ea7663bb333eb618091be53864dd88eda92))
-* **api:** api update ([32309ae](https://github.com/Finch-API/finch-api-kotlin/commit/32309aefb7eba23e5ab8a32001566dc0d2b72c1a))
-* **api:** make client id, client secret optional again ([13bf198](https://github.com/Finch-API/finch-api-kotlin/commit/13bf19897304e4e85662e0e738b63094a83c9b03))
-* **api:** manual updates ([8d8b926](https://github.com/Finch-API/finch-api-kotlin/commit/8d8b9260ba4a9b944efb4c646c547c362a7a210e))
-* **api:** update automated code reviewer selection ([788cfd9](https://github.com/Finch-API/finch-api-kotlin/commit/788cfd9c1a2f2b0425e966819b8cbe1f331d6129))
-* **client:** add `HttpRequest#url()` method ([15645e8](https://github.com/Finch-API/finch-api-kotlin/commit/15645e8c7a3447c727b53f894f64d5f9465b5c74))
-* **client:** add convenience overloads for some methods ([0614133](https://github.com/Finch-API/finch-api-kotlin/commit/0614133eb181ef87faa43dd92fdd8d22e482212b))
-* **client:** allow configuring dispatcher executor service ([9c2fe2e](https://github.com/Finch-API/finch-api-kotlin/commit/9c2fe2ea7d4af1608fa72a6a5f18778fab691b86))
-* **client:** expose sleeper option ([27e831e](https://github.com/Finch-API/finch-api-kotlin/commit/27e831e751e2266402ae51ba79e3ff31389760e9))
-* **client:** send `X-Stainless-Kotlin-Version` header ([f64f1c1](https://github.com/Finch-API/finch-api-kotlin/commit/f64f1c12afe6dcc56d3a904fbf4b68ed549a2ca7))
+## 8.6.0 (2026-05-12)
+
+Full Changelog: [v8.5.0...v8.6.0](https://github.com/Finch-API/finch-api-kotlin/compare/v8.5.0...v8.6.0)
+
+### Features
+
+* **api:** api update ([68ca045](https://github.com/Finch-API/finch-api-kotlin/commit/68ca045eaca1cd98b060e2c8e7f57bba29d2c68c))
+* **api:** api update ([9bab9c1](https://github.com/Finch-API/finch-api-kotlin/commit/9bab9c1f05bf4d57902d0a0e6ffbed87aa86e173))
+* **api:** api update ([4c05803](https://github.com/Finch-API/finch-api-kotlin/commit/4c058036a13c1a1ec21d6170b16bd15981f14f12))
+* **api:** api update ([f3241e4](https://github.com/Finch-API/finch-api-kotlin/commit/f3241e4931b673c91c26247a724bc9954ef1c59e))
+* **api:** api update ([daa74ec](https://github.com/Finch-API/finch-api-kotlin/commit/daa74ec575c45e93fcf046d6d5a0793306a9dfd1))
+* **api:** manual updates ([7d9ea64](https://github.com/Finch-API/finch-api-kotlin/commit/7d9ea64ef7397e88731c28dd1fdefb678a4a0974))
+* **client:** improve logging ([5807a19](https://github.com/Finch-API/finch-api-kotlin/commit/5807a19bcadfd042d7576642e622a9f0c7a38e5c))
+* **client:** more robust error parsing ([d8a4f7f](https://github.com/Finch-API/finch-api-kotlin/commit/d8a4f7fa40a0528a5ea4ee9f58f58c31df42d8e2))
+* **client:** support proxy authentication ([8319ffe](https://github.com/Finch-API/finch-api-kotlin/commit/8319ffeaf852fef42e97029528618701e4db7a0d))
+* support setting headers via env ([408296c](https://github.com/Finch-API/finch-api-kotlin/commit/408296c2c52902bf1cbf98fb083e164301e6555e))
### Bug Fixes
-* **ci:** use java-version 21 for publish step ([782e222](https://github.com/Finch-API/finch-api-kotlin/commit/782e222a460ed56f6e065e49bae5b1eff365d8c1))
-* **client:** add missing additional properties fields ([28d6a59](https://github.com/Finch-API/finch-api-kotlin/commit/28d6a59036cab616a350fe58c897d5c94bafbc47))
-* **client:** deserialization of empty objects ([c276a03](https://github.com/Finch-API/finch-api-kotlin/commit/c276a033104f54f76b275cc6ad43d34cfd27142d))
-* **client:** disallow coercion from float to int ([1ddc864](https://github.com/Finch-API/finch-api-kotlin/commit/1ddc864981aea3576dd138953b4ecf83d7bc0bcd))
-* **client:** ensure single timer is created per client ([27e831e](https://github.com/Finch-API/finch-api-kotlin/commit/27e831e751e2266402ae51ba79e3ff31389760e9))
-* **client:** fully respect max retries ([bcd5f4f](https://github.com/Finch-API/finch-api-kotlin/commit/bcd5f4f6e5a8b3736683ddfe8f0210a39e131862))
-* **client:** incorrect `getPackageVersion` impl ([f83aef2](https://github.com/Finch-API/finch-api-kotlin/commit/f83aef2bf5126f9e507c2196986207e6ca818a52))
-* **client:** multi-value header serialization ([13dfda2](https://github.com/Finch-API/finch-api-kotlin/commit/13dfda2a4e91bb6602c841eea820e48b769d7071))
-* **client:** preserve time zone in lenient date-time parsing ([6576ead](https://github.com/Finch-API/finch-api-kotlin/commit/6576ead83862d15dc1ee7e1a1e9a13eac6527685))
-* **client:** send retry count header for max retries 0 ([bcd5f4f](https://github.com/Finch-API/finch-api-kotlin/commit/bcd5f4f6e5a8b3736683ddfe8f0210a39e131862))
-* date time deserialization leniency ([0b080ba](https://github.com/Finch-API/finch-api-kotlin/commit/0b080badd55d24872e239e3fcd57b5710c6b3e98))
-* **java:** Resolve name collisions ([f10d902](https://github.com/Finch-API/finch-api-kotlin/commit/f10d9029128b8b03fbb42175df6d73b8a81f1291))
-* **tests:** skip broken date validation test ([8f1f92b](https://github.com/Finch-API/finch-api-kotlin/commit/8f1f92bf649633f3136b738c27e60756183325c7))
-
-
-### Chores
-
-* **ci:** upgrade `actions/setup-java` ([2f2ddfb](https://github.com/Finch-API/finch-api-kotlin/commit/2f2ddfbda64f35338a015293d526a812efec7208))
-* **internal:** change some comment formatting ([a6e3883](https://github.com/Finch-API/finch-api-kotlin/commit/a6e38837435deebe5913f14fa682298ea659bd1c))
-* **internal:** codegen related update ([23d76a6](https://github.com/Finch-API/finch-api-kotlin/commit/23d76a65ec6db36218d7d6e3e17747d80bad4259))
-* **internal:** codegen related update ([ffb4a15](https://github.com/Finch-API/finch-api-kotlin/commit/ffb4a1577e9f70c2effb85c043ac2747ab1b1949))
-* **internal:** codegen related update ([33faa66](https://github.com/Finch-API/finch-api-kotlin/commit/33faa662907715dd02b38de5ce6a3c98a09a538c))
-* **internal:** codegen related update ([f85811f](https://github.com/Finch-API/finch-api-kotlin/commit/f85811f0aec92f75bd7758c739003d26102750e1))
-* **internal:** codegen related update ([298ae24](https://github.com/Finch-API/finch-api-kotlin/commit/298ae24e0bd4da71847a39e917ed9e91523593bd))
-* **internal:** codegen related update ([b7d4aee](https://github.com/Finch-API/finch-api-kotlin/commit/b7d4aeeed44b7ecd05f3cbd200957b7295cb6b7d))
-* **internal:** codegen related update ([418c4c1](https://github.com/Finch-API/finch-api-kotlin/commit/418c4c1a93f37c0e4164c6a5afee683cbcab37f0))
-* **internal:** codegen related update ([abf9113](https://github.com/Finch-API/finch-api-kotlin/commit/abf91139b1f5a437b1e89bd4d40c5ed75f5f637c))
-* **internal:** correct cache invalidation for `SKIP_MOCK_TESTS` ([01ee1df](https://github.com/Finch-API/finch-api-kotlin/commit/01ee1df70eeb6c803bc1e7bc4330d198588e762d))
-* **internal:** depend on packages directly in example ([bcd5f4f](https://github.com/Finch-API/finch-api-kotlin/commit/bcd5f4f6e5a8b3736683ddfe8f0210a39e131862))
-* **internal:** refactor build files to support future stainless package uploads ([45ec3b4](https://github.com/Finch-API/finch-api-kotlin/commit/45ec3b45fe815bc5399e3083a55621c95dbefe87))
-* **internal:** update `actions/checkout` version ([92160db](https://github.com/Finch-API/finch-api-kotlin/commit/92160db8358a167fb656ca4fd09287aaec357152))
-* **ownership:** move from data to platform team ([#484](https://github.com/Finch-API/finch-api-kotlin/issues/484)) ([476cc47](https://github.com/Finch-API/finch-api-kotlin/commit/476cc4706fe9c6c18cdcc333bbbd62fd823aecf6))
-* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/Finch-API/finch-api-kotlin/issues/3240) in tests ([0b080ba](https://github.com/Finch-API/finch-api-kotlin/commit/0b080badd55d24872e239e3fcd57b5710c6b3e98))
+* restore SecurityOptions.kt referenced by ClientOptions.securityHeaders ([ddebddb](https://github.com/Finch-API/finch-api-kotlin/commit/ddebddbf24a7c9dd108c60e85cf5089654f25007))
-### Documentation
+### Performance Improvements
-* add comment for arbitrary value fields ([b4da983](https://github.com/Finch-API/finch-api-kotlin/commit/b4da983857d7a502fd2a35dfcfaf595df7a8b252))
-* prominently feature MCP server setup in root SDK readmes ([2e1c563](https://github.com/Finch-API/finch-api-kotlin/commit/2e1c5631df877ce99a6bdf8f9bed8f8a35a2f5cc))
-* remove `$` for better copy-pasteabality ([52e7c9f](https://github.com/Finch-API/finch-api-kotlin/commit/52e7c9f32d345860fe7c67a16e9ddd9423af777a))
+* **client:** create one json mapper ([d4ebc8d](https://github.com/Finch-API/finch-api-kotlin/commit/d4ebc8d84e40457b0110922479957a8df231c083))
-### Refactors
+### Chores
+
+* clean up custom code ([77494f4](https://github.com/Finch-API/finch-api-kotlin/commit/77494f41add0f7ea1f02419faf4c3efdb40d08b9))
+* **internal:** codegen related update ([95796d3](https://github.com/Finch-API/finch-api-kotlin/commit/95796d31c412bb7edb00b2bfd17869d77e6e5f97))
+* **internal:** codegen related update ([31cb838](https://github.com/Finch-API/finch-api-kotlin/commit/31cb838300ab36e645178a52b0eff3365d1f1853))
+* **internal:** codegen related update ([a28b92c](https://github.com/Finch-API/finch-api-kotlin/commit/a28b92c515f5b916d9ed66d29f2d90455c7c1b57))
+* **internal:** codegen related update ([5d33b36](https://github.com/Finch-API/finch-api-kotlin/commit/5d33b36689e4a717f06d7801158887ed3e206019))
+* redact api-key headers in debug logs ([087aaa3](https://github.com/Finch-API/finch-api-kotlin/commit/087aaa33dec17b7e82be861d1642c91554c38d8e))
+* remove duplicated dokka setup ([bc065c6](https://github.com/Finch-API/finch-api-kotlin/commit/bc065c602f93d047c1866431c8d616e12724b5c6))
+* update version in release manifest ([84c0231](https://github.com/Finch-API/finch-api-kotlin/commit/84c02313aab25f7074df39d714432ffce070ac77))
+
+
+### Documentation
-* **client:** handling of absent pagination total ([cf02f7b](https://github.com/Finch-API/finch-api-kotlin/commit/cf02f7b157c95c9fb5cacb3b49b0ec74cb099b01))
+* clarify forwards compat behavior ([9425d5a](https://github.com/Finch-API/finch-api-kotlin/commit/9425d5aa40f6ce623858e030a3fbffd6ae8d720a))
## 8.4.0 (2025-08-25)
diff --git a/README.md b/README.md
index dd45525e5..e8a6cf853 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.tryfinch.api/finch-kotlin/8.5.0)
-[](https://javadoc.io/doc/com.tryfinch.api/finch-kotlin/8.5.0)
+[](https://central.sonatype.com/artifact/com.tryfinch.api/finch-kotlin/8.6.0)
+[](https://javadoc.io/doc/com.tryfinch.api/finch-kotlin/8.6.0)
@@ -17,14 +17,14 @@ It is generated with [Stainless](https://www.stainless.com/).
Use the Finch MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
-[](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl19)
-[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%7D)
+[](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJNeSBBY2Nlc3MgVG9rZW4iLCJGSU5DSF9DTElFTlRfSUQiOiI0YWIxNWU1MS0xMWFkLTQ5ZjQtYWNhZS1mMzQzYjc3OTQzNzUiLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiTXkgQ2xpZW50IFNlY3JldCIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiTXkgV2ViaG9vayBTZWNyZXQifX0)
+[](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%2C%22env%22%3A%7B%22FINCH_ACCESS_TOKEN%22%3A%22My%20Access%20Token%22%2C%22FINCH_CLIENT_ID%22%3A%224ab15e51-11ad-49f4-acae-f343b7794375%22%2C%22FINCH_CLIENT_SECRET%22%3A%22My%20Client%20Secret%22%2C%22FINCH_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Secret%22%7D%7D)
> Note: You may need to set environment variables in your MCP client.
-The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). KDocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-kotlin/8.5.0).
+The REST API documentation can be found on [developer.tryfinch.com](https://developer.tryfinch.com/). KDocs are available on [javadoc.io](https://javadoc.io/doc/com.tryfinch.api/finch-kotlin/8.6.0).
@@ -35,7 +35,7 @@ The REST API documentation can be found on [developer.tryfinch.com](https://deve
### Gradle
```kotlin
-implementation("com.tryfinch.api:finch-kotlin:8.5.0")
+implementation("com.tryfinch.api:finch-kotlin:8.6.0")
```
### Maven
@@ -44,7 +44,7 @@ implementation("com.tryfinch.api:finch-kotlin:8.5.0")
com.tryfinch.api
finch-kotlin
- 8.5.0
+ 8.6.0
```
@@ -303,8 +303,6 @@ while (true) {
## Logging
-The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
-
Enable logging by setting the `FINCH_LOG` environment variable to `info`:
```sh
@@ -317,6 +315,20 @@ Or to `debug` for more verbose logging:
export FINCH_LOG=debug
```
+Or configure the client manually using the `logLevel` method:
+
+```kotlin
+import com.tryfinch.api.client.FinchClient
+import com.tryfinch.api.client.okhttp.FinchOkHttpClient
+import com.tryfinch.api.core.LogLevel
+
+val client: FinchClient = FinchOkHttpClient.builder()
+ .fromEnv()
+ .logLevel(LogLevel.INFO)
+ .accessToken("My Access Token")
+ .build()
+```
+
## Webhook Verification
We provide helper methods for verifying that a webhook request came from Finch, and not a malicious third party.
@@ -422,6 +434,42 @@ val client: FinchClient = FinchOkHttpClient.builder()
.build()
```
+If the proxy responds with `407 Proxy Authentication Required`, supply credentials by also configuring `proxyAuthenticator`:
+
+```kotlin
+import com.tryfinch.api.client.FinchClient
+import com.tryfinch.api.client.okhttp.FinchOkHttpClient
+import com.tryfinch.api.core.http.ProxyAuthenticator
+
+val client: FinchClient = FinchOkHttpClient.builder()
+ .fromEnv()
+ .proxy(...)
+ // Or a custom implementation of `ProxyAuthenticator`.
+ .proxyAuthenticator(ProxyAuthenticator.basic("username", "password"))
+ .accessToken("My Access Token")
+ .build()
+```
+
+### Connection pooling
+
+To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods:
+
+```kotlin
+import com.tryfinch.api.client.FinchClient
+import com.tryfinch.api.client.okhttp.FinchOkHttpClient
+import java.time.Duration
+
+val client: FinchClient = FinchOkHttpClient.builder()
+ .fromEnv()
+ // If `maxIdleConnections` is set, then `keepAliveDuration` must be set, and vice versa.
+ .maxIdleConnections(10)
+ .keepAliveDuration(Duration.ofMinutes(2))
+ .accessToken("My Access Token")
+ .build()
+```
+
+If both options are unset, OkHttp's default connection pool settings are used.
+
### HTTPS
> [!NOTE]
@@ -607,7 +655,9 @@ In rare cases, the API may return a response that doesn't match the expected typ
By default, the SDK will not throw an exception in this case. It will throw [`FinchInvalidDataException`](finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/FinchInvalidDataException.kt) only if you directly access the property.
-If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`:
+Validating the response is _not_ forwards compatible with new types from the API for existing fields.
+
+If you would still prefer to check that the response is completely well-typed upfront, then either call `validate()`:
```kotlin
import com.tryfinch.api.models.CreateAccessTokenResponse
diff --git a/build.gradle.kts b/build.gradle.kts
index d1d0bd6f9..2d52fc075 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.tryfinch.api"
- version = "8.5.0" // x-release-please-version
+ version = "8.6.0" // x-release-please-version
}
subprojects {
@@ -21,7 +21,6 @@ subprojects {
group = "Verification"
description = "Verifies all source files are formatted."
}
- apply(plugin = "org.jetbrains.dokka")
}
subprojects {
diff --git a/buildSrc/src/main/kotlin/finch.java.gradle.kts b/buildSrc/src/main/kotlin/finch.java.gradle.kts
index 70fc33f41..8f4f902a6 100644
--- a/buildSrc/src/main/kotlin/finch.java.gradle.kts
+++ b/buildSrc/src/main/kotlin/finch.java.gradle.kts
@@ -45,7 +45,7 @@ tasks.withType().configureEach {
val palantir by configurations.creating
dependencies {
- palantir("com.palantir.javaformat:palantir-java-format:2.73.0")
+ palantir("com.palantir.javaformat:palantir-java-format:2.89.0")
}
fun registerPalantir(
diff --git a/buildSrc/src/main/kotlin/finch.kotlin.gradle.kts b/buildSrc/src/main/kotlin/finch.kotlin.gradle.kts
index 460dbc151..0b43c8cf3 100644
--- a/buildSrc/src/main/kotlin/finch.kotlin.gradle.kts
+++ b/buildSrc/src/main/kotlin/finch.kotlin.gradle.kts
@@ -40,7 +40,7 @@ tasks.withType().configureEach {
val ktfmt by configurations.creating
dependencies {
- ktfmt("com.facebook:ktfmt:0.56")
+ ktfmt("com.facebook:ktfmt:0.61")
}
fun registerKtfmt(
diff --git a/finch-kotlin-client-okhttp/build.gradle.kts b/finch-kotlin-client-okhttp/build.gradle.kts
index f96f0463f..dd7b2bcf8 100644
--- a/finch-kotlin-client-okhttp/build.gradle.kts
+++ b/finch-kotlin-client-okhttp/build.gradle.kts
@@ -7,9 +7,8 @@ dependencies {
api(project(":finch-kotlin-core"))
implementation("com.squareup.okhttp3:okhttp:4.12.0")
- implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
testImplementation(kotlin("test"))
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
}
diff --git a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
index 322ba7e1a..000be98b3 100644
--- a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
+++ b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.kt
@@ -6,10 +6,12 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.tryfinch.api.client.FinchClient
import com.tryfinch.api.client.FinchClientImpl
import com.tryfinch.api.core.ClientOptions
+import com.tryfinch.api.core.LogLevel
import com.tryfinch.api.core.Sleeper
import com.tryfinch.api.core.Timeout
import com.tryfinch.api.core.http.Headers
import com.tryfinch.api.core.http.HttpClient
+import com.tryfinch.api.core.http.ProxyAuthenticator
import com.tryfinch.api.core.http.QueryParams
import com.tryfinch.api.core.jsonMapper
import java.net.Proxy
@@ -45,6 +47,9 @@ class FinchOkHttpClient private constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var dispatcherExecutorService: ExecutorService? = null
private var proxy: Proxy? = null
+ private var proxyAuthenticator: ProxyAuthenticator? = null
+ private var maxIdleConnections: Int? = null
+ private var keepAliveDuration: Duration? = null
private var sslSocketFactory: SSLSocketFactory? = null
private var trustManager: X509TrustManager? = null
private var hostnameVerifier: HostnameVerifier? = null
@@ -63,6 +68,44 @@ class FinchOkHttpClient private constructor() {
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ /**
+ * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication
+ * Required`.
+ */
+ fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
+ this.proxyAuthenticator = proxyAuthenticator
+ }
+
+ /**
+ * The maximum number of idle connections kept by the underlying OkHttp connection pool.
+ *
+ * If this is set, then [keepAliveDuration] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun maxIdleConnections(maxIdleConnections: Int?) = apply {
+ this.maxIdleConnections = maxIdleConnections
+ }
+
+ /**
+ * Alias for [Builder.maxIdleConnections].
+ *
+ * This unboxed primitive overload exists for backwards compatibility.
+ */
+ fun maxIdleConnections(maxIdleConnections: Int) =
+ maxIdleConnections(maxIdleConnections as Int?)
+
+ /**
+ * The keep-alive duration for idle connections in the underlying OkHttp connection pool.
+ *
+ * If this is set, then [maxIdleConnections] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
+ this.keepAliveDuration = keepAliveDuration
+ }
+
/**
* The socket factory used to secure HTTPS connections.
*
@@ -148,6 +191,9 @@ class FinchOkHttpClient private constructor() {
/**
* Whether to call `validate` on every response before returning it.
*
+ * Setting this to `true` is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
* 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.
*/
@@ -189,6 +235,15 @@ class FinchOkHttpClient private constructor() {
*/
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
+
fun accessToken(accessToken: String?) = apply { clientOptions.accessToken(accessToken) }
fun clientId(clientId: String?) = apply { clientOptions.clientId(clientId) }
@@ -298,6 +353,9 @@ class FinchOkHttpClient private constructor() {
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
+ .proxyAuthenticator(proxyAuthenticator)
+ .maxIdleConnections(maxIdleConnections)
+ .keepAliveDuration(keepAliveDuration)
.dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
diff --git a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
index 04d43567e..9667fa316 100644
--- a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
+++ b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.kt
@@ -6,10 +6,12 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.tryfinch.api.client.FinchClientAsync
import com.tryfinch.api.client.FinchClientAsyncImpl
import com.tryfinch.api.core.ClientOptions
+import com.tryfinch.api.core.LogLevel
import com.tryfinch.api.core.Sleeper
import com.tryfinch.api.core.Timeout
import com.tryfinch.api.core.http.Headers
import com.tryfinch.api.core.http.HttpClient
+import com.tryfinch.api.core.http.ProxyAuthenticator
import com.tryfinch.api.core.http.QueryParams
import com.tryfinch.api.core.jsonMapper
import java.net.Proxy
@@ -45,6 +47,9 @@ class FinchOkHttpClientAsync private constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var dispatcherExecutorService: ExecutorService? = null
private var proxy: Proxy? = null
+ private var proxyAuthenticator: ProxyAuthenticator? = null
+ private var maxIdleConnections: Int? = null
+ private var keepAliveDuration: Duration? = null
private var sslSocketFactory: SSLSocketFactory? = null
private var trustManager: X509TrustManager? = null
private var hostnameVerifier: HostnameVerifier? = null
@@ -63,6 +68,44 @@ class FinchOkHttpClientAsync private constructor() {
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ /**
+ * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication
+ * Required`.
+ */
+ fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
+ this.proxyAuthenticator = proxyAuthenticator
+ }
+
+ /**
+ * The maximum number of idle connections kept by the underlying OkHttp connection pool.
+ *
+ * If this is set, then [keepAliveDuration] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun maxIdleConnections(maxIdleConnections: Int?) = apply {
+ this.maxIdleConnections = maxIdleConnections
+ }
+
+ /**
+ * Alias for [Builder.maxIdleConnections].
+ *
+ * This unboxed primitive overload exists for backwards compatibility.
+ */
+ fun maxIdleConnections(maxIdleConnections: Int) =
+ maxIdleConnections(maxIdleConnections as Int?)
+
+ /**
+ * The keep-alive duration for idle connections in the underlying OkHttp connection pool.
+ *
+ * If this is set, then [maxIdleConnections] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
+ this.keepAliveDuration = keepAliveDuration
+ }
+
/**
* The socket factory used to secure HTTPS connections.
*
@@ -148,6 +191,9 @@ class FinchOkHttpClientAsync private constructor() {
/**
* Whether to call `validate` on every response before returning it.
*
+ * Setting this to `true` is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
* 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.
*/
@@ -189,6 +235,15 @@ class FinchOkHttpClientAsync private constructor() {
*/
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
+
fun accessToken(accessToken: String?) = apply { clientOptions.accessToken(accessToken) }
fun clientId(clientId: String?) = apply { clientOptions.clientId(clientId) }
@@ -298,6 +353,9 @@ class FinchOkHttpClientAsync private constructor() {
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
+ .proxyAuthenticator(proxyAuthenticator)
+ .maxIdleConnections(maxIdleConnections)
+ .keepAliveDuration(keepAliveDuration)
.dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
diff --git a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/OkHttpClient.kt b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/OkHttpClient.kt
index a84766bfd..c919bc068 100644
--- a/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/OkHttpClient.kt
+++ b/finch-kotlin-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/OkHttpClient.kt
@@ -8,19 +8,25 @@ import com.tryfinch.api.core.http.HttpMethod
import com.tryfinch.api.core.http.HttpRequest
import com.tryfinch.api.core.http.HttpRequestBody
import com.tryfinch.api.core.http.HttpResponse
+import com.tryfinch.api.core.http.ProxyAuthenticator
import com.tryfinch.api.errors.FinchIoException
import java.io.IOException
import java.io.InputStream
+import java.io.OutputStream
import java.net.Proxy
import java.time.Duration
import java.util.concurrent.ExecutorService
+import java.util.concurrent.TimeUnit
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
+import kotlin.jvm.optionals.getOrNull
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
+import okhttp3.ConnectionPool
import okhttp3.Dispatcher
+import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
@@ -28,17 +34,18 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
-import okhttp3.logging.HttpLoggingInterceptor
import okio.BufferedSink
+import okio.buffer
+import okio.sink
-class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpClient) :
+class OkHttpClient internal constructor(private val okHttpClient: okhttp3.OkHttpClient) :
HttpClient {
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
val call = newCall(request, requestOptions)
return try {
- call.execute().toResponse()
+ call.execute().toHttpResponse()
} catch (e: IOException) {
throw FinchIoException("Request failed", e)
} finally {
@@ -53,7 +60,7 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
val call = newCall(request, requestOptions)
return try {
- call.executeAsync().toResponse()
+ call.executeAsync().toHttpResponse()
} catch (e: IOException) {
throw FinchIoException("Request failed", e)
} finally {
@@ -70,18 +77,6 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
val clientBuilder = okHttpClient.newBuilder()
- val logLevel =
- when (System.getenv("FINCH_LOG")?.lowercase()) {
- "info" -> HttpLoggingInterceptor.Level.BASIC
- "debug" -> HttpLoggingInterceptor.Level.BODY
- else -> null
- }
- if (logLevel != null) {
- clientBuilder.addNetworkInterceptor(
- HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
- )
- }
-
requestOptions.timeout?.let {
clientBuilder
.connectTimeout(it.connect())
@@ -111,89 +106,6 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
)
}
- private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request {
- var body: RequestBody? = body?.toRequestBody()
- if (body == null && requiresBody(method)) {
- body = "".toRequestBody()
- }
-
- val builder = Request.Builder().url(toUrl()).method(method.name, body)
- headers.names().forEach { name ->
- headers.values(name).forEach { builder.addHeader(name, it) }
- }
-
- if (
- !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
- ) {
- builder.addHeader(
- "X-Stainless-Read-Timeout",
- Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
- )
- }
- if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
- builder.addHeader(
- "X-Stainless-Timeout",
- Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
- )
- }
-
- return builder.build()
- }
-
- /** `OkHttpClient` always requires a request body for some methods. */
- private fun requiresBody(method: HttpMethod): Boolean =
- when (method) {
- HttpMethod.POST,
- HttpMethod.PUT,
- HttpMethod.PATCH -> true
- else -> false
- }
-
- private fun HttpRequest.toUrl(): String {
- val builder = baseUrl.toHttpUrl().newBuilder()
- pathSegments.forEach(builder::addPathSegment)
- queryParams.keys().forEach { key ->
- queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
- }
-
- return builder.toString()
- }
-
- private fun HttpRequestBody.toRequestBody(): RequestBody {
- val mediaType = contentType()?.toMediaType()
- val length = contentLength()
-
- return object : RequestBody() {
- override fun contentType(): MediaType? = mediaType
-
- override fun contentLength(): Long = length
-
- override fun isOneShot(): Boolean = !repeatable()
-
- override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream())
- }
- }
-
- private fun Response.toResponse(): HttpResponse {
- val headers = headers.toHeaders()
-
- return object : HttpResponse {
- override fun statusCode(): Int = code
-
- override fun headers(): Headers = headers
-
- override fun body(): InputStream = body!!.byteStream()
-
- override fun close() = body!!.close()
- }
- }
-
- private fun okhttp3.Headers.toHeaders(): Headers {
- val headersBuilder = Headers.builder()
- forEach { (name, value) -> headersBuilder.put(name, value) }
- return headersBuilder.build()
- }
-
companion object {
fun builder() = Builder()
}
@@ -202,6 +114,9 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
+ private var proxyAuthenticator: ProxyAuthenticator? = null
+ private var maxIdleConnections: Int? = null
+ private var keepAliveDuration: Duration? = null
private var dispatcherExecutorService: ExecutorService? = null
private var sslSocketFactory: SSLSocketFactory? = null
private var trustManager: X509TrustManager? = null
@@ -213,6 +128,32 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
+ this.proxyAuthenticator = proxyAuthenticator
+ }
+
+ /**
+ * Sets the maximum number of idle connections kept by the underlying [ConnectionPool].
+ *
+ * If this is set, then [keepAliveDuration] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun maxIdleConnections(maxIdleConnections: Int?) = apply {
+ this.maxIdleConnections = maxIdleConnections
+ }
+
+ /**
+ * Sets the keep-alive duration for idle connections in the underlying [ConnectionPool].
+ *
+ * If this is set, then [maxIdleConnections] must also be set.
+ *
+ * If unset, then OkHttp's default is used.
+ */
+ fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
+ this.keepAliveDuration = keepAliveDuration
+ }
+
fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply {
this.dispatcherExecutorService = dispatcherExecutorService
}
@@ -240,8 +181,37 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
.callTimeout(timeout.request())
.proxy(proxy)
.apply {
+ proxyAuthenticator?.let { auth ->
+ proxyAuthenticator { route, response ->
+ auth
+ .authenticate(
+ route?.proxy ?: Proxy.NO_PROXY,
+ response.request.toHttpRequest(),
+ response.toHttpResponse(),
+ )
+ .getOrNull()
+ ?.toRequest(client = null)
+ }
+ }
+
dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) }
+ val maxIdleConnections = maxIdleConnections
+ val keepAliveDuration = keepAliveDuration
+ if (maxIdleConnections != null && keepAliveDuration != null) {
+ connectionPool(
+ ConnectionPool(
+ maxIdleConnections,
+ keepAliveDuration.toNanos(),
+ TimeUnit.NANOSECONDS,
+ )
+ )
+ } else {
+ check((maxIdleConnections != null) == (keepAliveDuration != null)) {
+ "Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set"
+ }
+ }
+
val sslSocketFactory = sslSocketFactory
val trustManager = trustManager
if (sslSocketFactory != null && trustManager != null) {
@@ -263,3 +233,126 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC
)
}
}
+
+private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient?): Request {
+ var body: RequestBody? = body?.toRequestBody()
+ if (body == null && requiresBody(method)) {
+ body = "".toRequestBody()
+ }
+
+ val builder = Request.Builder().url(toUrl()).method(method.name, body)
+ headers.names().forEach { name -> headers.values(name).forEach { builder.addHeader(name, it) } }
+
+ if (client != null) {
+ if (
+ !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
+ ) {
+ builder.addHeader(
+ "X-Stainless-Read-Timeout",
+ Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
+ )
+ }
+ if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
+ builder.addHeader(
+ "X-Stainless-Timeout",
+ Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
+ )
+ }
+ }
+
+ return builder.build()
+}
+
+/** `OkHttpClient` always requires a request body for some methods. */
+private fun requiresBody(method: HttpMethod): Boolean =
+ when (method) {
+ HttpMethod.POST,
+ HttpMethod.PUT,
+ HttpMethod.PATCH -> true
+ else -> false
+ }
+
+private fun HttpRequest.toUrl(): String {
+ val builder = baseUrl.toHttpUrl().newBuilder()
+ pathSegments.forEach(builder::addPathSegment)
+ queryParams.keys().forEach { key ->
+ queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
+ }
+
+ return builder.toString()
+}
+
+private fun HttpRequestBody.toRequestBody(): RequestBody {
+ val mediaType = contentType()?.toMediaType()
+ val length = contentLength()
+
+ return object : RequestBody() {
+ override fun contentType(): MediaType? = mediaType
+
+ override fun contentLength(): Long = length
+
+ override fun isOneShot(): Boolean = !repeatable()
+
+ override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream())
+ }
+}
+
+private fun Request.toHttpRequest(): HttpRequest {
+ val builder = HttpRequest.builder().method(HttpMethod.valueOf(method)).baseUrl(url.toBaseUrl())
+ url.pathSegments.forEach(builder::addPathSegment)
+ url.queryParameterNames.forEach { name ->
+ url.queryParameterValues(name).filterNotNull().forEach { builder.putQueryParam(name, it) }
+ }
+ headers.forEach { (name, value) -> builder.putHeader(name, value) }
+ body?.let { builder.body(it.toHttpRequestBody()) }
+ return builder.build()
+}
+
+private fun HttpUrl.toBaseUrl(): String = buildString {
+ append(scheme).append("://").append(host)
+ if (port != HttpUrl.defaultPort(scheme)) {
+ append(":").append(port)
+ }
+}
+
+private fun RequestBody.toHttpRequestBody(): HttpRequestBody {
+ val mediaType = contentType()?.toString()
+ val length = contentLength()
+ val isOneShot = isOneShot()
+ val source = this
+ return object : HttpRequestBody {
+ override fun contentType(): String? = mediaType
+
+ override fun contentLength(): Long = length
+
+ override fun repeatable(): Boolean = !isOneShot
+
+ override fun writeTo(outputStream: OutputStream) {
+ val sink = outputStream.sink().buffer()
+ source.writeTo(sink)
+ sink.flush()
+ }
+
+ override fun close() {}
+ }
+}
+
+private fun Response.toHttpResponse(): HttpResponse {
+ val headers = headers.toHeaders()
+
+ return object : HttpResponse {
+ override fun statusCode(): Int = code
+
+ override fun headers(): Headers = headers
+
+ override fun body(): InputStream = body!!.byteStream()
+
+ override fun close() = body!!.close()
+ }
+}
+
+private fun okhttp3.Headers.toHeaders(): Headers {
+ val headersBuilder = Headers.builder()
+ forEach { (name, value) -> headersBuilder.put(name, value) }
+ return headersBuilder.build()
+}
diff --git a/finch-kotlin-core/build.gradle.kts b/finch-kotlin-core/build.gradle.kts
index a8f0366a8..aa4f7ce9c 100644
--- a/finch-kotlin-core/build.gradle.kts
+++ b/finch-kotlin-core/build.gradle.kts
@@ -27,14 +27,12 @@ dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
- implementation("org.apache.httpcomponents.core5:httpcore5:5.2.4")
- implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
testImplementation(kotlin("test"))
testImplementation(project(":finch-kotlin-client-okhttp"))
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
testImplementation("org.junit-pioneer:junit-pioneer:1.9.1")
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
index b6fc77c51..70f71e675 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt
@@ -5,6 +5,7 @@ package com.tryfinch.api.core
import com.fasterxml.jackson.databind.json.JsonMapper
import com.tryfinch.api.core.http.Headers
import com.tryfinch.api.core.http.HttpClient
+import com.tryfinch.api.core.http.LoggingHttpClient
import com.tryfinch.api.core.http.PhantomReachableClosingHttpClient
import com.tryfinch.api.core.http.QueryParams
import com.tryfinch.api.core.http.RetryingHttpClient
@@ -65,6 +66,9 @@ private constructor(
/**
* Whether to call `validate` on every response before returning it.
*
+ * Setting this to `true` is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
* 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.
*/
@@ -92,6 +96,14 @@ private constructor(
* Defaults to 2.
*/
val maxRetries: Int,
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ val logLevel: LogLevel,
val accessToken: String?,
val clientId: String?,
val clientSecret: String?,
@@ -149,6 +161,7 @@ private constructor(
private var responseValidation: Boolean = false
private var timeout: Timeout = Timeout.default()
private var maxRetries: Int = 2
+ private var logLevel: LogLevel = LogLevel.fromEnv()
private var accessToken: String? = null
private var clientId: String? = null
private var clientSecret: String? = null
@@ -166,6 +179,7 @@ private constructor(
responseValidation = clientOptions.responseValidation
timeout = clientOptions.timeout
maxRetries = clientOptions.maxRetries
+ logLevel = clientOptions.logLevel
accessToken = clientOptions.accessToken
clientId = clientOptions.clientId
clientSecret = clientOptions.clientSecret
@@ -232,6 +246,9 @@ private constructor(
/**
* Whether to call `validate` on every response before returning it.
*
+ * Setting this to `true` is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
* 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.
*/
@@ -273,6 +290,15 @@ private constructor(
*/
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { this.logLevel = logLevel }
+
fun accessToken(accessToken: String?) = apply { this.accessToken = accessToken }
fun clientId(clientId: String?) = apply { this.clientId = clientId }
@@ -378,6 +404,7 @@ private constructor(
* System properties take precedence over environment variables.
*/
fun fromEnv() = apply {
+ logLevel(LogLevel.fromEnv())
(System.getProperty("finch.baseUrl") ?: System.getenv("FINCH_BASE_URL"))?.let {
baseUrl(it)
}
@@ -388,6 +415,14 @@ private constructor(
?.let { clientSecret(it) }
(System.getProperty("finch.webhookSecret") ?: System.getenv("FINCH_WEBHOOK_SECRET"))
?.let { webhookSecret(it) }
+ System.getenv("FINCH_CUSTOM_HEADERS")?.let { customHeadersEnv ->
+ for (line in customHeadersEnv.split("\n")) {
+ val colon = line.indexOf(':')
+ if (colon >= 0) {
+ putHeader(line.substring(0, colon).trim(), line.substring(colon + 1).trim())
+ }
+ }
+ }
}
/**
@@ -417,28 +452,20 @@ private constructor(
headers.put("X-Stainless-Runtime-Version", getJavaVersion())
headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString())
headers.put("Finch-API-Version", "2020-09-17")
- clientId?.let { username ->
- clientSecret?.let { password ->
- if (!username.isEmpty() && !password.isEmpty()) {
- headers.put(
- "Authorization",
- "Basic ${Base64.getEncoder().encodeToString("$username:$password".toByteArray())}",
- )
- }
- }
- }
- accessToken?.let {
- if (!it.isEmpty()) {
- headers.put("Authorization", "Bearer $it")
- }
- }
+ // We replace after all the default headers to allow end-users to overwrite them.
headers.replaceAll(this.headers.build())
queryParams.replaceAll(this.queryParams.build())
return ClientOptions(
httpClient,
RetryingHttpClient.builder()
- .httpClient(httpClient)
+ .httpClient(
+ LoggingHttpClient.builder()
+ .httpClient(httpClient)
+ .clock(clock)
+ .level(logLevel)
+ .build()
+ )
.sleeper(sleeper)
.clock(clock)
.maxRetries(maxRetries)
@@ -453,6 +480,7 @@ private constructor(
responseValidation,
timeout,
maxRetries,
+ logLevel,
accessToken,
clientId,
clientSecret,
@@ -475,4 +503,28 @@ private constructor(
httpClient.close()
sleeper.close()
}
+
+ internal fun securityHeaders(security: SecurityOptions): Headers {
+ val headers = Headers.builder()
+ if (security.bearerAuth) {
+ accessToken?.let {
+ if (!it.isEmpty()) {
+ headers.replace("Authorization", "Bearer $it")
+ }
+ }
+ }
+ if (security.basicAuth) {
+ clientId?.let { username ->
+ clientSecret?.let { password ->
+ if (!username.isEmpty() && !password.isEmpty()) {
+ headers.replace(
+ "Authorization",
+ "Basic ${Base64.getEncoder().encodeToString("$username:$password".toByteArray())}",
+ )
+ }
+ }
+ }
+ }
+ return headers.build()
+ }
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/LogLevel.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/LogLevel.kt
new file mode 100644
index 000000000..2891f6668
--- /dev/null
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/LogLevel.kt
@@ -0,0 +1,33 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.tryfinch.api.core
+
+/** The level at which to log request and response information. */
+enum class LogLevel {
+ /** No logging. */
+ OFF,
+ /** Minimal request and response summary logs. No headers or bodies are logged. */
+ INFO,
+ /** [INFO] logs plus details about request failures. */
+ ERROR,
+ /**
+ * Full request and response logs. Sensitive headers are redacted, but sensitive data in request
+ * and response bodies may still be visible.
+ */
+ DEBUG;
+
+ /** Returns whether this level is at or higher than the given [level]. */
+ fun shouldLog(level: LogLevel): Boolean = ordinal >= level.ordinal
+
+ companion object {
+
+ /** Returns a [LogLevel] based on the `FINCH_LOG` environment variable. */
+ fun fromEnv() =
+ when (System.getenv("FINCH_LOG")?.lowercase()) {
+ "info" -> INFO
+ "error" -> ERROR
+ "debug" -> DEBUG
+ else -> OFF
+ }
+ }
+}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ObjectMappers.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ObjectMappers.kt
index 5ad93de49..7a3ddcb33 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ObjectMappers.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/ObjectMappers.kt
@@ -29,7 +29,9 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
-fun jsonMapper(): JsonMapper =
+fun jsonMapper(): JsonMapper = JSON_MAPPER
+
+private val JSON_MAPPER: JsonMapper =
JsonMapper.builder()
.addModule(kotlinModule())
.addModule(Jdk8Module())
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Properties.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Properties.kt
index 29bb4994f..795814992 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Properties.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Properties.kt
@@ -34,9 +34,9 @@ fun getOsName(): String {
}
}
-fun getOsVersion(): String = System.getProperty("os.version", "unknown")
+fun getOsVersion(): String = System.getProperty("os.version", "unknown") ?: "unknown"
fun getPackageVersion(): String =
- FinchClient::class.java.`package`.implementationVersion ?: "unknown"
+ FinchClient::class.java.`package`?.implementationVersion ?: "unknown"
-fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
+fun getJavaVersion(): String = System.getProperty("java.version", "unknown") ?: "unknown"
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/RequestOptions.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/RequestOptions.kt
index 28703ba7b..3e5ae5fab 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/RequestOptions.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/RequestOptions.kt
@@ -32,6 +32,15 @@ class RequestOptions private constructor(val responseValidation: Boolean?, val t
private var responseValidation: Boolean? = null
private var timeout: Timeout? = null
+ /**
+ * Whether to call `validate` on the response before returning it.
+ *
+ * Setting this to `true` is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * 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
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/SecurityOptions.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/SecurityOptions.kt
new file mode 100644
index 000000000..1d7aeb686
--- /dev/null
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/SecurityOptions.kt
@@ -0,0 +1,68 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.tryfinch.api.core
+
+import java.util.Objects
+
+/** A class for configuring which security schemes are enabled for a request. */
+class SecurityOptions
+private constructor(
+ /** Whether the bearerAuth security scheme is enabled. */
+ val bearerAuth: Boolean,
+ /** Whether the basicAuth security scheme is enabled. */
+ val basicAuth: Boolean,
+) {
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /** Returns a mutable builder for constructing an instance of [SecurityOptions]. */
+ fun builder() = Builder()
+
+ /** Returns a [Security] instance with all security schemes enabled. */
+ fun all(): SecurityOptions = builder().bearerAuth(true).basicAuth(true).build()
+
+ /** Returns a [Security] instance with no security schemes enabled. */
+ fun none(): SecurityOptions = builder().build()
+ }
+
+ /** A builder for [SecurityOptions]. */
+ class Builder internal constructor() {
+
+ private var bearerAuth: Boolean = false
+ private var basicAuth: Boolean = false
+
+ internal fun from(securityOptions: SecurityOptions) = apply {
+ bearerAuth = securityOptions.bearerAuth
+ basicAuth = securityOptions.basicAuth
+ }
+
+ /** Whether the bearerAuth security scheme is enabled. */
+ fun bearerAuth(bearerAuth: Boolean) = apply { this.bearerAuth = bearerAuth }
+
+ /** Whether the basicAuth security scheme is enabled. */
+ fun basicAuth(basicAuth: Boolean) = apply { this.basicAuth = basicAuth }
+
+ /**
+ * Returns an immutable instance of [SecurityOptions].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
+ fun build(): SecurityOptions = SecurityOptions(bearerAuth, basicAuth)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is SecurityOptions &&
+ bearerAuth == other.bearerAuth &&
+ basicAuth == other.basicAuth
+ }
+
+ override fun hashCode(): Int = Objects.hash(bearerAuth, basicAuth)
+
+ override fun toString() = "SecurityOptions{bearerAuth=$bearerAuth, basicAuth=$basicAuth}"
+}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Utils.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Utils.kt
index 3f2dc4073..2b56e918a 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Utils.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/Utils.kt
@@ -6,6 +6,7 @@ import com.tryfinch.api.core.http.Headers
import com.tryfinch.api.errors.FinchInvalidDataException
import java.util.Collections
import java.util.SortedMap
+import java.util.SortedSet
import java.util.concurrent.locks.Lock
internal fun T?.getOrThrow(name: String): T =
@@ -14,6 +15,10 @@ internal fun T?.getOrThrow(name: String): T =
internal fun List.toImmutable(): List =
if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList())
+internal fun > SortedSet.toImmutable(): SortedSet =
+ if (isEmpty()) Collections.emptySortedSet()
+ else Collections.unmodifiableSortedSet(toSortedSet(comparator() ?: Comparator.naturalOrder()))
+
internal fun Map.toImmutable(): Map =
if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap())
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/HttpRequestBodies.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/HttpRequestBodies.kt
index 647975355..0ab79755a 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/HttpRequestBodies.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/HttpRequestBodies.kt
@@ -6,12 +6,12 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.node.JsonNodeType
import com.tryfinch.api.core.MultipartField
+import com.tryfinch.api.core.toImmutable
import com.tryfinch.api.errors.FinchInvalidDataException
+import java.io.ByteArrayInputStream
import java.io.InputStream
import java.io.OutputStream
-import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
-import org.apache.hc.core5.http.ContentType
-import org.apache.hc.core5.http.HttpEntity
+import java.util.UUID
internal inline fun json(jsonMapper: JsonMapper, value: T): HttpRequestBody =
object : HttpRequestBody {
@@ -32,70 +32,202 @@ internal fun multipartFormData(
jsonMapper: JsonMapper,
fields: Map>,
): HttpRequestBody =
- object : HttpRequestBody {
- private val entity: HttpEntity by lazy {
- MultipartEntityBuilder.create()
- .apply {
- fields.forEach { (name, field) ->
- val knownValue = field.value.asKnown()
- val parts =
- if (knownValue is InputStream) {
- // Read directly from the `InputStream` instead of reading it all
- // into memory due to the `jsonMapper` serialization below.
- sequenceOf(name to knownValue)
- } else {
- val node = jsonMapper.valueToTree(field.value)
- serializePart(name, node)
+ MultipartBody.Builder()
+ .apply {
+ fields.forEach { (name, field) ->
+ val knownValue = field.value.asKnown()
+ val parts =
+ if (knownValue is InputStream) {
+ // Read directly from the `InputStream` instead of reading it all
+ // into memory due to the `jsonMapper` serialization below.
+ sequenceOf(name to knownValue)
+ } else {
+ val node = jsonMapper.valueToTree(field.value)
+ serializePart(name, node)
+ }
+
+ parts.forEach { (name, bytes) ->
+ val partBody =
+ if (bytes is ByteArrayInputStream) {
+ val byteArray = bytes.readBytes()
+
+ object : HttpRequestBody {
+
+ override fun writeTo(outputStream: OutputStream) {
+ outputStream.write(byteArray)
+ }
+
+ override fun contentType(): String = field.contentType
+
+ override fun contentLength(): Long = byteArray.size.toLong()
+
+ override fun repeatable(): Boolean = true
+
+ override fun close() {}
}
+ } else {
+ object : HttpRequestBody {
+
+ override fun writeTo(outputStream: OutputStream) {
+ bytes.copyTo(outputStream)
+ }
+
+ override fun contentType(): String = field.contentType
- parts.forEach { (name, bytes) ->
- addBinaryBody(
- name,
- bytes,
- ContentType.parseLenient(field.contentType),
- field.filename,
- )
+ override fun contentLength(): Long = -1L
+
+ override fun repeatable(): Boolean = false
+
+ override fun close() = bytes.close()
+ }
}
- }
+
+ addPart(
+ MultipartBody.Part.create(name, field.filename, field.contentType, partBody)
+ )
}
- .build()
+ }
}
+ .build()
+
+private fun serializePart(name: String, node: JsonNode): Sequence> =
+ when (node.nodeType) {
+ JsonNodeType.MISSING,
+ JsonNodeType.NULL -> emptySequence()
+ JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream())
+ JsonNodeType.STRING -> sequenceOf(name to node.textValue().byteInputStream())
+ JsonNodeType.BOOLEAN -> sequenceOf(name to node.booleanValue().toString().byteInputStream())
+ JsonNodeType.NUMBER -> sequenceOf(name to node.numberValue().toString().byteInputStream())
+ JsonNodeType.ARRAY ->
+ node.elements().asSequence().flatMap { element -> serializePart("$name[]", element) }
+ JsonNodeType.OBJECT ->
+ node.fields().asSequence().flatMap { (key, value) ->
+ serializePart("$name[$key]", value)
+ }
+ JsonNodeType.POJO,
+ null -> throw FinchInvalidDataException("Unexpected JsonNode type: ${node.nodeType}")
+ }
- private fun serializePart(
- name: String,
- node: JsonNode,
- ): Sequence> =
- when (node.nodeType) {
- JsonNodeType.MISSING,
- JsonNodeType.NULL -> emptySequence()
- JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream())
- JsonNodeType.STRING -> sequenceOf(name to node.textValue().inputStream())
- JsonNodeType.BOOLEAN ->
- sequenceOf(name to node.booleanValue().toString().inputStream())
- JsonNodeType.NUMBER ->
- sequenceOf(name to node.numberValue().toString().inputStream())
- JsonNodeType.ARRAY ->
- node.elements().asSequence().flatMap { element ->
- serializePart("$name[]", element)
- }
- JsonNodeType.OBJECT ->
- node.fields().asSequence().flatMap { (key, value) ->
- serializePart("$name[$key]", value)
- }
- JsonNodeType.POJO,
- null ->
- throw FinchInvalidDataException("Unexpected JsonNode type: ${node.nodeType}")
+private class MultipartBody
+private constructor(private val boundary: String, private val parts: List) : HttpRequestBody {
+ private val boundaryBytes: ByteArray = boundary.toByteArray()
+ private val contentType = "multipart/form-data; boundary=$boundary"
+
+ // This must remain in sync with `contentLength`.
+ override fun writeTo(outputStream: OutputStream) {
+ parts.forEach { part ->
+ outputStream.write(DASHDASH)
+ outputStream.write(boundaryBytes)
+ outputStream.write(CRLF)
+
+ outputStream.write(CONTENT_DISPOSITION)
+ outputStream.write(part.contentDisposition.toByteArray())
+ outputStream.write(CRLF)
+
+ outputStream.write(CONTENT_TYPE)
+ outputStream.write(part.contentType.toByteArray())
+ outputStream.write(CRLF)
+
+ outputStream.write(CRLF)
+ part.body.writeTo(outputStream)
+ outputStream.write(CRLF)
+ }
+
+ outputStream.write(DASHDASH)
+ outputStream.write(boundaryBytes)
+ outputStream.write(DASHDASH)
+ outputStream.write(CRLF)
+ }
+
+ override fun contentType(): String = contentType
+
+ // This must remain in sync with `writeTo`.
+ override fun contentLength(): Long {
+ var byteCount = 0L
+
+ parts.forEach { part ->
+ val contentLength = part.body.contentLength()
+ if (contentLength == -1L) {
+ return -1L
}
- private fun String.inputStream(): InputStream = toByteArray().inputStream()
+ byteCount +=
+ DASHDASH.size +
+ boundaryBytes.size +
+ CRLF.size +
+ CONTENT_DISPOSITION.size +
+ part.contentDisposition.toByteArray().size +
+ CRLF.size +
+ CONTENT_TYPE.size +
+ part.contentType.toByteArray().size +
+ CRLF.size +
+ CRLF.size +
+ contentLength +
+ CRLF.size
+ }
- override fun writeTo(outputStream: OutputStream) = entity.writeTo(outputStream)
+ byteCount += DASHDASH.size + boundaryBytes.size + DASHDASH.size + CRLF.size
+ return byteCount
+ }
- override fun contentType(): String = entity.contentType
+ override fun repeatable(): Boolean = parts.all { it.body.repeatable() }
- override fun contentLength(): Long = entity.contentLength
+ override fun close() {
+ parts.forEach { it.body.close() }
+ }
- override fun repeatable(): Boolean = entity.isRepeatable
+ class Builder {
+ private val boundary = UUID.randomUUID().toString()
+ private val parts: MutableList = mutableListOf()
- override fun close() = entity.close()
+ fun addPart(part: Part) = apply { parts.add(part) }
+
+ fun build() = MultipartBody(boundary, parts.toImmutable())
+ }
+
+ class Part
+ private constructor(
+ val contentDisposition: String,
+ val contentType: String,
+ val body: HttpRequestBody,
+ ) {
+ companion object {
+ fun create(
+ name: String,
+ filename: String?,
+ contentType: String,
+ body: HttpRequestBody,
+ ): Part {
+ val disposition = buildString {
+ append("form-data; name=")
+ appendQuotedString(name)
+ if (filename != null) {
+ append("; filename=")
+ appendQuotedString(filename)
+ }
+ }
+ return Part(disposition, contentType, body)
+ }
+ }
+ }
+
+ companion object {
+ private val CRLF = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte())
+ private val DASHDASH = byteArrayOf('-'.code.toByte(), '-'.code.toByte())
+ private val CONTENT_DISPOSITION = "Content-Disposition: ".toByteArray()
+ private val CONTENT_TYPE = "Content-Type: ".toByteArray()
+
+ private fun StringBuilder.appendQuotedString(key: String) {
+ append('"')
+ for (ch in key) {
+ when (ch) {
+ '\n' -> append("%0A")
+ '\r' -> append("%0D")
+ '"' -> append("%22")
+ else -> append(ch)
+ }
+ }
+ append('"')
+ }
}
+}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/LoggingHttpClient.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/LoggingHttpClient.kt
new file mode 100644
index 000000000..c105cc1b9
--- /dev/null
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/LoggingHttpClient.kt
@@ -0,0 +1,617 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.tryfinch.api.core.http
+
+import com.tryfinch.api.core.LogLevel
+import com.tryfinch.api.core.RequestOptions
+import com.tryfinch.api.core.checkRequired
+import com.tryfinch.api.core.toImmutable
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.nio.ByteBuffer
+import java.nio.charset.CharacterCodingException
+import java.nio.charset.Charset
+import java.nio.charset.CharsetDecoder
+import java.nio.charset.CodingErrorAction
+import java.nio.charset.StandardCharsets
+import java.time.Clock
+import java.time.Duration
+import java.time.OffsetDateTime
+import java.util.SortedSet
+import kotlin.time.toKotlinDuration
+
+/** A wrapper [HttpClient] around [httpClient] that logs request and response information. */
+class LoggingHttpClient
+private constructor(
+ /** The underlying [HttpClient] for making requests. */
+ val httpClient: HttpClient,
+ /**
+ * Sensitive headers to redact from logs.
+ *
+ * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie")`.
+ */
+ val redactedHeaders: SortedSet,
+ /**
+ * The clock to use for measuring request and response durations.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
+ val clock: Clock,
+ /**
+ * The log level to use.
+ *
+ * Pass [LogLevel.fromEnv] to read from environment variables.
+ */
+ val level: LogLevel,
+) : HttpClient {
+
+ override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
+ val loggingRequest = logRequest(request)
+
+ val before = OffsetDateTime.now(clock)
+ val response =
+ try {
+ httpClient.execute(loggingRequest, requestOptions)
+ } catch (e: Throwable) {
+ logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
+ throw e
+ }
+
+ val took = Duration.between(before, OffsetDateTime.now(clock))
+ return logResponse(response, took)
+ }
+
+ override suspend fun executeAsync(
+ request: HttpRequest,
+ requestOptions: RequestOptions,
+ ): HttpResponse {
+ val loggingRequest = logRequest(request)
+
+ val before = OffsetDateTime.now(clock)
+ val response =
+ try {
+ httpClient.executeAsync(loggingRequest, requestOptions)
+ } catch (e: Throwable) {
+ logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
+ throw e
+ }
+
+ val took = Duration.between(before, OffsetDateTime.now(clock))
+ return logResponse(response, took)
+ }
+
+ private fun logRequest(request: HttpRequest): HttpRequest {
+ if (!level.shouldLog(LogLevel.INFO)) {
+ return request
+ }
+
+ System.err.println(
+ buildString {
+ append("--> ${request.method} ${request.url()}")
+ request.body?.let {
+ val length = it.contentLength()
+ append(if (length >= 0) " ($length-byte body)" else " (unknown-length body)")
+ }
+ }
+ )
+
+ if (!level.shouldLog(LogLevel.DEBUG)) {
+ return request
+ }
+
+ logHeaders(request.headers)
+
+ if (request.body == null) {
+ System.err.println("--> END ${request.method}")
+ System.err.println()
+ return request
+ }
+
+ return request
+ .toBuilder()
+ .body(LoggingHttpRequestBody(request.method, request.body))
+ .build()
+ }
+
+ private fun logResponse(response: HttpResponse, took: Duration): HttpResponse {
+ if (!level.shouldLog(LogLevel.INFO)) {
+ return response
+ }
+
+ val contentLength = response.headers().values("Content-Length").firstOrNull()?.toIntOrNull()
+ System.err.println(
+ "<-- ${response.statusCode()} (${
+ buildString {
+ append(took.format())
+ contentLength?.let { append(", $contentLength-byte body") }
+ }
+ })"
+ )
+
+ if (!level.shouldLog(LogLevel.DEBUG)) {
+ return response
+ }
+
+ logHeaders(response.headers())
+ return LoggingHttpResponse(response)
+ }
+
+ private fun logFailure(error: Throwable, took: Duration) {
+ if (!level.shouldLog(LogLevel.ERROR)) {
+ return
+ }
+
+ System.err.println(
+ buildString {
+ append("<-- !! ${error.javaClass.simpleName}")
+ error.message?.let { append(": $it") }
+ append(" (${took.format()})")
+ }
+ )
+ }
+
+ private fun logHeaders(headers: Headers) =
+ headers.names().forEach { name ->
+ headers.values(name).forEach { value ->
+ System.err.println("$name: ${if (redactedHeaders.contains(name)) "██" else value}")
+ }
+ }
+
+ override fun close() = httpClient.close()
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /**
+ * Returns a mutable builder for constructing an instance of [LoggingHttpClient].
+ *
+ * The following fields are required:
+ * ```kotlin
+ * .httpClient()
+ * .level()
+ * ```
+ */
+ fun builder() = Builder()
+ }
+
+ /** A builder for [LoggingHttpClient]. */
+ class Builder internal constructor() {
+
+ private var httpClient: HttpClient? = null
+ private var redactedHeaders: Set =
+ setOf("authorization", "api-key", "x-api-key", "cookie", "set-cookie")
+ private var clock: Clock = Clock.systemUTC()
+ private var level: LogLevel? = null
+
+ internal fun from(loggingHttpClient: LoggingHttpClient) = apply {
+ httpClient = loggingHttpClient.httpClient
+ redactedHeaders = loggingHttpClient.redactedHeaders
+ clock = loggingHttpClient.clock
+ level = loggingHttpClient.level
+ }
+
+ /** The underlying [HttpClient] for making requests. */
+ fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
+
+ /**
+ * Sensitive headers to redact from logs.
+ *
+ * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie")`.
+ */
+ fun redactedHeaders(redactedHeaders: Set) = apply {
+ this.redactedHeaders = redactedHeaders
+ }
+
+ /**
+ * The clock to use for measuring request and response durations.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
+ fun clock(clock: Clock) = apply { this.clock = clock }
+
+ /**
+ * The log level to use.
+ *
+ * Pass [LogLevel.fromEnv] to read from environment variables.
+ */
+ fun level(level: LogLevel) = apply { this.level = level }
+
+ /**
+ * Returns an immutable instance of [LoggingHttpClient].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```kotlin
+ * .httpClient()
+ * .level()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): LoggingHttpClient =
+ LoggingHttpClient(
+ checkRequired("httpClient", httpClient),
+ redactedHeaders.toSortedSet(String.CASE_INSENSITIVE_ORDER).toImmutable(),
+ clock,
+ checkRequired("level", level),
+ )
+ }
+}
+
+/**
+ * An [HttpRequestBody] wrapper that delegates to [body] while also logging line by line as it's
+ * written.
+ *
+ * The logging occurs in a streaming manner with minimal buffering.
+ */
+private class LoggingHttpRequestBody(
+ private val method: HttpMethod,
+ private val body: HttpRequestBody,
+) : HttpRequestBody {
+
+ private val charset by lazy { parseCharset(body.contentType()) }
+
+ override fun writeTo(outputStream: OutputStream) {
+ val loggingOutputStream = LoggingOutputStream(outputStream, charset)
+ body.writeTo(loggingOutputStream)
+
+ loggingOutputStream.flush()
+ System.err.println("--> END $method (${loggingOutputStream.writeCount()}-byte body)")
+ System.err.println()
+ }
+
+ override fun contentType(): String? = body.contentType()
+
+ override fun contentLength(): Long = body.contentLength()
+
+ override fun repeatable(): Boolean = body.repeatable()
+
+ override fun close() = body.close()
+}
+
+/**
+ * An [OutputStream] wrapper that delegates to [outputStream] while also logging bytes line by line
+ * as it's written to.
+ *
+ * The written content is assumed to be in the given [charset] and the logging occurs in a streaming
+ * manner with minimal buffering.
+ */
+private class LoggingOutputStream(private val outputStream: OutputStream, charset: Charset?) :
+ OutputStream() {
+
+ private val buffer = LoggingBuffer(charset)
+
+ fun writeCount() = buffer.writeCount()
+
+ override fun write(b: Int) {
+ outputStream.write(b)
+ buffer.write(b)
+ }
+
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ outputStream.write(b, off, len)
+ for (i in off until off + len) {
+ buffer.write(b[i].toInt() and 0xFF)
+ }
+ }
+
+ /** Prints any currently buffered content. */
+ override fun flush() {
+ buffer.flush()
+ outputStream.flush()
+ }
+
+ override fun close() = outputStream.close()
+}
+
+/**
+ * An [HttpResponse] wrapper that delegates to [response] while also logging line-by-line as it's
+ * read.
+ *
+ * The logging occurs in a streaming manner with minimal buffering.
+ */
+private class LoggingHttpResponse(private val response: HttpResponse) : HttpResponse {
+
+ private val loggingBody: Lazy = lazy {
+ LoggingInputStream(
+ response.body(),
+ parseCharset(response.headers().values("Content-Type").firstOrNull()),
+ )
+ }
+
+ override fun statusCode(): Int = response.statusCode()
+
+ override fun headers(): Headers = response.headers()
+
+ override fun body(): InputStream = loggingBody.value
+
+ override fun close() {
+ if (loggingBody.isInitialized()) {
+ loggingBody.value.close()
+ }
+ response.close()
+ }
+}
+
+/**
+ * An [InputStream] wrapper that delegates to [inputStream] while also logging bytes line by line as
+ * it's read.
+ *
+ * The contents of [inputStream] are assumed to be in the given [charset] and the logging occurs in
+ * a streaming manner with minimal buffering.
+ */
+private class LoggingInputStream(private val inputStream: InputStream, charset: Charset?) :
+ InputStream() {
+
+ private var isDone = false
+ private val buffer = LoggingBuffer(charset)
+
+ override fun read(): Int {
+ if (isDone) {
+ return -1
+ }
+
+ val b = inputStream.read()
+
+ if (b == -1) {
+ markDone()
+ return b
+ }
+
+ buffer.write(b)
+ return b
+ }
+
+ override fun read(b: ByteArray, off: Int, len: Int): Int {
+ if (isDone) {
+ return -1
+ }
+
+ val bytesRead = inputStream.read(b, off, len)
+
+ if (bytesRead == -1) {
+ markDone()
+ return bytesRead
+ }
+
+ for (i in off until off + bytesRead) {
+ buffer.write(b[i].toInt() and 0xFF)
+ }
+ return bytesRead
+ }
+
+ override fun close() {
+ if (!isDone) {
+ markDone(closedEarly = true)
+ }
+ inputStream.close()
+ }
+
+ private fun markDone(closedEarly: Boolean = false) {
+ isDone = true
+ buffer.flush()
+ val suffix = if (closedEarly) ", closed early" else ""
+ System.err.println("<-- END HTTP (${buffer.writeCount()}-byte body$suffix)")
+ System.err.println()
+ }
+}
+
+/**
+ * A byte buffer that prints line by line, using the given [charset], as bytes are written to it.
+ *
+ * When [charset] is `null`, the buffer performs an upfront check to detect binary content. If
+ * non-whitespace ISO control characters are found in the first [PROBABLY_UTF8_CODE_POINT_LIMIT]
+ * code points, body logging is suppressed entirely.
+ */
+private class LoggingBuffer(charset: Charset?) {
+
+ private val charset = charset ?: StandardCharsets.UTF_8
+
+ private val decoder: CharsetDecoder =
+ this.charset
+ .newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ private var writeCount = 0
+ private val buffer = ByteArrayOutputStream(128)
+
+ /**
+ * Whether logging has been suppressed because the content doesn't appear to be readable text.
+ *
+ * This is only set when [charset] is `null` and the content fails the [isProbablyUtf8] check.
+ */
+ private var suppressed = false
+
+ /**
+ * Bytes accumulated for the [isProbablyUtf8] check before any lines are printed.
+ *
+ * Once the check passes (or [charset] is non-null), this is set to `null` and bytes flow
+ * directly to [buffer].
+ */
+ private var prefetchBuffer: ByteArrayOutputStream? =
+ if (charset != null) null else ByteArrayOutputStream(128)
+
+ fun writeCount() = writeCount
+
+ fun write(b: Int) {
+ if (writeCount == 0) {
+ // Print a newline before we start printing anything to separate the printed content
+ // from previous content.
+ System.err.println()
+ }
+
+ writeCount++
+
+ if (suppressed) {
+ return
+ }
+
+ val prefetch = prefetchBuffer
+ if (prefetch != null) {
+ prefetch.write(b)
+ // Continue accumulating until we have enough bytes to decide.
+ if (prefetch.size() < PROBABLY_UTF8_BYTE_LIMIT && b != '\n'.code) {
+ return
+ }
+ // We have enough bytes. Check if the content is probably UTF-8.
+ prefetchBuffer = null
+ val bytes = prefetch.toByteArray()
+ if (!isProbablyUtf8(bytes)) {
+ suppressed = true
+ System.err.println("(binary body omitted)")
+ return
+ }
+ // Content looks like UTF-8. Feed the accumulated bytes into the normal buffer.
+ for (byte in bytes) {
+ writeToBuffer(byte.toInt() and 0xFF)
+ }
+ return
+ }
+
+ writeToBuffer(b)
+ }
+
+ private fun writeToBuffer(b: Int) {
+ if (b == '\n'.code) {
+ flush()
+ return
+ }
+
+ buffer.write(b)
+ }
+
+ /** Prints any currently buffered content. */
+ fun flush() {
+ if (suppressed) {
+ return
+ }
+
+ // If we still have a prefetch buffer when flush is called (body was shorter than the
+ // limit), run the check now.
+ val prefetch = prefetchBuffer
+ if (prefetch != null) {
+ prefetchBuffer = null
+ val bytes = prefetch.toByteArray()
+ if (bytes.isEmpty()) {
+ return
+ }
+ if (!isProbablyUtf8(bytes)) {
+ suppressed = true
+ System.err.println("(binary body omitted)")
+ return
+ }
+ for (byte in bytes) {
+ writeToBuffer(byte.toInt() and 0xFF)
+ }
+ }
+
+ if (buffer.size() == 0) {
+ return
+ }
+
+ val line =
+ try {
+ decoder.decode(ByteBuffer.wrap(buffer.toByteArray()))
+ } catch (e: CharacterCodingException) {
+ "(omitted line is not valid $charset)"
+ }
+ buffer.reset()
+ System.err.println(line)
+ }
+}
+
+/** The maximum number of code points to sample when checking if content is probably UTF-8. */
+private const val PROBABLY_UTF8_CODE_POINT_LIMIT = 64
+
+/**
+ * The maximum number of bytes to accumulate before running the [isProbablyUtf8] check. UTF-8 code
+ * points are at most 4 bytes, so this accommodates [PROBABLY_UTF8_CODE_POINT_LIMIT] code points.
+ */
+private const val PROBABLY_UTF8_BYTE_LIMIT = PROBABLY_UTF8_CODE_POINT_LIMIT * 4
+
+/**
+ * Returns `true` if the given [bytes] probably contain human-readable UTF-8 text.
+ *
+ * Decodes up to [PROBABLY_UTF8_CODE_POINT_LIMIT] code points and returns `false` if any
+ * non-whitespace ISO control characters are found, or if the bytes are not valid UTF-8.
+ */
+private fun isProbablyUtf8(bytes: ByteArray): Boolean {
+ try {
+ val decoder =
+ StandardCharsets.UTF_8.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ val charBuffer = decoder.decode(ByteBuffer.wrap(bytes))
+ var codePointCount = 0
+ var i = 0
+ while (i < charBuffer.length && codePointCount < PROBABLY_UTF8_CODE_POINT_LIMIT) {
+ val codePoint = Character.codePointAt(charBuffer, i)
+ if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+ return false
+ }
+ i += Character.charCount(codePoint)
+ codePointCount++
+ }
+ return true
+ } catch (e: CharacterCodingException) {
+ return false
+ }
+}
+
+/** Returns the [Charset] in the given [contentType] string, or `null` if unspecified. */
+private fun parseCharset(contentType: String?): Charset? =
+ contentType
+ ?.split(";")
+ ?.drop(1)
+ ?.map { it.trim() }
+ ?.firstOrNull { it.startsWith("charset=", ignoreCase = true) }
+ ?.substringAfter("=")
+ ?.trim()
+ ?.removeSurrounding("\"")
+ ?.let { runCatching { charset(it) }.getOrNull() }
+
+/** Formats the [Duration] into a string like "1m 40s 467ms". */
+private fun Duration.format(): String =
+ toKotlinDuration().toComponents { days, hours, minutes, seconds, nanoseconds ->
+ buildString {
+ val milliseconds = nanoseconds / 1_000_000
+ if (days > 0) {
+ append("${days}d")
+ }
+ if (hours > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${hours}h")
+ }
+ if (minutes > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${minutes}m")
+ }
+ if (seconds > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${seconds}s")
+ }
+ if (milliseconds > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${milliseconds}ms")
+ }
+
+ if (isEmpty()) {
+ append("0s")
+ }
+ }
+ }
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/ProxyAuthenticator.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/ProxyAuthenticator.kt
new file mode 100644
index 000000000..7f4bcb93c
--- /dev/null
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/ProxyAuthenticator.kt
@@ -0,0 +1,59 @@
+package com.tryfinch.api.core.http
+
+import java.net.Proxy
+import java.nio.charset.Charset
+import java.nio.charset.StandardCharsets
+import java.util.Base64
+import java.util.Optional
+
+/**
+ * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication Required`.
+ *
+ * Implementations inspect the 407 [response] (typically its `Proxy-Authenticate` header) and return
+ * the request to retry with a `Proxy-Authorization` header set, or [Optional.empty] to abandon
+ * authentication and surface the 407 to the caller.
+ *
+ * Implementations must be thread-safe; they may be invoked concurrently from multiple HTTP calls.
+ */
+fun interface ProxyAuthenticator {
+
+ /**
+ * @param proxy the proxy that produced the challenge, or [Proxy.NO_PROXY] if the route is not
+ * yet established
+ * @param request the request that produced [response]
+ * @param response the 407 challenge response
+ * @return the retry request to send (typically [request] with a `Proxy-Authorization` header
+ * added), or [Optional.empty] to abandon authentication
+ */
+ fun authenticate(
+ proxy: Proxy,
+ request: HttpRequest,
+ response: HttpResponse,
+ ): Optional
+
+ companion object {
+
+ /**
+ * A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the ISO-8859-1
+ * charset.
+ */
+ @JvmStatic
+ fun basic(username: String, password: String): ProxyAuthenticator =
+ basic(username, password, StandardCharsets.ISO_8859_1)
+
+ /**
+ * A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the given [charset].
+ */
+ @JvmStatic
+ fun basic(username: String, password: String, charset: Charset): ProxyAuthenticator {
+ val token =
+ Base64.getEncoder().encodeToString("$username:$password".toByteArray(charset))
+ val headerValue = "Basic $token"
+ return ProxyAuthenticator { _, request, _ ->
+ Optional.of(
+ request.toBuilder().putHeader("Proxy-Authorization", headerValue).build()
+ )
+ }
+ }
+ }
+}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/RetryingHttpClient.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/RetryingHttpClient.kt
index 09b372088..11fd9c11f 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/RetryingHttpClient.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/core/http/RetryingHttpClient.kt
@@ -1,3 +1,5 @@
+// File generated from our OpenAPI spec by Stainless.
+
package com.tryfinch.api.core.http
import com.tryfinch.api.core.DefaultSleeper
@@ -182,7 +184,7 @@ private constructor(
?: headers.values("Retry-After").getOrNull(0)?.let { retryAfter ->
retryAfter.toFloatOrNull()?.times(TimeUnit.SECONDS.toNanos(1))
?: try {
- ChronoUnit.MILLIS.between(
+ ChronoUnit.NANOS.between(
OffsetDateTime.now(clock),
OffsetDateTime.parse(
retryAfter,
@@ -195,13 +197,8 @@ private constructor(
}
}
?.let { retryAfterNanos ->
- // If the API asks us to wait a certain amount of time (and it's a reasonable
- // amount), just
- // do what it says.
- val retryAfter = Duration.ofNanos(retryAfterNanos.toLong())
- if (retryAfter in Duration.ofNanos(0)..Duration.ofMinutes(1)) {
- return retryAfter
- }
+ // If the API asks us to wait a certain amount of time, do what it says.
+ return Duration.ofNanos(retryAfterNanos.toLong())
}
// Apply exponential backoff, but not more than the max.
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/BadRequestException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/BadRequestException.kt
index 6493c288b..2384a9229 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/BadRequestException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/BadRequestException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class BadRequestException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("400: $body", cause) {
+ FinchServiceException(
+ "400: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 400
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/InternalServerException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/InternalServerException.kt
index 7f3a3b51a..1b46fdef9 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/InternalServerException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/InternalServerException.kt
@@ -5,6 +5,7 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class InternalServerException
private constructor(
@@ -12,7 +13,11 @@ private constructor(
private val headers: Headers,
private val body: JsonValue,
cause: Throwable?,
-) : FinchServiceException("$statusCode: $body", cause) {
+) :
+ FinchServiceException(
+ "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = statusCode
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/NotFoundException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/NotFoundException.kt
index f6d3cd404..5daabcae4 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/NotFoundException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/NotFoundException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class NotFoundException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("404: $body", cause) {
+ FinchServiceException(
+ "404: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 404
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/PermissionDeniedException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/PermissionDeniedException.kt
index 44b2c30ec..f8912760c 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/PermissionDeniedException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/PermissionDeniedException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class PermissionDeniedException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("403: $body", cause) {
+ FinchServiceException(
+ "403: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 403
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/RateLimitException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/RateLimitException.kt
index a0bf64372..c6997d713 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/RateLimitException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/RateLimitException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class RateLimitException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("429: $body", cause) {
+ FinchServiceException(
+ "429: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 429
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnauthorizedException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnauthorizedException.kt
index 88cdd26ec..d79d054f5 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnauthorizedException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnauthorizedException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class UnauthorizedException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("401: $body", cause) {
+ FinchServiceException(
+ "401: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 401
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnexpectedStatusCodeException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnexpectedStatusCodeException.kt
index 076b2ba3f..a85266d84 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnexpectedStatusCodeException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnexpectedStatusCodeException.kt
@@ -5,6 +5,7 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class UnexpectedStatusCodeException
private constructor(
@@ -12,7 +13,11 @@ private constructor(
private val headers: Headers,
private val body: JsonValue,
cause: Throwable?,
-) : FinchServiceException("$statusCode: $body", cause) {
+) :
+ FinchServiceException(
+ "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = statusCode
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnprocessableEntityException.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnprocessableEntityException.kt
index 34c9d1dd1..93e36fd55 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnprocessableEntityException.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/errors/UnprocessableEntityException.kt
@@ -5,10 +5,14 @@ package com.tryfinch.api.errors
import com.tryfinch.api.core.JsonValue
import com.tryfinch.api.core.checkRequired
import com.tryfinch.api.core.http.Headers
+import com.tryfinch.api.core.jsonMapper
class UnprocessableEntityException
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
- FinchServiceException("422: $body", cause) {
+ FinchServiceException(
+ "422: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}",
+ cause,
+ ) {
override fun statusCode(): Int = 422
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccessTokenCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccessTokenCreateParams.kt
index 0c69ffd7b..6b20fccac 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccessTokenCreateParams.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccessTokenCreateParams.kt
@@ -551,6 +551,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): CreateAccessTokenRequest = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountCreateResponse.kt
index 9009210d5..957030cd2 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountCreateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountCreateResponse.kt
@@ -418,6 +418,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AccountCreateResponse = apply {
if (validated) {
return@apply
@@ -560,6 +568,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AuthenticationType = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateEvent.kt
index e2f48a21e..6f770eb42 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateEvent.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateEvent.kt
@@ -22,6 +22,7 @@ private constructor(
private val accountId: JsonField,
private val companyId: JsonField,
private val connectionId: JsonField,
+ private val entityId: JsonField,
private val data: JsonField,
private val eventType: JsonField,
private val additionalProperties: MutableMap,
@@ -34,17 +35,19 @@ private constructor(
@JsonProperty("connection_id")
@ExcludeMissing
connectionId: JsonField = JsonMissing.of(),
+ @JsonProperty("entity_id") @ExcludeMissing entityId: JsonField = JsonMissing.of(),
@JsonProperty("data") @ExcludeMissing data: JsonField = JsonMissing.of(),
@JsonProperty("event_type")
@ExcludeMissing
eventType: JsonField = JsonMissing.of(),
- ) : this(accountId, companyId, connectionId, data, eventType, mutableMapOf())
+ ) : this(accountId, companyId, connectionId, entityId, data, eventType, mutableMapOf())
fun toBaseWebhookEvent(): BaseWebhookEvent =
BaseWebhookEvent.builder()
.accountId(accountId)
.companyId(companyId)
.connectionId(connectionId)
+ .entityId(entityId)
.build()
/**
@@ -73,6 +76,14 @@ private constructor(
*/
fun connectionId(): String? = connectionId.getNullable("connection_id")
+ /**
+ * Unique Finch id of the entity for which data has been updated.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun entityId(): String? = entityId.getNullable("entity_id")
+
/**
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -114,6 +125,13 @@ private constructor(
@ExcludeMissing
fun _connectionId(): JsonField = connectionId
+ /**
+ * Returns the raw JSON value of [entityId].
+ *
+ * Unlike [entityId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("entity_id") @ExcludeMissing fun _entityId(): JsonField = entityId
+
/**
* Returns the raw JSON value of [data].
*
@@ -160,6 +178,7 @@ private constructor(
private var accountId: JsonField? = null
private var companyId: JsonField? = null
private var connectionId: JsonField = JsonMissing.of()
+ private var entityId: JsonField = JsonMissing.of()
private var data: JsonField = JsonMissing.of()
private var eventType: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -168,6 +187,7 @@ private constructor(
accountId = accountUpdateEvent.accountId
companyId = accountUpdateEvent.companyId
connectionId = accountUpdateEvent.connectionId
+ entityId = accountUpdateEvent.entityId
data = accountUpdateEvent.data
eventType = accountUpdateEvent.eventType
additionalProperties = accountUpdateEvent.additionalProperties.toMutableMap()
@@ -221,6 +241,17 @@ private constructor(
this.connectionId = connectionId
}
+ /** Unique Finch id of the entity for which data has been updated. */
+ fun entityId(entityId: String) = entityId(JsonField.of(entityId))
+
+ /**
+ * Sets [Builder.entityId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.entityId] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun entityId(entityId: JsonField) = apply { this.entityId = entityId }
+
fun data(data: Data) = data(JsonField.of(data))
/**
@@ -279,6 +310,7 @@ private constructor(
checkRequired("accountId", accountId),
checkRequired("companyId", companyId),
connectionId,
+ entityId,
data,
eventType,
additionalProperties.toMutableMap(),
@@ -287,6 +319,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AccountUpdateEvent = apply {
if (validated) {
return@apply
@@ -295,6 +335,7 @@ private constructor(
accountId()
companyId()
connectionId()
+ entityId()
data()?.validate()
eventType()?.validate()
validated = true
@@ -317,6 +358,7 @@ private constructor(
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
(if (connectionId.asKnown() == null) 0 else 1) +
+ (if (entityId.asKnown() == null) 0 else 1) +
(data.asKnown()?.validity() ?: 0) +
(eventType.asKnown()?.validity() ?: 0)
@@ -477,6 +519,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Data = apply {
if (validated) {
return@apply
@@ -696,6 +747,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AuthenticationMethod = apply {
if (validated) {
return@apply
@@ -1069,6 +1130,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): SupportedFields = apply {
if (validated) {
return@apply
@@ -1513,6 +1584,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedCompanyFields = apply {
if (validated) {
return@apply
@@ -1824,6 +1905,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Accounts = apply {
if (validated) {
return@apply
@@ -2029,6 +2120,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Departments = apply {
if (validated) {
return@apply
@@ -2167,6 +2268,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Parent = apply {
if (validated) {
return@apply
@@ -2373,6 +2484,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Entity = apply {
if (validated) {
return@apply
@@ -2706,6 +2827,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Locations = apply {
if (validated) {
return@apply
@@ -2963,6 +3094,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedDirectoryFields = apply {
if (validated) {
return@apply
@@ -3328,6 +3469,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Individuals = apply {
if (validated) {
return@apply
@@ -3474,6 +3625,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Manager = apply {
if (validated) {
return@apply
@@ -3694,6 +3855,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Paging = apply {
if (validated) {
return@apply
@@ -4465,6 +4636,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedEmploymentFields = apply {
if (validated) {
return@apply
@@ -4630,6 +4811,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Department = apply {
if (validated) {
return@apply
@@ -4817,6 +5008,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Employment = apply {
if (validated) {
return@apply
@@ -5039,6 +5240,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Income = apply {
if (validated) {
return@apply
@@ -5374,6 +5585,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Location = apply {
if (validated) {
return@apply
@@ -5547,6 +5768,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Manager = apply {
if (validated) {
return@apply
@@ -6193,6 +6424,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedIndividualFields = apply {
if (validated) {
return@apply
@@ -6379,6 +6620,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Emails = apply {
if (validated) {
return@apply
@@ -6566,6 +6817,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): PhoneNumbers = apply {
if (validated) {
return@apply
@@ -6899,6 +7160,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Residence = apply {
if (validated) {
return@apply
@@ -7231,6 +7502,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedPayGroupFields = apply {
if (validated) {
return@apply
@@ -7437,6 +7718,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedPayStatementFields = apply {
if (validated) {
return@apply
@@ -7625,6 +7916,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Paging = apply {
if (validated) {
return@apply
@@ -8131,6 +8432,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): PayStatements = apply {
if (validated) {
return@apply
@@ -8394,6 +8705,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Earnings = apply {
if (validated) {
return@apply
@@ -8704,6 +9025,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): EmployeeDeductions = apply {
if (validated) {
return@apply
@@ -8956,6 +9287,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): EmployerContributions = apply {
if (validated) {
return@apply
@@ -9262,6 +9603,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API
+ * for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): Taxes = apply {
if (validated) {
return@apply
@@ -9910,6 +10261,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
fun validate(): SupportedPaymentFields = apply {
if (validated) {
return@apply
@@ -10103,6 +10464,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their
+ * expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
fun validate(): PayPeriod = apply {
if (validated) {
return@apply
@@ -10341,6 +10712,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
@@ -10504,6 +10885,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): EventType = apply {
if (validated) {
return@apply
@@ -10551,17 +10941,26 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
connectionId == other.connectionId &&
+ entityId == other.entityId &&
data == other.data &&
eventType == other.eventType &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(accountId, companyId, connectionId, data, eventType, additionalProperties)
+ Objects.hash(
+ accountId,
+ companyId,
+ connectionId,
+ entityId,
+ data,
+ eventType,
+ additionalProperties,
+ )
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "AccountUpdateEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
+ "AccountUpdateEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, entityId=$entityId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateResponse.kt
index 3e83c6ec7..fadca9ae8 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AccountUpdateResponse.kt
@@ -382,6 +382,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AccountUpdateResponse = apply {
if (validated) {
return@apply
@@ -522,6 +530,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AuthenticationType = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedAsyncJob.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedAsyncJob.kt
index 053c5a02a..e0d5c15ce 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedAsyncJob.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedAsyncJob.kt
@@ -439,6 +439,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AutomatedAsyncJob = apply {
if (validated) {
return@apply
@@ -585,6 +593,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Params = apply {
if (validated) {
return@apply
@@ -738,6 +755,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Status = apply {
if (validated) {
return@apply
@@ -863,6 +889,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedCreateResponse.kt
index f807cdbb8..9edc951d0 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedCreateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedCreateResponse.kt
@@ -273,6 +273,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AutomatedCreateResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedListResponse.kt
index ccf3d0b7e..dde750ad1 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedListResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/AutomatedListResponse.kt
@@ -175,6 +175,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): AutomatedListResponse = apply {
if (validated) {
return@apply
@@ -304,6 +312,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Meta = apply {
if (validated) {
return@apply
@@ -438,6 +455,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Quotas = apply {
if (validated) {
return@apply
@@ -610,6 +637,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): DataSyncAll = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BaseWebhookEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BaseWebhookEvent.kt
index 8d8cc26bb..5c8b3f5a2 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BaseWebhookEvent.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BaseWebhookEvent.kt
@@ -21,6 +21,7 @@ private constructor(
private val accountId: JsonField,
private val companyId: JsonField,
private val connectionId: JsonField,
+ private val entityId: JsonField,
private val additionalProperties: MutableMap,
) {
@@ -31,7 +32,8 @@ private constructor(
@JsonProperty("connection_id")
@ExcludeMissing
connectionId: JsonField = JsonMissing.of(),
- ) : this(accountId, companyId, connectionId, mutableMapOf())
+ @JsonProperty("entity_id") @ExcludeMissing entityId: JsonField = JsonMissing.of(),
+ ) : this(accountId, companyId, connectionId, entityId, mutableMapOf())
/**
* [DEPRECATED] Unique Finch ID of the employer account used to make this connection. Use
@@ -59,6 +61,14 @@ private constructor(
*/
fun connectionId(): String? = connectionId.getNullable("connection_id")
+ /**
+ * Unique Finch id of the entity for which data has been updated.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun entityId(): String? = entityId.getNullable("entity_id")
+
/**
* Returns the raw JSON value of [accountId].
*
@@ -88,6 +98,13 @@ private constructor(
@ExcludeMissing
fun _connectionId(): JsonField = connectionId
+ /**
+ * Returns the raw JSON value of [entityId].
+ *
+ * Unlike [entityId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("entity_id") @ExcludeMissing fun _entityId(): JsonField = entityId
+
@JsonAnySetter
private fun putAdditionalProperty(key: String, value: JsonValue) {
additionalProperties.put(key, value)
@@ -120,12 +137,14 @@ private constructor(
private var accountId: JsonField? = null
private var companyId: JsonField? = null
private var connectionId: JsonField = JsonMissing.of()
+ private var entityId: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
internal fun from(baseWebhookEvent: BaseWebhookEvent) = apply {
accountId = baseWebhookEvent.accountId
companyId = baseWebhookEvent.companyId
connectionId = baseWebhookEvent.connectionId
+ entityId = baseWebhookEvent.entityId
additionalProperties = baseWebhookEvent.additionalProperties.toMutableMap()
}
@@ -177,6 +196,17 @@ private constructor(
this.connectionId = connectionId
}
+ /** Unique Finch id of the entity for which data has been updated. */
+ fun entityId(entityId: String) = entityId(JsonField.of(entityId))
+
+ /**
+ * Sets [Builder.entityId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.entityId] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun entityId(entityId: JsonField) = apply { this.entityId = entityId }
+
fun additionalProperties(additionalProperties: Map) = apply {
this.additionalProperties.clear()
putAllAdditionalProperties(additionalProperties)
@@ -214,12 +244,21 @@ private constructor(
checkRequired("accountId", accountId),
checkRequired("companyId", companyId),
connectionId,
+ entityId,
additionalProperties.toMutableMap(),
)
}
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BaseWebhookEvent = apply {
if (validated) {
return@apply
@@ -228,6 +267,7 @@ private constructor(
accountId()
companyId()
connectionId()
+ entityId()
validated = true
}
@@ -247,7 +287,8 @@ private constructor(
internal fun validity(): Int =
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
- (if (connectionId.asKnown() == null) 0 else 1)
+ (if (connectionId.asKnown() == null) 0 else 1) +
+ (if (entityId.asKnown() == null) 0 else 1)
override fun equals(other: Any?): Boolean {
if (this === other) {
@@ -258,15 +299,16 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
connectionId == other.connectionId &&
+ entityId == other.entityId &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(accountId, companyId, connectionId, additionalProperties)
+ Objects.hash(accountId, companyId, connectionId, entityId, additionalProperties)
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "BaseWebhookEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, additionalProperties=$additionalProperties}"
+ "BaseWebhookEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, entityId=$entityId, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitContribution.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitContribution.kt
index 5b2164f33..641d228e5 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitContribution.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitContribution.kt
@@ -33,42 +33,74 @@ import java.util.Objects
@JsonSerialize(using = BenefitContribution.Serializer::class)
class BenefitContribution
private constructor(
- private val unionMember0: UnionMember0? = null,
- private val unionMember1: UnionMember1? = null,
- private val unionMember2: UnionMember2? = null,
+ private val fixed: BenefitContributionFixed? = null,
+ private val percent: BenefitContributionPercent? = null,
+ private val tiered: BenefitContributionTiered? = null,
private val _json: JsonValue? = null,
) {
- fun unionMember0(): UnionMember0? = unionMember0
+ fun fixed(): BenefitContributionFixed? = fixed
- fun unionMember1(): UnionMember1? = unionMember1
+ fun percent(): BenefitContributionPercent? = percent
- fun unionMember2(): UnionMember2? = unionMember2
+ fun tiered(): BenefitContributionTiered? = tiered
- fun isUnionMember0(): Boolean = unionMember0 != null
+ fun isFixed(): Boolean = fixed != null
- fun isUnionMember1(): Boolean = unionMember1 != null
+ fun isPercent(): Boolean = percent != null
- fun isUnionMember2(): Boolean = unionMember2 != null
+ fun isTiered(): Boolean = tiered != null
- fun asUnionMember0(): UnionMember0 = unionMember0.getOrThrow("unionMember0")
+ fun asFixed(): BenefitContributionFixed = fixed.getOrThrow("fixed")
- fun asUnionMember1(): UnionMember1 = unionMember1.getOrThrow("unionMember1")
+ fun asPercent(): BenefitContributionPercent = percent.getOrThrow("percent")
- fun asUnionMember2(): UnionMember2 = unionMember2.getOrThrow("unionMember2")
+ fun asTiered(): BenefitContributionTiered = tiered.getOrThrow("tiered")
fun _json(): JsonValue? = _json
+ /**
+ * Maps this instance's current variant to a value of type [T] using the given [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the API, unless
+ * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of the
+ * SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```kotlin
+ * import com.tryfinch.api.core.JsonValue
+ *
+ * val result: String? = benefitContribution.accept(object : BenefitContribution.Visitor {
+ * override fun visitFixed(fixed: BenefitContributionFixed): String? = fixed.toString()
+ *
+ * // ...
+ *
+ * override fun unknown(json: JsonValue?): String? {
+ * // Or inspect the `json`.
+ * return null
+ * }
+ * })
+ * ```
+ *
+ * @throws FinchInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and the
+ * current variant is unknown.
+ */
fun accept(visitor: Visitor): T =
when {
- unionMember0 != null -> visitor.visitUnionMember0(unionMember0)
- unionMember1 != null -> visitor.visitUnionMember1(unionMember1)
- unionMember2 != null -> visitor.visitUnionMember2(unionMember2)
+ fixed != null -> visitor.visitFixed(fixed)
+ percent != null -> visitor.visitPercent(percent)
+ tiered != null -> visitor.visitTiered(tiered)
else -> visitor.unknown(_json)
}
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BenefitContribution = apply {
if (validated) {
return@apply
@@ -76,16 +108,16 @@ private constructor(
accept(
object : Visitor {
- override fun visitUnionMember0(unionMember0: UnionMember0) {
- unionMember0.validate()
+ override fun visitFixed(fixed: BenefitContributionFixed) {
+ fixed.validate()
}
- override fun visitUnionMember1(unionMember1: UnionMember1) {
- unionMember1.validate()
+ override fun visitPercent(percent: BenefitContributionPercent) {
+ percent.validate()
}
- override fun visitUnionMember2(unionMember2: UnionMember2) {
- unionMember2.validate()
+ override fun visitTiered(tiered: BenefitContributionTiered) {
+ tiered.validate()
}
}
)
@@ -108,11 +140,11 @@ private constructor(
internal fun validity(): Int =
accept(
object : Visitor {
- override fun visitUnionMember0(unionMember0: UnionMember0) = unionMember0.validity()
+ override fun visitFixed(fixed: BenefitContributionFixed) = fixed.validity()
- override fun visitUnionMember1(unionMember1: UnionMember1) = unionMember1.validity()
+ override fun visitPercent(percent: BenefitContributionPercent) = percent.validity()
- override fun visitUnionMember2(unionMember2: UnionMember2) = unionMember2.validity()
+ override fun visitTiered(tiered: BenefitContributionTiered) = tiered.validity()
override fun unknown(json: JsonValue?) = 0
}
@@ -124,32 +156,29 @@ private constructor(
}
return other is BenefitContribution &&
- unionMember0 == other.unionMember0 &&
- unionMember1 == other.unionMember1 &&
- unionMember2 == other.unionMember2
+ fixed == other.fixed &&
+ percent == other.percent &&
+ tiered == other.tiered
}
- override fun hashCode(): Int = Objects.hash(unionMember0, unionMember1, unionMember2)
+ override fun hashCode(): Int = Objects.hash(fixed, percent, tiered)
override fun toString(): String =
when {
- unionMember0 != null -> "BenefitContribution{unionMember0=$unionMember0}"
- unionMember1 != null -> "BenefitContribution{unionMember1=$unionMember1}"
- unionMember2 != null -> "BenefitContribution{unionMember2=$unionMember2}"
+ fixed != null -> "BenefitContribution{fixed=$fixed}"
+ percent != null -> "BenefitContribution{percent=$percent}"
+ tiered != null -> "BenefitContribution{tiered=$tiered}"
_json != null -> "BenefitContribution{_unknown=$_json}"
else -> throw IllegalStateException("Invalid BenefitContribution")
}
companion object {
- fun ofUnionMember0(unionMember0: UnionMember0) =
- BenefitContribution(unionMember0 = unionMember0)
+ fun ofFixed(fixed: BenefitContributionFixed) = BenefitContribution(fixed = fixed)
- fun ofUnionMember1(unionMember1: UnionMember1) =
- BenefitContribution(unionMember1 = unionMember1)
+ fun ofPercent(percent: BenefitContributionPercent) = BenefitContribution(percent = percent)
- fun ofUnionMember2(unionMember2: UnionMember2) =
- BenefitContribution(unionMember2 = unionMember2)
+ fun ofTiered(tiered: BenefitContributionTiered) = BenefitContribution(tiered = tiered)
}
/**
@@ -158,11 +187,11 @@ private constructor(
*/
interface Visitor {
- fun visitUnionMember0(unionMember0: UnionMember0): T
+ fun visitFixed(fixed: BenefitContributionFixed): T
- fun visitUnionMember1(unionMember1: UnionMember1): T
+ fun visitPercent(percent: BenefitContributionPercent): T
- fun visitUnionMember2(unionMember2: UnionMember2): T
+ fun visitTiered(tiered: BenefitContributionTiered): T
/**
* Maps an unknown variant of [BenefitContribution] to a value of type [T].
@@ -187,14 +216,14 @@ private constructor(
val bestMatches =
sequenceOf(
- tryDeserialize(node, jacksonTypeRef())?.let {
- BenefitContribution(unionMember0 = it, _json = json)
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ BenefitContribution(fixed = it, _json = json)
},
- tryDeserialize(node, jacksonTypeRef())?.let {
- BenefitContribution(unionMember1 = it, _json = json)
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ BenefitContribution(percent = it, _json = json)
},
- tryDeserialize(node, jacksonTypeRef())?.let {
- BenefitContribution(unionMember2 = it, _json = json)
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ BenefitContribution(tiered = it, _json = json)
},
)
.filterNotNull()
@@ -220,16 +249,16 @@ private constructor(
provider: SerializerProvider,
) {
when {
- value.unionMember0 != null -> generator.writeObject(value.unionMember0)
- value.unionMember1 != null -> generator.writeObject(value.unionMember1)
- value.unionMember2 != null -> generator.writeObject(value.unionMember2)
+ value.fixed != null -> generator.writeObject(value.fixed)
+ value.percent != null -> generator.writeObject(value.percent)
+ value.tiered != null -> generator.writeObject(value.tiered)
value._json != null -> generator.writeObject(value._json)
else -> throw IllegalStateException("Invalid BenefitContribution")
}
}
}
- class UnionMember0
+ class BenefitContributionFixed
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val amount: JsonField,
@@ -288,7 +317,7 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of [UnionMember0].
+ * Returns a mutable builder for constructing an instance of [BenefitContributionFixed].
*
* The following fields are required:
* ```kotlin
@@ -299,17 +328,17 @@ private constructor(
fun builder() = Builder()
}
- /** A builder for [UnionMember0]. */
+ /** A builder for [BenefitContributionFixed]. */
class Builder internal constructor() {
private var amount: JsonField? = null
private var type: JsonField? = null
private var additionalProperties: MutableMap = mutableMapOf()
- internal fun from(unionMember0: UnionMember0) = apply {
- amount = unionMember0.amount
- type = unionMember0.type
- additionalProperties = unionMember0.additionalProperties.toMutableMap()
+ internal fun from(benefitContributionFixed: BenefitContributionFixed) = apply {
+ amount = benefitContributionFixed.amount
+ type = benefitContributionFixed.type
+ additionalProperties = benefitContributionFixed.additionalProperties.toMutableMap()
}
/** Contribution amount in cents. */
@@ -356,7 +385,7 @@ private constructor(
}
/**
- * Returns an immutable instance of [UnionMember0].
+ * Returns an immutable instance of [BenefitContributionFixed].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
@@ -368,8 +397,8 @@ private constructor(
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): UnionMember0 =
- UnionMember0(
+ fun build(): BenefitContributionFixed =
+ BenefitContributionFixed(
checkRequired("amount", amount),
checkRequired("type", type),
additionalProperties.toMutableMap(),
@@ -378,7 +407,16 @@ private constructor(
private var validated: Boolean = false
- fun validate(): UnionMember0 = apply {
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): BenefitContributionFixed = apply {
if (validated) {
return@apply
}
@@ -487,6 +525,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
@@ -530,7 +578,7 @@ private constructor(
return true
}
- return other is UnionMember0 &&
+ return other is BenefitContributionFixed &&
amount == other.amount &&
type == other.type &&
additionalProperties == other.additionalProperties
@@ -541,10 +589,10 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "UnionMember0{amount=$amount, type=$type, additionalProperties=$additionalProperties}"
+ "BenefitContributionFixed{amount=$amount, type=$type, additionalProperties=$additionalProperties}"
}
- class UnionMember1
+ class BenefitContributionPercent
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val amount: JsonField,
@@ -603,7 +651,8 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of [UnionMember1].
+ * Returns a mutable builder for constructing an instance of
+ * [BenefitContributionPercent].
*
* The following fields are required:
* ```kotlin
@@ -614,17 +663,18 @@ private constructor(
fun builder() = Builder()
}
- /** A builder for [UnionMember1]. */
+ /** A builder for [BenefitContributionPercent]. */
class Builder internal constructor() {
private var amount: JsonField? = null
private var type: JsonField? = null
private var additionalProperties: MutableMap = mutableMapOf()
- internal fun from(unionMember1: UnionMember1) = apply {
- amount = unionMember1.amount
- type = unionMember1.type
- additionalProperties = unionMember1.additionalProperties.toMutableMap()
+ internal fun from(benefitContributionPercent: BenefitContributionPercent) = apply {
+ amount = benefitContributionPercent.amount
+ type = benefitContributionPercent.type
+ additionalProperties =
+ benefitContributionPercent.additionalProperties.toMutableMap()
}
/** Contribution amount in basis points (1/100th of a percent). */
@@ -671,7 +721,7 @@ private constructor(
}
/**
- * Returns an immutable instance of [UnionMember1].
+ * Returns an immutable instance of [BenefitContributionPercent].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
@@ -683,8 +733,8 @@ private constructor(
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): UnionMember1 =
- UnionMember1(
+ fun build(): BenefitContributionPercent =
+ BenefitContributionPercent(
checkRequired("amount", amount),
checkRequired("type", type),
additionalProperties.toMutableMap(),
@@ -693,7 +743,16 @@ private constructor(
private var validated: Boolean = false
- fun validate(): UnionMember1 = apply {
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): BenefitContributionPercent = apply {
if (validated) {
return@apply
}
@@ -802,6 +861,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
@@ -845,7 +914,7 @@ private constructor(
return true
}
- return other is UnionMember1 &&
+ return other is BenefitContributionPercent &&
amount == other.amount &&
type == other.type &&
additionalProperties == other.additionalProperties
@@ -856,10 +925,10 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "UnionMember1{amount=$amount, type=$type, additionalProperties=$additionalProperties}"
+ "BenefitContributionPercent{amount=$amount, type=$type, additionalProperties=$additionalProperties}"
}
- class UnionMember2
+ class BenefitContributionTiered
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val tiers: JsonField>,
@@ -919,7 +988,8 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of [UnionMember2].
+ * Returns a mutable builder for constructing an instance of
+ * [BenefitContributionTiered].
*
* The following fields are required:
* ```kotlin
@@ -930,17 +1000,17 @@ private constructor(
fun builder() = Builder()
}
- /** A builder for [UnionMember2]. */
+ /** A builder for [BenefitContributionTiered]. */
class Builder internal constructor() {
private var tiers: JsonField>? = null
private var type: JsonField? = null
private var additionalProperties: MutableMap = mutableMapOf()
- internal fun from(unionMember2: UnionMember2) = apply {
- tiers = unionMember2.tiers.map { it.toMutableList() }
- type = unionMember2.type
- additionalProperties = unionMember2.additionalProperties.toMutableMap()
+ internal fun from(benefitContributionTiered: BenefitContributionTiered) = apply {
+ tiers = benefitContributionTiered.tiers.map { it.toMutableList() }
+ type = benefitContributionTiered.type
+ additionalProperties = benefitContributionTiered.additionalProperties.toMutableMap()
}
/**
@@ -1004,7 +1074,7 @@ private constructor(
}
/**
- * Returns an immutable instance of [UnionMember2].
+ * Returns an immutable instance of [BenefitContributionTiered].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
@@ -1016,8 +1086,8 @@ private constructor(
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): UnionMember2 =
- UnionMember2(
+ fun build(): BenefitContributionTiered =
+ BenefitContributionTiered(
checkRequired("tiers", tiers).map { it.toImmutable() },
checkRequired("type", type),
additionalProperties.toMutableMap(),
@@ -1026,7 +1096,16 @@ private constructor(
private var validated: Boolean = false
- fun validate(): UnionMember2 = apply {
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): BenefitContributionTiered = apply {
if (validated) {
return@apply
}
@@ -1205,6 +1284,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Tier = apply {
if (validated) {
return@apply
@@ -1336,6 +1425,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
@@ -1379,7 +1478,7 @@ private constructor(
return true
}
- return other is UnionMember2 &&
+ return other is BenefitContributionTiered &&
tiers == other.tiers &&
type == other.type &&
additionalProperties == other.additionalProperties
@@ -1390,6 +1489,6 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "UnionMember2{tiers=$tiers, type=$type, additionalProperties=$additionalProperties}"
+ "BenefitContributionTiered{tiers=$tiers, type=$type, additionalProperties=$additionalProperties}"
}
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFeaturesAndOperations.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFeaturesAndOperations.kt
index 2308c1fb5..ca26dc21b 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFeaturesAndOperations.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFeaturesAndOperations.kt
@@ -160,6 +160,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BenefitFeaturesAndOperations = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFrequency.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFrequency.kt
index c6230e143..22c3a829c 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFrequency.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitFrequency.kt
@@ -102,6 +102,14 @@ class BenefitFrequency @JsonCreator private constructor(private val value: JsonF
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BenefitFrequency = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitType.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitType.kt
index df37990d2..e9bc17e59 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitType.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitType.kt
@@ -195,6 +195,14 @@ class BenefitType @JsonCreator private constructor(private val value: JsonField<
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BenefitType = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitsSupport.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitsSupport.kt
index 2cf7322e2..b40dcc175 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitsSupport.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/BenefitsSupport.kt
@@ -529,6 +529,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): BenefitsSupport = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Company.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Company.kt
index 60af017b4..34058ba5c 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Company.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Company.kt
@@ -481,6 +481,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): Company = apply {
if (validated) {
return@apply
@@ -820,6 +828,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Account = apply {
if (validated) {
return@apply
@@ -946,6 +963,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AccountType = apply {
if (validated) {
return@apply
@@ -1164,6 +1191,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Department = apply {
if (validated) {
return@apply
@@ -1307,6 +1343,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Parent = apply {
if (validated) {
return@apply
@@ -1519,6 +1565,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Entity = apply {
if (validated) {
return@apply
@@ -1643,6 +1698,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Subtype = apply {
if (validated) {
return@apply
@@ -1799,6 +1864,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyBenefit.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyBenefit.kt
index ea00629d0..337ddeab7 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyBenefit.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyBenefit.kt
@@ -281,6 +281,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): CompanyBenefit = apply {
if (validated) {
return@apply
@@ -475,6 +483,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): BenefitCompanyMatchContribution = apply {
if (validated) {
return@apply
@@ -654,6 +671,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Tier = apply {
if (validated) {
return@apply
@@ -784,6 +811,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyEvent.kt
index 66685b083..0d49a6231 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyEvent.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyEvent.kt
@@ -23,6 +23,7 @@ private constructor(
private val accountId: JsonField,
private val companyId: JsonField,
private val connectionId: JsonField,
+ private val entityId: JsonField,
private val data: JsonField,
private val eventType: JsonField,
private val additionalProperties: MutableMap,
@@ -35,17 +36,19 @@ private constructor(
@JsonProperty("connection_id")
@ExcludeMissing
connectionId: JsonField = JsonMissing.of(),
+ @JsonProperty("entity_id") @ExcludeMissing entityId: JsonField = JsonMissing.of(),
@JsonProperty("data") @ExcludeMissing data: JsonField = JsonMissing.of(),
@JsonProperty("event_type")
@ExcludeMissing
eventType: JsonField = JsonMissing.of(),
- ) : this(accountId, companyId, connectionId, data, eventType, mutableMapOf())
+ ) : this(accountId, companyId, connectionId, entityId, data, eventType, mutableMapOf())
fun toBaseWebhookEvent(): BaseWebhookEvent =
BaseWebhookEvent.builder()
.accountId(accountId)
.companyId(companyId)
.connectionId(connectionId)
+ .entityId(entityId)
.build()
/**
@@ -74,6 +77,14 @@ private constructor(
*/
fun connectionId(): String? = connectionId.getNullable("connection_id")
+ /**
+ * Unique Finch id of the entity for which data has been updated.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun entityId(): String? = entityId.getNullable("entity_id")
+
/**
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -115,6 +126,13 @@ private constructor(
@ExcludeMissing
fun _connectionId(): JsonField = connectionId
+ /**
+ * Returns the raw JSON value of [entityId].
+ *
+ * Unlike [entityId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("entity_id") @ExcludeMissing fun _entityId(): JsonField = entityId
+
/**
* Returns the raw JSON value of [data].
*
@@ -161,6 +179,7 @@ private constructor(
private var accountId: JsonField? = null
private var companyId: JsonField? = null
private var connectionId: JsonField = JsonMissing.of()
+ private var entityId: JsonField = JsonMissing.of()
private var data: JsonField = JsonMissing.of()
private var eventType: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -169,6 +188,7 @@ private constructor(
accountId = companyEvent.accountId
companyId = companyEvent.companyId
connectionId = companyEvent.connectionId
+ entityId = companyEvent.entityId
data = companyEvent.data
eventType = companyEvent.eventType
additionalProperties = companyEvent.additionalProperties.toMutableMap()
@@ -222,6 +242,17 @@ private constructor(
this.connectionId = connectionId
}
+ /** Unique Finch id of the entity for which data has been updated. */
+ fun entityId(entityId: String) = entityId(JsonField.of(entityId))
+
+ /**
+ * Sets [Builder.entityId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.entityId] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun entityId(entityId: JsonField) = apply { this.entityId = entityId }
+
fun data(data: Data?) = data(JsonField.ofNullable(data))
/**
@@ -280,6 +311,7 @@ private constructor(
checkRequired("accountId", accountId),
checkRequired("companyId", companyId),
connectionId,
+ entityId,
data,
eventType,
additionalProperties.toMutableMap(),
@@ -288,6 +320,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): CompanyEvent = apply {
if (validated) {
return@apply
@@ -296,6 +336,7 @@ private constructor(
accountId()
companyId()
connectionId()
+ entityId()
data()?.validate()
eventType()?.validate()
validated = true
@@ -318,6 +359,7 @@ private constructor(
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
(if (connectionId.asKnown() == null) 0 else 1) +
+ (if (entityId.asKnown() == null) 0 else 1) +
(data.asKnown()?.validity() ?: 0) +
(eventType.asKnown()?.validity() ?: 0)
@@ -378,6 +420,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Data = apply {
if (validated) {
return@apply
@@ -500,6 +551,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): EventType = apply {
if (validated) {
return@apply
@@ -547,17 +607,26 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
connectionId == other.connectionId &&
+ entityId == other.entityId &&
data == other.data &&
eventType == other.eventType &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(accountId, companyId, connectionId, data, eventType, additionalProperties)
+ Objects.hash(
+ accountId,
+ companyId,
+ connectionId,
+ entityId,
+ data,
+ eventType,
+ additionalProperties,
+ )
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "CompanyEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
+ "CompanyEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, entityId=$entityId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyUpdateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyUpdateResponse.kt
index 06edbc5c0..c78a30dde 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyUpdateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CompanyUpdateResponse.kt
@@ -447,6 +447,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): CompanyUpdateResponse = apply {
if (validated) {
return@apply
@@ -762,6 +770,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Account = apply {
if (validated) {
return@apply
@@ -888,6 +905,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AccountType = apply {
if (validated) {
return@apply
@@ -1085,6 +1112,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Department = apply {
if (validated) {
return@apply
@@ -1213,6 +1249,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Parent = apply {
if (validated) {
return@apply
@@ -1404,6 +1450,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Entity = apply {
if (validated) {
return@apply
@@ -1528,6 +1583,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Subtype = apply {
if (validated) {
return@apply
@@ -1684,6 +1749,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionNewParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionNewParams.kt
index ad70c0beb..4ff68340d 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionNewParams.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionNewParams.kt
@@ -46,7 +46,9 @@ private constructor(
fun customerName(): String = body.customerName()
/**
- * The Finch products to request access to
+ * The Finch products to request access to. Use `benefits` to access deductions endpoints —
+ * `deduction` is a deprecated alias that is still accepted but should not be combined with
+ * `benefits`.
*
* @throws FinchInvalidDataException if the JSON field has an unexpected type or is unexpectedly
* missing or null (e.g. if the server responded with an unexpected value).
@@ -243,7 +245,11 @@ private constructor(
body.customerName(customerName)
}
- /** The Finch products to request access to */
+ /**
+ * The Finch products to request access to. Use `benefits` to access deductions endpoints —
+ * `deduction` is a deprecated alias that is still accepted but should not be combined with
+ * `benefits`.
+ */
fun products(products: List) = apply { body.products(products) }
/**
@@ -571,7 +577,9 @@ private constructor(
fun customerName(): String = customerName.getRequired("customer_name")
/**
- * The Finch products to request access to
+ * The Finch products to request access to. Use `benefits` to access deductions endpoints —
+ * `deduction` is a deprecated alias that is still accepted but should not be combined with
+ * `benefits`.
*
* @throws FinchInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
@@ -788,7 +796,11 @@ private constructor(
this.customerName = customerName
}
- /** The Finch products to request access to */
+ /**
+ * The Finch products to request access to. Use `benefits` to access deductions
+ * endpoints — `deduction` is a deprecated alias that is still accepted but should not
+ * be combined with `benefits`.
+ */
fun products(products: List) = products(JsonField.of(products))
/**
@@ -965,6 +977,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): CreateConnectSessionRequest = apply {
if (validated) {
return@apply
@@ -1046,7 +1067,6 @@ private constructor(
"CreateConnectSessionRequest{customerId=$customerId, customerName=$customerName, products=$products, customerEmail=$customerEmail, integration=$integration, manual=$manual, minutesToExpire=$minutesToExpire, redirectUri=$redirectUri, sandbox=$sandbox, additionalProperties=$additionalProperties}"
}
- /** The Finch products that can be requested during the Connect flow. */
class ConnectProducts @JsonCreator private constructor(private val value: JsonField) :
Enum {
@@ -1185,6 +1205,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): ConnectProducts = apply {
if (validated) {
return@apply
@@ -1379,6 +1408,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Integration = apply {
if (validated) {
return@apply
@@ -1510,6 +1548,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AuthMethod = apply {
if (validated) {
return@apply
@@ -1656,6 +1704,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Sandbox = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionReauthenticateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionReauthenticateParams.kt
index bef57644d..fdd219107 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionReauthenticateParams.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectSessionReauthenticateParams.kt
@@ -46,7 +46,9 @@ private constructor(
fun minutesToExpire(): Long? = body.minutesToExpire()
/**
- * The products to request access to (optional for reauthentication)
+ * The products to request access to (optional for reauthentication). Use `benefits` to access
+ * deductions endpoints — `deduction` is a deprecated alias that is still accepted but should
+ * not be combined with `benefits`.
*
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -170,7 +172,11 @@ private constructor(
body.minutesToExpire(minutesToExpire)
}
- /** The products to request access to (optional for reauthentication) */
+ /**
+ * The products to request access to (optional for reauthentication). Use `benefits` to
+ * access deductions endpoints — `deduction` is a deprecated alias that is still accepted
+ * but should not be combined with `benefits`.
+ */
fun products(products: List?) = apply { body.products(products) }
/**
@@ -387,7 +393,9 @@ private constructor(
fun minutesToExpire(): Long? = minutesToExpire.getNullable("minutes_to_expire")
/**
- * The products to request access to (optional for reauthentication)
+ * The products to request access to (optional for reauthentication). Use `benefits` to
+ * access deductions endpoints — `deduction` is a deprecated alias that is still accepted
+ * but should not be combined with `benefits`.
*
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -514,7 +522,11 @@ private constructor(
this.minutesToExpire = minutesToExpire
}
- /** The products to request access to (optional for reauthentication) */
+ /**
+ * The products to request access to (optional for reauthentication). Use `benefits` to
+ * access deductions endpoints — `deduction` is a deprecated alias that is still
+ * accepted but should not be combined with `benefits`.
+ */
fun products(products: List?) =
products(JsonField.ofNullable(products))
@@ -598,6 +610,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): ReauthenticateRequest = apply {
if (validated) {
return@apply
@@ -653,7 +674,6 @@ private constructor(
"ReauthenticateRequest{connectionId=$connectionId, minutesToExpire=$minutesToExpire, products=$products, redirectUri=$redirectUri, additionalProperties=$additionalProperties}"
}
- /** The Finch products that can be requested during the Connect flow. */
class ConnectProducts @JsonCreator private constructor(private val value: JsonField) :
Enum {
@@ -792,6 +812,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): ConnectProducts = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionCreateResponse.kt
index 9cb6ffb42..52dd61867 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionCreateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionCreateResponse.kt
@@ -448,6 +448,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): ConnectionCreateResponse = apply {
if (validated) {
return@apply
@@ -592,6 +600,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): AuthenticationType = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionStatusType.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionStatusType.kt
index e25b8aa96..dd228bcda 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionStatusType.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ConnectionStatusType.kt
@@ -120,6 +120,14 @@ class ConnectionStatusType @JsonCreator private constructor(private val value: J
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): ConnectionStatusType = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateAccessTokenResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateAccessTokenResponse.kt
index 130c48fd6..2f726bc9b 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateAccessTokenResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateAccessTokenResponse.kt
@@ -32,6 +32,7 @@ private constructor(
private val accountId: JsonField,
private val companyId: JsonField,
private val customerId: JsonField,
+ private val customerName: JsonField,
private val additionalProperties: MutableMap,
) {
@@ -64,6 +65,9 @@ private constructor(
@JsonProperty("customer_id")
@ExcludeMissing
customerId: JsonField = JsonMissing.of(),
+ @JsonProperty("customer_name")
+ @ExcludeMissing
+ customerName: JsonField = JsonMissing.of(),
) : this(
accessToken,
clientType,
@@ -76,6 +80,7 @@ private constructor(
accountId,
companyId,
customerId,
+ customerName,
mutableMapOf(),
)
@@ -170,6 +175,15 @@ private constructor(
*/
fun customerId(): String? = customerId.getNullable("customer_id")
+ /**
+ * The name of your customer you provided to Finch when a connect session was created for this
+ * connection
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun customerName(): String? = customerName.getNullable("customer_name")
+
/**
* Returns the raw JSON value of [accessToken].
*
@@ -263,6 +277,15 @@ private constructor(
*/
@JsonProperty("customer_id") @ExcludeMissing fun _customerId(): JsonField = customerId
+ /**
+ * Returns the raw JSON value of [customerName].
+ *
+ * Unlike [customerName], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("customer_name")
+ @ExcludeMissing
+ fun _customerName(): JsonField = customerName
+
@JsonAnySetter
private fun putAdditionalProperty(key: String, value: JsonValue) {
additionalProperties.put(key, value)
@@ -309,6 +332,7 @@ private constructor(
private var accountId: JsonField = JsonMissing.of()
private var companyId: JsonField = JsonMissing.of()
private var customerId: JsonField = JsonMissing.of()
+ private var customerName: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
internal fun from(createAccessTokenResponse: CreateAccessTokenResponse) = apply {
@@ -323,6 +347,7 @@ private constructor(
accountId = createAccessTokenResponse.accountId
companyId = createAccessTokenResponse.companyId
customerId = createAccessTokenResponse.customerId
+ customerName = createAccessTokenResponse.customerName
additionalProperties = createAccessTokenResponse.additionalProperties.toMutableMap()
}
@@ -506,6 +531,23 @@ private constructor(
*/
fun customerId(customerId: JsonField) = apply { this.customerId = customerId }
+ /**
+ * The name of your customer you provided to Finch when a connect session was created for
+ * this connection
+ */
+ fun customerName(customerName: String?) = customerName(JsonField.ofNullable(customerName))
+
+ /**
+ * Sets [Builder.customerName] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.customerName] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun customerName(customerName: JsonField) = apply {
+ this.customerName = customerName
+ }
+
fun additionalProperties(additionalProperties: Map) = apply {
this.additionalProperties.clear()
putAllAdditionalProperties(additionalProperties)
@@ -557,12 +599,21 @@ private constructor(
accountId,
companyId,
customerId,
+ customerName,
additionalProperties.toMutableMap(),
)
}
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): CreateAccessTokenResponse = apply {
if (validated) {
return@apply
@@ -579,6 +630,7 @@ private constructor(
accountId()
companyId()
customerId()
+ customerName()
validated = true
}
@@ -606,7 +658,8 @@ private constructor(
(if (tokenType.asKnown() == null) 0 else 1) +
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
- (if (customerId.asKnown() == null) 0 else 1)
+ (if (customerId.asKnown() == null) 0 else 1) +
+ (if (customerName.asKnown() == null) 0 else 1)
/** The type of application associated with a token. */
class ClientType @JsonCreator private constructor(private val value: JsonField) : Enum {
@@ -703,6 +756,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): ClientType = apply {
if (validated) {
return@apply
@@ -836,6 +898,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): ConnectionType = apply {
if (validated) {
return@apply
@@ -891,6 +962,7 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
customerId == other.customerId &&
+ customerName == other.customerName &&
additionalProperties == other.additionalProperties
}
@@ -907,6 +979,7 @@ private constructor(
accountId,
companyId,
customerId,
+ customerName,
additionalProperties,
)
}
@@ -914,5 +987,5 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "CreateAccessTokenResponse{accessToken=$accessToken, clientType=$clientType, connectionId=$connectionId, connectionType=$connectionType, entityIds=$entityIds, products=$products, providerId=$providerId, tokenType=$tokenType, accountId=$accountId, companyId=$companyId, customerId=$customerId, additionalProperties=$additionalProperties}"
+ "CreateAccessTokenResponse{accessToken=$accessToken, clientType=$clientType, connectionId=$connectionId, connectionType=$connectionType, entityIds=$entityIds, products=$products, providerId=$providerId, tokenType=$tokenType, accountId=$accountId, companyId=$companyId, customerId=$customerId, customerName=$customerName, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateCompanyBenefitsResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateCompanyBenefitsResponse.kt
index 1f8abf1eb..27420bbce 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateCompanyBenefitsResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/CreateCompanyBenefitsResponse.kt
@@ -161,6 +161,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): CreateCompanyBenefitsResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryCreateResponse.kt
index 1326d12cf..a25eac39f 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryCreateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryCreateResponse.kt
@@ -74,6 +74,14 @@ private constructor(private val additionalProperties: MutableMap,
private val companyId: JsonField,
private val connectionId: JsonField,
+ private val entityId: JsonField,
private val data: JsonField,
private val eventType: JsonField,
private val additionalProperties: MutableMap,
@@ -34,17 +35,19 @@ private constructor(
@JsonProperty("connection_id")
@ExcludeMissing
connectionId: JsonField = JsonMissing.of(),
+ @JsonProperty("entity_id") @ExcludeMissing entityId: JsonField = JsonMissing.of(),
@JsonProperty("data") @ExcludeMissing data: JsonField = JsonMissing.of(),
@JsonProperty("event_type")
@ExcludeMissing
eventType: JsonField = JsonMissing.of(),
- ) : this(accountId, companyId, connectionId, data, eventType, mutableMapOf())
+ ) : this(accountId, companyId, connectionId, entityId, data, eventType, mutableMapOf())
fun toBaseWebhookEvent(): BaseWebhookEvent =
BaseWebhookEvent.builder()
.accountId(accountId)
.companyId(companyId)
.connectionId(connectionId)
+ .entityId(entityId)
.build()
/**
@@ -73,6 +76,14 @@ private constructor(
*/
fun connectionId(): String? = connectionId.getNullable("connection_id")
+ /**
+ * Unique Finch id of the entity for which data has been updated.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun entityId(): String? = entityId.getNullable("entity_id")
+
/**
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -114,6 +125,13 @@ private constructor(
@ExcludeMissing
fun _connectionId(): JsonField = connectionId
+ /**
+ * Returns the raw JSON value of [entityId].
+ *
+ * Unlike [entityId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("entity_id") @ExcludeMissing fun _entityId(): JsonField = entityId
+
/**
* Returns the raw JSON value of [data].
*
@@ -160,6 +178,7 @@ private constructor(
private var accountId: JsonField? = null
private var companyId: JsonField? = null
private var connectionId: JsonField = JsonMissing.of()
+ private var entityId: JsonField = JsonMissing.of()
private var data: JsonField = JsonMissing.of()
private var eventType: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -168,6 +187,7 @@ private constructor(
accountId = directoryEvent.accountId
companyId = directoryEvent.companyId
connectionId = directoryEvent.connectionId
+ entityId = directoryEvent.entityId
data = directoryEvent.data
eventType = directoryEvent.eventType
additionalProperties = directoryEvent.additionalProperties.toMutableMap()
@@ -221,6 +241,17 @@ private constructor(
this.connectionId = connectionId
}
+ /** Unique Finch id of the entity for which data has been updated. */
+ fun entityId(entityId: String) = entityId(JsonField.of(entityId))
+
+ /**
+ * Sets [Builder.entityId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.entityId] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun entityId(entityId: JsonField) = apply { this.entityId = entityId }
+
fun data(data: Data) = data(JsonField.of(data))
/**
@@ -279,6 +310,7 @@ private constructor(
checkRequired("accountId", accountId),
checkRequired("companyId", companyId),
connectionId,
+ entityId,
data,
eventType,
additionalProperties.toMutableMap(),
@@ -287,6 +319,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DirectoryEvent = apply {
if (validated) {
return@apply
@@ -295,6 +335,7 @@ private constructor(
accountId()
companyId()
connectionId()
+ entityId()
data()?.validate()
eventType()?.validate()
validated = true
@@ -317,6 +358,7 @@ private constructor(
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
(if (connectionId.asKnown() == null) 0 else 1) +
+ (if (entityId.asKnown() == null) 0 else 1) +
(data.asKnown()?.validity() ?: 0) +
(eventType.asKnown()?.validity() ?: 0)
@@ -424,6 +466,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Data = apply {
if (validated) {
return@apply
@@ -561,6 +612,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): EventType = apply {
if (validated) {
return@apply
@@ -608,17 +668,26 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
connectionId == other.connectionId &&
+ entityId == other.entityId &&
data == other.data &&
eventType == other.eventType &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(accountId, companyId, connectionId, data, eventType, additionalProperties)
+ Objects.hash(
+ accountId,
+ companyId,
+ connectionId,
+ entityId,
+ data,
+ eventType,
+ additionalProperties,
+ )
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "DirectoryEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
+ "DirectoryEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, entityId=$entityId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryListIndividualsResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryListIndividualsResponse.kt
index 42ae65733..3ca44d9a5 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryListIndividualsResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DirectoryListIndividualsResponse.kt
@@ -185,6 +185,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DirectoryListIndividualsResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DisconnectResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DisconnectResponse.kt
index eab0047fe..13bef2797 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DisconnectResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DisconnectResponse.kt
@@ -126,6 +126,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DisconnectResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentListResponse.kt
index d1f49ad5d..b3a644829 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentListResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentListResponse.kt
@@ -177,6 +177,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DocumentListResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentResponse.kt
index 8245bea26..037df4b6c 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentResponse.kt
@@ -277,6 +277,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DocumentResponse = apply {
if (validated) {
return@apply
@@ -397,6 +405,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentRetreiveResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentRetreiveResponse.kt
index a5e39e73d..94d6f553b 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentRetreiveResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/DocumentRetreiveResponse.kt
@@ -59,6 +59,30 @@ private constructor(
fun _json(): JsonValue? = _json
+ /**
+ * Maps this instance's current variant to a value of type [T] using the given [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the API, unless
+ * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of the
+ * SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```kotlin
+ * import com.tryfinch.api.core.JsonValue
+ *
+ * val result: String? = documentRetreiveResponse.accept(object : DocumentRetreiveResponse.Visitor {
+ * override fun visitW42020(w42020: W42020): String? = w42020.toString()
+ *
+ * // ...
+ *
+ * override fun unknown(json: JsonValue?): String? {
+ * // Or inspect the `json`.
+ * return null
+ * }
+ * })
+ * ```
+ *
+ * @throws FinchInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and the
+ * current variant is unknown.
+ */
fun accept(visitor: Visitor): T =
when {
w42020 != null -> visitor.visitW42020(w42020)
@@ -68,6 +92,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): DocumentRetreiveResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentData.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentData.kt
index 67fcc0f74..3bceff66c 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentData.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentData.kt
@@ -33,34 +33,66 @@ import java.util.Objects
@JsonSerialize(using = EmploymentData.Serializer::class)
class EmploymentData
private constructor(
- private val unionMember0: UnionMember0? = null,
+ private val responseBody: EmploymentDataResponseBody? = null,
private val batchError: BatchError? = null,
private val _json: JsonValue? = null,
) {
- fun unionMember0(): UnionMember0? = unionMember0
+ fun responseBody(): EmploymentDataResponseBody? = responseBody
fun batchError(): BatchError? = batchError
- fun isUnionMember0(): Boolean = unionMember0 != null
+ fun isResponseBody(): Boolean = responseBody != null
fun isBatchError(): Boolean = batchError != null
- fun asUnionMember0(): UnionMember0 = unionMember0.getOrThrow("unionMember0")
+ fun asResponseBody(): EmploymentDataResponseBody = responseBody.getOrThrow("responseBody")
fun asBatchError(): BatchError = batchError.getOrThrow("batchError")
fun _json(): JsonValue? = _json
+ /**
+ * Maps this instance's current variant to a value of type [T] using the given [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the API, unless
+ * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of the
+ * SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```kotlin
+ * import com.tryfinch.api.core.JsonValue
+ *
+ * val result: String? = employmentData.accept(object : EmploymentData.Visitor {
+ * override fun visitResponseBody(responseBody: EmploymentDataResponseBody): String? = responseBody.toString()
+ *
+ * // ...
+ *
+ * override fun unknown(json: JsonValue?): String? {
+ * // Or inspect the `json`.
+ * return null
+ * }
+ * })
+ * ```
+ *
+ * @throws FinchInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and the
+ * current variant is unknown.
+ */
fun accept(visitor: Visitor): T =
when {
- unionMember0 != null -> visitor.visitUnionMember0(unionMember0)
+ responseBody != null -> visitor.visitResponseBody(responseBody)
batchError != null -> visitor.visitBatchError(batchError)
else -> visitor.unknown(_json)
}
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): EmploymentData = apply {
if (validated) {
return@apply
@@ -68,8 +100,8 @@ private constructor(
accept(
object : Visitor {
- override fun visitUnionMember0(unionMember0: UnionMember0) {
- unionMember0.validate()
+ override fun visitResponseBody(responseBody: EmploymentDataResponseBody) {
+ responseBody.validate()
}
override fun visitBatchError(batchError: BatchError) {
@@ -96,7 +128,8 @@ private constructor(
internal fun validity(): Int =
accept(
object : Visitor {
- override fun visitUnionMember0(unionMember0: UnionMember0) = unionMember0.validity()
+ override fun visitResponseBody(responseBody: EmploymentDataResponseBody) =
+ responseBody.validity()
override fun visitBatchError(batchError: BatchError) = batchError.validity()
@@ -110,15 +143,15 @@ private constructor(
}
return other is EmploymentData &&
- unionMember0 == other.unionMember0 &&
+ responseBody == other.responseBody &&
batchError == other.batchError
}
- override fun hashCode(): Int = Objects.hash(unionMember0, batchError)
+ override fun hashCode(): Int = Objects.hash(responseBody, batchError)
override fun toString(): String =
when {
- unionMember0 != null -> "EmploymentData{unionMember0=$unionMember0}"
+ responseBody != null -> "EmploymentData{responseBody=$responseBody}"
batchError != null -> "EmploymentData{batchError=$batchError}"
_json != null -> "EmploymentData{_unknown=$_json}"
else -> throw IllegalStateException("Invalid EmploymentData")
@@ -126,7 +159,8 @@ private constructor(
companion object {
- fun ofUnionMember0(unionMember0: UnionMember0) = EmploymentData(unionMember0 = unionMember0)
+ fun ofResponseBody(responseBody: EmploymentDataResponseBody) =
+ EmploymentData(responseBody = responseBody)
fun ofBatchError(batchError: BatchError) = EmploymentData(batchError = batchError)
}
@@ -136,7 +170,7 @@ private constructor(
*/
interface Visitor {
- fun visitUnionMember0(unionMember0: UnionMember0): T
+ fun visitResponseBody(responseBody: EmploymentDataResponseBody): T
fun visitBatchError(batchError: BatchError): T
@@ -162,8 +196,8 @@ private constructor(
val bestMatches =
sequenceOf(
- tryDeserialize(node, jacksonTypeRef())?.let {
- EmploymentData(unionMember0 = it, _json = json)
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ EmploymentData(responseBody = it, _json = json)
},
tryDeserialize(node, jacksonTypeRef())?.let {
EmploymentData(batchError = it, _json = json)
@@ -192,7 +226,7 @@ private constructor(
provider: SerializerProvider,
) {
when {
- value.unionMember0 != null -> generator.writeObject(value.unionMember0)
+ value.responseBody != null -> generator.writeObject(value.responseBody)
value.batchError != null -> generator.writeObject(value.batchError)
value._json != null -> generator.writeObject(value._json)
else -> throw IllegalStateException("Invalid EmploymentData")
@@ -200,7 +234,7 @@ private constructor(
}
}
- class UnionMember0
+ class EmploymentDataResponseBody
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val id: JsonField,
@@ -210,6 +244,7 @@ private constructor(
private val employmentStatus: JsonField,
private val endDate: JsonField,
private val firstName: JsonField,
+ private val flsaStatus: JsonField,
private val isActive: JsonField,
private val lastName: JsonField,
private val latestRehireDate: JsonField,
@@ -245,6 +280,9 @@ private constructor(
@JsonProperty("first_name")
@ExcludeMissing
firstName: JsonField = JsonMissing.of(),
+ @JsonProperty("flsa_status")
+ @ExcludeMissing
+ flsaStatus: JsonField = JsonMissing.of(),
@JsonProperty("is_active")
@ExcludeMissing
isActive: JsonField = JsonMissing.of(),
@@ -284,6 +322,7 @@ private constructor(
employmentStatus,
endDate,
firstName,
+ flsaStatus,
isActive,
lastName,
latestRehireDate,
@@ -355,6 +394,14 @@ private constructor(
*/
fun firstName(): String? = firstName.getNullable("first_name")
+ /**
+ * The FLSA status of the individual. Available options: `exempt`, `non_exempt`, `unknown`.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun flsaStatus(): FlsaStatus? = flsaStatus.getNullable("flsa_status")
+
/**
* `true` if the individual an an active employee or contractor at the company.
*
@@ -512,6 +559,15 @@ private constructor(
*/
@JsonProperty("first_name") @ExcludeMissing fun _firstName(): JsonField = firstName
+ /**
+ * Returns the raw JSON value of [flsaStatus].
+ *
+ * Unlike [flsaStatus], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("flsa_status")
+ @ExcludeMissing
+ fun _flsaStatus(): JsonField = flsaStatus
+
/**
* Returns the raw JSON value of [isActive].
*
@@ -632,7 +688,8 @@ private constructor(
companion object {
/**
- * Returns a mutable builder for constructing an instance of [UnionMember0].
+ * Returns a mutable builder for constructing an instance of
+ * [EmploymentDataResponseBody].
*
* The following fields are required:
* ```kotlin
@@ -643,6 +700,7 @@ private constructor(
* .employmentStatus()
* .endDate()
* .firstName()
+ * .flsaStatus()
* .isActive()
* .lastName()
* .latestRehireDate()
@@ -656,7 +714,7 @@ private constructor(
fun builder() = Builder()
}
- /** A builder for [UnionMember0]. */
+ /** A builder for [EmploymentDataResponseBody]. */
class Builder internal constructor() {
private var id: JsonField? = null
@@ -666,6 +724,7 @@ private constructor(
private var employmentStatus: JsonField? = null
private var endDate: JsonField? = null
private var firstName: JsonField? = null
+ private var flsaStatus: JsonField? = null
private var isActive: JsonField? = null
private var lastName: JsonField? = null
private var latestRehireDate: JsonField? = null
@@ -681,28 +740,30 @@ private constructor(
private var workId: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
- internal fun from(unionMember0: UnionMember0) = apply {
- id = unionMember0.id
- classCode = unionMember0.classCode
- department = unionMember0.department
- employment = unionMember0.employment
- employmentStatus = unionMember0.employmentStatus
- endDate = unionMember0.endDate
- firstName = unionMember0.firstName
- isActive = unionMember0.isActive
- lastName = unionMember0.lastName
- latestRehireDate = unionMember0.latestRehireDate
- location = unionMember0.location
- manager = unionMember0.manager
- middleName = unionMember0.middleName
- startDate = unionMember0.startDate
- title = unionMember0.title
- customFields = unionMember0.customFields.map { it.toMutableList() }
- income = unionMember0.income
- incomeHistory = unionMember0.incomeHistory.map { it.toMutableList() }
- sourceId = unionMember0.sourceId
- workId = unionMember0.workId
- additionalProperties = unionMember0.additionalProperties.toMutableMap()
+ internal fun from(employmentDataResponseBody: EmploymentDataResponseBody) = apply {
+ id = employmentDataResponseBody.id
+ classCode = employmentDataResponseBody.classCode
+ department = employmentDataResponseBody.department
+ employment = employmentDataResponseBody.employment
+ employmentStatus = employmentDataResponseBody.employmentStatus
+ endDate = employmentDataResponseBody.endDate
+ firstName = employmentDataResponseBody.firstName
+ flsaStatus = employmentDataResponseBody.flsaStatus
+ isActive = employmentDataResponseBody.isActive
+ lastName = employmentDataResponseBody.lastName
+ latestRehireDate = employmentDataResponseBody.latestRehireDate
+ location = employmentDataResponseBody.location
+ manager = employmentDataResponseBody.manager
+ middleName = employmentDataResponseBody.middleName
+ startDate = employmentDataResponseBody.startDate
+ title = employmentDataResponseBody.title
+ customFields = employmentDataResponseBody.customFields.map { it.toMutableList() }
+ income = employmentDataResponseBody.income
+ incomeHistory = employmentDataResponseBody.incomeHistory.map { it.toMutableList() }
+ sourceId = employmentDataResponseBody.sourceId
+ workId = employmentDataResponseBody.workId
+ additionalProperties =
+ employmentDataResponseBody.additionalProperties.toMutableMap()
}
/** A stable Finch `id` (UUID v4) for an individual in the company. */
@@ -795,6 +856,23 @@ private constructor(
*/
fun firstName(firstName: JsonField) = apply { this.firstName = firstName }
+ /**
+ * The FLSA status of the individual. Available options: `exempt`, `non_exempt`,
+ * `unknown`.
+ */
+ fun flsaStatus(flsaStatus: FlsaStatus?) = flsaStatus(JsonField.ofNullable(flsaStatus))
+
+ /**
+ * Sets [Builder.flsaStatus] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.flsaStatus] with a well-typed [FlsaStatus] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun flsaStatus(flsaStatus: JsonField) = apply {
+ this.flsaStatus = flsaStatus
+ }
+
/** `true` if the individual an an active employee or contractor at the company. */
fun isActive(isActive: Boolean?) = isActive(JsonField.ofNullable(isActive))
@@ -1017,7 +1095,7 @@ private constructor(
}
/**
- * Returns an immutable instance of [UnionMember0].
+ * Returns an immutable instance of [EmploymentDataResponseBody].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
@@ -1030,6 +1108,7 @@ private constructor(
* .employmentStatus()
* .endDate()
* .firstName()
+ * .flsaStatus()
* .isActive()
* .lastName()
* .latestRehireDate()
@@ -1042,8 +1121,8 @@ private constructor(
*
* @throws IllegalStateException if any required field is unset.
*/
- fun build(): UnionMember0 =
- UnionMember0(
+ fun build(): EmploymentDataResponseBody =
+ EmploymentDataResponseBody(
checkRequired("id", id),
checkRequired("classCode", classCode),
checkRequired("department", department),
@@ -1051,6 +1130,7 @@ private constructor(
checkRequired("employmentStatus", employmentStatus),
checkRequired("endDate", endDate),
checkRequired("firstName", firstName),
+ checkRequired("flsaStatus", flsaStatus),
checkRequired("isActive", isActive),
checkRequired("lastName", lastName),
checkRequired("latestRehireDate", latestRehireDate),
@@ -1070,7 +1150,16 @@ private constructor(
private var validated: Boolean = false
- fun validate(): UnionMember0 = apply {
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): EmploymentDataResponseBody = apply {
if (validated) {
return@apply
}
@@ -1082,6 +1171,7 @@ private constructor(
employmentStatus()?.validate()
endDate()
firstName()
+ flsaStatus()?.validate()
isActive()
lastName()
latestRehireDate()
@@ -1120,6 +1210,7 @@ private constructor(
(employmentStatus.asKnown()?.validity() ?: 0) +
(if (endDate.asKnown() == null) 0 else 1) +
(if (firstName.asKnown() == null) 0 else 1) +
+ (flsaStatus.asKnown()?.validity() ?: 0) +
(if (isActive.asKnown() == null) 0 else 1) +
(if (lastName.asKnown() == null) 0 else 1) +
(if (latestRehireDate.asKnown() == null) 0 else 1) +
@@ -1250,6 +1341,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Department = apply {
if (validated) {
return@apply
@@ -1452,6 +1553,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Employment = apply {
if (validated) {
return@apply
@@ -1598,6 +1709,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): Subtype = apply {
if (validated) {
return@apply
@@ -1727,6 +1848,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): Type = apply {
if (validated) {
return@apply
@@ -1908,6 +2039,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): EmploymentStatus = apply {
if (validated) {
return@apply
@@ -1946,6 +2087,154 @@ private constructor(
override fun toString() = value.toString()
}
+ /**
+ * The FLSA status of the individual. Available options: `exempt`, `non_exempt`, `unknown`.
+ */
+ class FlsaStatus @JsonCreator private constructor(private val value: JsonField) :
+ Enum {
+
+ /**
+ * Returns this class instance's raw value.
+ *
+ * This is usually only useful if this instance was deserialized from data that doesn't
+ * match any known member, and you want to know that value. For example, if the SDK is
+ * on an older version than the API, then the API may respond with new members that the
+ * SDK is unaware of.
+ */
+ @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
+
+ companion object {
+
+ val EXEMPT = of("exempt")
+
+ val NON_EXEMPT = of("non_exempt")
+
+ val UNKNOWN = of("unknown")
+
+ fun of(value: String) = FlsaStatus(JsonField.of(value))
+ }
+
+ /** An enum containing [FlsaStatus]'s known values. */
+ enum class Known {
+ EXEMPT,
+ NON_EXEMPT,
+ UNKNOWN,
+ }
+
+ /**
+ * An enum containing [FlsaStatus]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [FlsaStatus] can contain an unknown value in a couple of cases:
+ * - It was deserialized from data that doesn't match any known member. For example, if
+ * the SDK is on an older version than the API, then the API may respond with new
+ * members that the SDK is unaware of.
+ * - It was constructed with an arbitrary value using the [of] method.
+ */
+ enum class Value {
+ EXEMPT,
+ NON_EXEMPT,
+ UNKNOWN,
+ /**
+ * An enum member indicating that [FlsaStatus] was instantiated with an unknown
+ * value.
+ */
+ _UNKNOWN,
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value, or
+ * [Value._UNKNOWN] if the class was instantiated with an unknown value.
+ *
+ * Use the [known] method instead if you're certain the value is always known or if you
+ * want to throw for the unknown case.
+ */
+ fun value(): Value =
+ when (this) {
+ EXEMPT -> Value.EXEMPT
+ NON_EXEMPT -> Value.NON_EXEMPT
+ UNKNOWN -> Value.UNKNOWN
+ else -> Value._UNKNOWN
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value.
+ *
+ * Use the [value] method instead if you're uncertain the value is always known and
+ * don't want to throw for the unknown case.
+ *
+ * @throws FinchInvalidDataException if this class instance's value is a not a known
+ * member.
+ */
+ fun known(): Known =
+ when (this) {
+ EXEMPT -> Known.EXEMPT
+ NON_EXEMPT -> Known.NON_EXEMPT
+ UNKNOWN -> Known.UNKNOWN
+ else -> throw FinchInvalidDataException("Unknown FlsaStatus: $value")
+ }
+
+ /**
+ * Returns this class instance's primitive wire representation.
+ *
+ * This differs from the [toString] method because that method is primarily for
+ * debugging and generally doesn't throw.
+ *
+ * @throws FinchInvalidDataException if this class instance's value does not have the
+ * expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString() ?: throw FinchInvalidDataException("Value is not a String")
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): FlsaStatus = apply {
+ if (validated) {
+ return@apply
+ }
+
+ known()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: FinchInvalidDataException) {
+ false
+ }
+
+ /**
+ * Returns a score indicating how many valid values are contained in this object
+ * recursively.
+ *
+ * Used for best match union deserialization.
+ */
+ internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is FlsaStatus && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
+
/** The manager object representing the manager of the individual within the org. */
class Manager
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
@@ -2063,6 +2352,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Manager = apply {
if (validated) {
return@apply
@@ -2247,6 +2546,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): CustomField = apply {
if (validated) {
return@apply
@@ -2318,6 +2627,31 @@ private constructor(
fun _json(): JsonValue? = _json
+ /**
+ * Maps this instance's current variant to a value of type [T] using the given
+ * [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the
+ * API, unless [visitor] overrides [Visitor.unknown]. To handle variants not known
+ * to this version of the SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```kotlin
+ * import com.tryfinch.api.core.JsonValue
+ *
+ * val result: String? = value.accept(object : Value.Visitor {
+ * override fun visitString(string: String): String? = string.toString()
+ *
+ * // ...
+ *
+ * override fun unknown(json: JsonValue?): String? {
+ * // Or inspect the `json`.
+ * return null
+ * }
+ * })
+ * ```
+ *
+ * @throws FinchInvalidDataException if [Visitor.unknown] is not overridden in
+ * [visitor] and the current variant is unknown.
+ */
fun accept(visitor: Visitor): T =
when {
string != null -> visitor.visitString(string)
@@ -2330,6 +2664,16 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
fun validate(): Value = apply {
if (validated) {
return@apply
@@ -2538,7 +2882,7 @@ private constructor(
return true
}
- return other is UnionMember0 &&
+ return other is EmploymentDataResponseBody &&
id == other.id &&
classCode == other.classCode &&
department == other.department &&
@@ -2546,6 +2890,7 @@ private constructor(
employmentStatus == other.employmentStatus &&
endDate == other.endDate &&
firstName == other.firstName &&
+ flsaStatus == other.flsaStatus &&
isActive == other.isActive &&
lastName == other.lastName &&
latestRehireDate == other.latestRehireDate &&
@@ -2571,6 +2916,7 @@ private constructor(
employmentStatus,
endDate,
firstName,
+ flsaStatus,
isActive,
lastName,
latestRehireDate,
@@ -2591,7 +2937,7 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "UnionMember0{id=$id, classCode=$classCode, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, startDate=$startDate, title=$title, customFields=$customFields, income=$income, incomeHistory=$incomeHistory, sourceId=$sourceId, workId=$workId, additionalProperties=$additionalProperties}"
+ "EmploymentDataResponseBody{id=$id, classCode=$classCode, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, flsaStatus=$flsaStatus, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, startDate=$startDate, title=$title, customFields=$customFields, income=$income, incomeHistory=$incomeHistory, sourceId=$sourceId, workId=$workId, additionalProperties=$additionalProperties}"
}
class BatchError
@@ -2799,6 +3145,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): BatchError = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentDataResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentDataResponse.kt
index 7a20a7f21..c8004bbad 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentDataResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentDataResponse.kt
@@ -129,9 +129,9 @@ private constructor(
*/
fun body(body: JsonField) = apply { this.body = body }
- /** Alias for calling [body] with `EmploymentData.ofUnionMember0(unionMember0)`. */
- fun body(unionMember0: EmploymentData.UnionMember0) =
- body(EmploymentData.ofUnionMember0(unionMember0))
+ /** Alias for calling [body] with `EmploymentData.ofResponseBody(responseBody)`. */
+ fun body(responseBody: EmploymentData.EmploymentDataResponseBody) =
+ body(EmploymentData.ofResponseBody(responseBody))
/** Alias for calling [body] with `EmploymentData.ofBatchError(batchError)`. */
fun body(batchError: EmploymentData.BatchError) =
@@ -205,6 +205,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): EmploymentDataResponse = apply {
if (validated) {
return@apply
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentEvent.kt
index 65a9e7320..c62cfafa5 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentEvent.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentEvent.kt
@@ -22,6 +22,7 @@ private constructor(
private val accountId: JsonField,
private val companyId: JsonField,
private val connectionId: JsonField,
+ private val entityId: JsonField,
private val data: JsonField,
private val eventType: JsonField,
private val additionalProperties: MutableMap,
@@ -34,17 +35,19 @@ private constructor(
@JsonProperty("connection_id")
@ExcludeMissing
connectionId: JsonField = JsonMissing.of(),
+ @JsonProperty("entity_id") @ExcludeMissing entityId: JsonField = JsonMissing.of(),
@JsonProperty("data") @ExcludeMissing data: JsonField = JsonMissing.of(),
@JsonProperty("event_type")
@ExcludeMissing
eventType: JsonField = JsonMissing.of(),
- ) : this(accountId, companyId, connectionId, data, eventType, mutableMapOf())
+ ) : this(accountId, companyId, connectionId, entityId, data, eventType, mutableMapOf())
fun toBaseWebhookEvent(): BaseWebhookEvent =
BaseWebhookEvent.builder()
.accountId(accountId)
.companyId(companyId)
.connectionId(connectionId)
+ .entityId(entityId)
.build()
/**
@@ -73,6 +76,14 @@ private constructor(
*/
fun connectionId(): String? = connectionId.getNullable("connection_id")
+ /**
+ * Unique Finch id of the entity for which data has been updated.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun entityId(): String? = entityId.getNullable("entity_id")
+
/**
* @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -114,6 +125,13 @@ private constructor(
@ExcludeMissing
fun _connectionId(): JsonField = connectionId
+ /**
+ * Returns the raw JSON value of [entityId].
+ *
+ * Unlike [entityId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("entity_id") @ExcludeMissing fun _entityId(): JsonField = entityId
+
/**
* Returns the raw JSON value of [data].
*
@@ -160,6 +178,7 @@ private constructor(
private var accountId: JsonField? = null
private var companyId: JsonField? = null
private var connectionId: JsonField = JsonMissing.of()
+ private var entityId: JsonField = JsonMissing.of()
private var data: JsonField = JsonMissing.of()
private var eventType: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -168,6 +187,7 @@ private constructor(
accountId = employmentEvent.accountId
companyId = employmentEvent.companyId
connectionId = employmentEvent.connectionId
+ entityId = employmentEvent.entityId
data = employmentEvent.data
eventType = employmentEvent.eventType
additionalProperties = employmentEvent.additionalProperties.toMutableMap()
@@ -221,6 +241,17 @@ private constructor(
this.connectionId = connectionId
}
+ /** Unique Finch id of the entity for which data has been updated. */
+ fun entityId(entityId: String) = entityId(JsonField.of(entityId))
+
+ /**
+ * Sets [Builder.entityId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.entityId] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun entityId(entityId: JsonField) = apply { this.entityId = entityId }
+
fun data(data: Data) = data(JsonField.of(data))
/**
@@ -279,6 +310,7 @@ private constructor(
checkRequired("accountId", accountId),
checkRequired("companyId", companyId),
connectionId,
+ entityId,
data,
eventType,
additionalProperties.toMutableMap(),
@@ -287,6 +319,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): EmploymentEvent = apply {
if (validated) {
return@apply
@@ -295,6 +335,7 @@ private constructor(
accountId()
companyId()
connectionId()
+ entityId()
data()?.validate()
eventType()?.validate()
validated = true
@@ -317,6 +358,7 @@ private constructor(
(if (accountId.asKnown() == null) 0 else 1) +
(if (companyId.asKnown() == null) 0 else 1) +
(if (connectionId.asKnown() == null) 0 else 1) +
+ (if (entityId.asKnown() == null) 0 else 1) +
(data.asKnown()?.validity() ?: 0) +
(eventType.asKnown()?.validity() ?: 0)
@@ -424,6 +466,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): Data = apply {
if (validated) {
return@apply
@@ -561,6 +612,15 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): EventType = apply {
if (validated) {
return@apply
@@ -608,17 +668,26 @@ private constructor(
accountId == other.accountId &&
companyId == other.companyId &&
connectionId == other.connectionId &&
+ entityId == other.entityId &&
data == other.data &&
eventType == other.eventType &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(accountId, companyId, connectionId, data, eventType, additionalProperties)
+ Objects.hash(
+ accountId,
+ companyId,
+ connectionId,
+ entityId,
+ data,
+ eventType,
+ additionalProperties,
+ )
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "EmploymentEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
+ "EmploymentEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, entityId=$entityId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}"
}
diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentUpdateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentUpdateResponse.kt
index 985b43c1a..0d5d5b9ee 100644
--- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentUpdateResponse.kt
+++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EmploymentUpdateResponse.kt
@@ -6,12 +6,23 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter
import com.fasterxml.jackson.annotation.JsonAnySetter
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.ObjectCodec
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
+import com.tryfinch.api.core.BaseDeserializer
+import com.tryfinch.api.core.BaseSerializer
import com.tryfinch.api.core.Enum
import com.tryfinch.api.core.ExcludeMissing
import com.tryfinch.api.core.JsonField
import com.tryfinch.api.core.JsonMissing
import com.tryfinch.api.core.JsonValue
+import com.tryfinch.api.core.allMaxBy
import com.tryfinch.api.core.checkKnown
+import com.tryfinch.api.core.getOrThrow
import com.tryfinch.api.core.toImmutable
import com.tryfinch.api.errors.FinchInvalidDataException
import java.util.Collections
@@ -28,6 +39,7 @@ private constructor(
private val employmentStatus: JsonField,
private val endDate: JsonField,
private val firstName: JsonField,
+ private val flsaStatus: JsonField,
private val income: JsonField,
private val incomeHistory: JsonField>,
private val isActive: JsonField,
@@ -60,6 +72,9 @@ private constructor(
employmentStatus: JsonField = JsonMissing.of(),
@JsonProperty("end_date") @ExcludeMissing endDate: JsonField = JsonMissing.of(),
@JsonProperty("first_name") @ExcludeMissing firstName: JsonField = JsonMissing.of(),
+ @JsonProperty("flsa_status")
+ @ExcludeMissing
+ flsaStatus: JsonField = JsonMissing.of(),
@JsonProperty("income") @ExcludeMissing income: JsonField = JsonMissing.of(),
@JsonProperty("income_history")
@ExcludeMissing
@@ -86,6 +101,7 @@ private constructor(
employmentStatus,
endDate,
firstName,
+ flsaStatus,
income,
incomeHistory,
isActive,
@@ -163,6 +179,14 @@ private constructor(
*/
fun firstName(): String? = firstName.getNullable("first_name")
+ /**
+ * The FLSA status of the individual. Available options: `exempt`, `non_exempt`, `unknown`.
+ *
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun flsaStatus(): FlsaStatus? = flsaStatus.getNullable("flsa_status")
+
/**
* The employee's income as reported by the provider. This may not always be annualized income,
* but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what information the
@@ -312,6 +336,15 @@ private constructor(
*/
@JsonProperty("first_name") @ExcludeMissing fun _firstName(): JsonField = firstName
+ /**
+ * Returns the raw JSON value of [flsaStatus].
+ *
+ * Unlike [flsaStatus], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("flsa_status")
+ @ExcludeMissing
+ fun _flsaStatus(): JsonField = flsaStatus
+
/**
* Returns the raw JSON value of [income].
*
@@ -423,6 +456,7 @@ private constructor(
private var employmentStatus: JsonField = JsonMissing.of()
private var endDate: JsonField = JsonMissing.of()
private var firstName: JsonField = JsonMissing.of()
+ private var flsaStatus: JsonField = JsonMissing.of()
private var income: JsonField = JsonMissing.of()
private var incomeHistory: JsonField>? = null
private var isActive: JsonField = JsonMissing.of()
@@ -445,6 +479,7 @@ private constructor(
employmentStatus = employmentUpdateResponse.employmentStatus
endDate = employmentUpdateResponse.endDate
firstName = employmentUpdateResponse.firstName
+ flsaStatus = employmentUpdateResponse.flsaStatus
income = employmentUpdateResponse.income
incomeHistory = employmentUpdateResponse.incomeHistory.map { it.toMutableList() }
isActive = employmentUpdateResponse.isActive
@@ -573,6 +608,20 @@ private constructor(
*/
fun firstName(firstName: JsonField) = apply { this.firstName = firstName }
+ /**
+ * The FLSA status of the individual. Available options: `exempt`, `non_exempt`, `unknown`.
+ */
+ fun flsaStatus(flsaStatus: FlsaStatus?) = flsaStatus(JsonField.ofNullable(flsaStatus))
+
+ /**
+ * Sets [Builder.flsaStatus] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.flsaStatus] with a well-typed [FlsaStatus] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun flsaStatus(flsaStatus: JsonField) = apply { this.flsaStatus = flsaStatus }
+
/**
* The employee's income as reported by the provider. This may not always be annualized
* income, but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what
@@ -760,6 +809,7 @@ private constructor(
employmentStatus,
endDate,
firstName,
+ flsaStatus,
income,
(incomeHistory ?: JsonMissing.of()).map { it.toImmutable() },
isActive,
@@ -777,6 +827,14 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its expected
+ * type.
+ */
fun validate(): EmploymentUpdateResponse = apply {
if (validated) {
return@apply
@@ -790,6 +848,7 @@ private constructor(
employmentStatus()?.validate()
endDate()
firstName()
+ flsaStatus()?.validate()
income()?.validate()
incomeHistory()?.forEach { it?.validate() }
isActive()
@@ -826,6 +885,7 @@ private constructor(
(employmentStatus.asKnown()?.validity() ?: 0) +
(if (endDate.asKnown() == null) 0 else 1) +
(if (firstName.asKnown() == null) 0 else 1) +
+ (flsaStatus.asKnown()?.validity() ?: 0) +
(income.asKnown()?.validity() ?: 0) +
(incomeHistory.asKnown()?.sumOf { (it?.validity() ?: 0).toInt() } ?: 0) +
(if (isActive.asKnown() == null) 0 else 1) +
@@ -842,14 +902,14 @@ private constructor(
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val name: JsonField,
- private val value: JsonValue,
+ private val value: JsonField,
private val additionalProperties: MutableMap,
) {
@JsonCreator
private constructor(
@JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(),
- @JsonProperty("value") @ExcludeMissing value: JsonValue = JsonMissing.of(),
+ @JsonProperty("value") @ExcludeMissing value: JsonField = JsonMissing.of(),
) : this(name, value, mutableMapOf())
/**
@@ -859,12 +919,10 @@ private constructor(
fun name(): String? = name.getNullable("name")
/**
- * This arbitrary value can be deserialized into a custom type using the `convert` method:
- * ```kotlin
- * val myObject: MyClass = customField.value().convert(MyClass::class.java)
- * ```
+ * @throws FinchInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
*/
- @JsonProperty("value") @ExcludeMissing fun _value(): JsonValue = value
+ fun value(): Value? = value.getNullable("value")
/**
* Returns the raw JSON value of [name].
@@ -873,6 +931,13 @@ private constructor(
*/
@JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name
+ /**
+ * Returns the raw JSON value of [value].
+ *
+ * Unlike [value], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("value") @ExcludeMissing fun _value(): JsonField = value
+
@JsonAnySetter
private fun putAdditionalProperty(key: String, value: JsonValue) {
additionalProperties.put(key, value)
@@ -895,7 +960,7 @@ private constructor(
class Builder internal constructor() {
private var name: JsonField = JsonMissing.of()
- private var value: JsonValue = JsonMissing.of()
+ private var value: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
internal fun from(customField: CustomField) = apply {
@@ -915,7 +980,32 @@ private constructor(
*/
fun name(name: JsonField) = apply { this.name = name }
- fun value(value: JsonValue) = apply { this.value = value }
+ fun value(value: Value?) = value(JsonField.ofNullable(value))
+
+ /**
+ * Sets [Builder.value] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.value] with a well-typed [Value] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun value(value: JsonField) = apply { this.value = value }
+
+ /** Alias for calling [value] with `Value.ofString(string)`. */
+ fun value(string: String) = value(Value.ofString(string))
+
+ /** Alias for calling [value] with `Value.ofJsonValues(jsonValues)`. */
+ fun valueOfJsonValues(jsonValues: List) =
+ value(Value.ofJsonValues(jsonValues))
+
+ /** Alias for calling [value] with `Value.ofJson(json)`. */
+ fun value(json: JsonValue) = value(Value.ofJson(json))
+
+ /** Alias for calling [value] with `Value.ofDouble(double)`. */
+ fun value(double: Double) = value(Value.ofDouble(double))
+
+ /** Alias for calling [value] with `Value.ofBoolean(boolean)`. */
+ fun value(boolean: Boolean) = value(Value.ofBoolean(boolean))
fun additionalProperties(additionalProperties: Map) = apply {
this.additionalProperties.clear()
@@ -946,12 +1036,22 @@ private constructor(
private var validated: Boolean = false
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws FinchInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
fun validate(): CustomField = apply {
if (validated) {
return@apply
}
name()
+ value()?.validate()
validated = true
}
@@ -969,7 +1069,280 @@ private constructor(
*
* Used for best match union deserialization.
*/
- internal fun validity(): Int = (if (name.asKnown() == null) 0 else 1)
+ internal fun validity(): Int =
+ (if (name.asKnown() == null) 0 else 1) + (value.asKnown()?.validity() ?: 0)
+
+ @JsonDeserialize(using = Value.Deserializer::class)
+ @JsonSerialize(using = Value.Serializer::class)
+ class Value
+ private constructor(
+ private val string: String? = null,
+ private val jsonValues: List