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 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.tryfinch.api/finch-kotlin)](https://central.sonatype.com/artifact/com.tryfinch.api/finch-kotlin/8.5.0) -[![javadoc](https://javadoc.io/badge2/com.tryfinch.api/finch-kotlin/8.5.0/javadoc.svg)](https://javadoc.io/doc/com.tryfinch.api/finch-kotlin/8.5.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.tryfinch.api/finch-kotlin)](https://central.sonatype.com/artifact/com.tryfinch.api/finch-kotlin/8.6.0) +[![javadoc](https://javadoc.io/badge2/com.tryfinch.api/finch-kotlin/8.6.0/javadoc.svg)](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. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl19) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](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) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJNeSBBY2Nlc3MgVG9rZW4iLCJGSU5DSF9DTElFTlRfSUQiOiI0YWIxNWU1MS0xMWFkLTQ5ZjQtYWNhZS1mMzQzYjc3OTQzNzUiLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiTXkgQ2xpZW50IFNlY3JldCIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiTXkgV2ViaG9vayBTZWNyZXQifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](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? = null, + private val json: JsonValue? = null, + private val double: Double? = null, + private val boolean: Boolean? = null, + private val _json: JsonValue? = null, + ) { + + fun string(): String? = string + + fun jsonValues(): List? = jsonValues + + fun json(): JsonValue? = json + + fun double(): Double? = double + + fun boolean(): Boolean? = boolean + + fun isString(): Boolean = string != null + + fun isJsonValues(): Boolean = jsonValues != null + + fun isJson(): Boolean = json != null + + fun isDouble(): Boolean = double != null + + fun isBoolean(): Boolean = boolean != null + + fun asString(): String = string.getOrThrow("string") + + fun asJsonValues(): List = jsonValues.getOrThrow("jsonValues") + + fun asJson(): JsonValue = json.getOrThrow("json") + + fun asDouble(): Double = double.getOrThrow("double") + + fun asBoolean(): Boolean = boolean.getOrThrow("boolean") + + 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) + jsonValues != null -> visitor.visitJsonValues(jsonValues) + json != null -> visitor.visitJson(json) + double != null -> visitor.visitDouble(double) + boolean != null -> visitor.visitBoolean(boolean) + 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(): Value = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitString(string: String) {} + + override fun visitJsonValues(jsonValues: List) {} + + override fun visitJson(json: JsonValue) {} + + override fun visitDouble(double: Double) {} + + override fun visitBoolean(boolean: Boolean) {} + } + ) + 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 = + accept( + object : Visitor { + override fun visitString(string: String) = 1 + + override fun visitJsonValues(jsonValues: List) = jsonValues.size + + override fun visitJson(json: JsonValue) = 1 + + override fun visitDouble(double: Double) = 1 + + override fun visitBoolean(boolean: Boolean) = 1 + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Value && + string == other.string && + jsonValues == other.jsonValues && + json == other.json && + double == other.double && + boolean == other.boolean + } + + override fun hashCode(): Int = Objects.hash(string, jsonValues, json, double, boolean) + + override fun toString(): String = + when { + string != null -> "Value{string=$string}" + jsonValues != null -> "Value{jsonValues=$jsonValues}" + json != null -> "Value{json=$json}" + double != null -> "Value{double=$double}" + boolean != null -> "Value{boolean=$boolean}" + _json != null -> "Value{_unknown=$_json}" + else -> throw IllegalStateException("Invalid Value") + } + + companion object { + + fun ofString(string: String) = Value(string = string) + + fun ofJsonValues(jsonValues: List) = + Value(jsonValues = jsonValues.toImmutable()) + + fun ofJson(json: JsonValue) = Value(json = json) + + fun ofDouble(double: Double) = Value(double = double) + + fun ofBoolean(boolean: Boolean) = Value(boolean = boolean) + } + + /** + * An interface that defines how to map each variant of [Value] to a value of type [T]. + */ + interface Visitor { + + fun visitString(string: String): T + + fun visitJsonValues(jsonValues: List): T + + fun visitJson(json: JsonValue): T + + fun visitDouble(double: Double): T + + fun visitBoolean(boolean: Boolean): T + + /** + * Maps an unknown variant of [Value] to a value of type [T]. + * + * An instance of [Value] can contain an unknown variant if it was deserialized from + * data that doesn't match any known variant. For example, if the SDK is on an older + * version than the API, then the API may respond with new variants that the SDK is + * unaware of. + * + * @throws FinchInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw FinchInvalidDataException("Unknown Value: $json") + } + } + + internal class Deserializer : BaseDeserializer(Value::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): Value { + val json = JsonValue.fromJsonNode(node) + + val bestMatches = + sequenceOf( + tryDeserialize(node, jacksonTypeRef())?.let { + Value(string = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef>())?.let { + Value(jsonValues = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(double = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(boolean = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(json = it, _json = json) + }, + ) + .filterNotNull() + .allMaxBy { it.validity() } + .toList() + return when (bestMatches.size) { + // This can happen if what we're deserializing is completely incompatible + // with all the possible variants. + 0 -> Value(_json = json) + 1 -> bestMatches.single() + // If there's more than one match with the highest validity, then use the + // first completely valid match, or simply the first match if none are + // completely valid. + else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first() + } + } + } + + internal class Serializer : BaseSerializer(Value::class) { + + override fun serialize( + value: Value, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.string != null -> generator.writeObject(value.string) + value.jsonValues != null -> generator.writeObject(value.jsonValues) + value.json != null -> generator.writeObject(value.json) + value.double != null -> generator.writeObject(value.double) + value.boolean != null -> generator.writeObject(value.boolean) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid Value") + } + } + } + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -1088,6 +1461,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 @@ -1264,6 +1646,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(): Employment = apply { if (validated) { return@apply @@ -1409,6 +1800,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 @@ -1535,6 +1936,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 @@ -1713,6 +2124,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(): EmploymentStatus = apply { if (validated) { return@apply @@ -1751,6 +2171,148 @@ 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) @@ -1849,6 +2411,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(): Manager = apply { if (validated) { return@apply @@ -1905,6 +2476,7 @@ private constructor( employmentStatus == other.employmentStatus && endDate == other.endDate && firstName == other.firstName && + flsaStatus == other.flsaStatus && income == other.income && incomeHistory == other.incomeHistory && isActive == other.isActive && @@ -1929,6 +2501,7 @@ private constructor( employmentStatus, endDate, firstName, + flsaStatus, income, incomeHistory, isActive, @@ -1947,5 +2520,5 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "EmploymentUpdateResponse{id=$id, classCode=$classCode, customFields=$customFields, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, sourceId=$sourceId, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" + "EmploymentUpdateResponse{id=$id, classCode=$classCode, customFields=$customFields, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, flsaStatus=$flsaStatus, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, sourceId=$sourceId, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" } diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EnrolledIndividualBenefitResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EnrolledIndividualBenefitResponse.kt index e7fbe0910..8acdfd07c 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EnrolledIndividualBenefitResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/EnrolledIndividualBenefitResponse.kt @@ -129,6 +129,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(): EnrolledIndividualBenefitResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitCreateParams.kt index 798b03f41..ce9135731 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitCreateParams.kt @@ -581,6 +581,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(): Body = apply { if (validated) { return@apply @@ -797,6 +806,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 @@ -976,6 +994,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 @@ -1106,6 +1134,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/HrisBenefitIndividualUnenrollManyParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitIndividualUnenrollManyParams.kt index b759334e2..62831c684 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitIndividualUnenrollManyParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitIndividualUnenrollManyParams.kt @@ -408,6 +408,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(): Body = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitUpdateParams.kt index 4ec1ab384..08ee937f2 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisBenefitUpdateParams.kt @@ -373,6 +373,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(): Body = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListPageResponse.kt index 8e8e13120..d6a33b730 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListPageResponse.kt @@ -153,6 +153,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(): HrisCompanyPayStatementItemListPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListParams.kt index f8be5d2f2..d59e9a7aa 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemListParams.kt @@ -376,6 +376,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(): Category = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleCreateParams.kt index 3251b9a21..28d838ba5 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleCreateParams.kt @@ -677,6 +677,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(): CreateRuleRequest = apply { if (validated) { return@apply @@ -846,6 +855,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(): Attributes = apply { if (validated) { return@apply @@ -935,6 +953,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(): Metadata = apply { if (validated) { return@apply @@ -1155,6 +1183,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(): Condition = apply { if (validated) { return@apply @@ -1270,6 +1307,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(): Operator = apply { if (validated) { return@apply @@ -1413,6 +1460,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(): EntityType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleListPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleListPageResponse.kt index 920a29551..7f208334f 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleListPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleListPageResponse.kt @@ -153,6 +153,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(): HrisCompanyPayStatementItemRuleListPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleUpdateParams.kt index 1a4c1031f..92c97a95a 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisCompanyPayStatementItemRuleUpdateParams.kt @@ -348,6 +348,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(): UpdateRuleRequest = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDirectoryListPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDirectoryListPageResponse.kt index 3d7e2d246..4b857ef75 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDirectoryListPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDirectoryListPageResponse.kt @@ -182,6 +182,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(): HrisDirectoryListPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDocumentListParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDocumentListParams.kt index 6fc268f42..751f58608 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDocumentListParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisDocumentListParams.kt @@ -355,6 +355,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/HrisEmploymentRetrieveManyPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponse.kt index 8d45691ce..350e90653 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponse.kt @@ -151,6 +151,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(): HrisEmploymentRetrieveManyPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyParams.kt index 7e8c224eb..d16e01c40 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyParams.kt @@ -414,6 +414,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(): Body = apply { if (validated) { return@apply @@ -585,6 +594,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(): Request = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponse.kt index 8c1cf3efe..09bd3adb6 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponse.kt @@ -151,6 +151,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(): HrisIndividualRetrieveManyPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyParams.kt index b846887b9..a23e88c1a 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyParams.kt @@ -435,6 +435,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(): Body = apply { if (validated) { return@apply @@ -596,6 +605,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(): Options = apply { if (validated) { return@apply @@ -740,6 +758,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(): Request = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyPageResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyPageResponse.kt index 79d4daf4e..ea7fc8054 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyPageResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyPageResponse.kt @@ -152,6 +152,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(): HrisPayStatementRetrieveManyPageResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyParams.kt index 9fad63d64..b0d1ef30d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/HrisPayStatementRetrieveManyParams.kt @@ -417,6 +417,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(): Body = apply { if (validated) { return@apply @@ -641,6 +650,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(): Request = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Income.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Income.kt index 543073e48..e05dec01a 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Income.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Income.kt @@ -254,6 +254,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(): Income = apply { if (validated) { return@apply @@ -417,6 +425,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(): Unit = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Individual.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Individual.kt index 3a6221de5..cda484858 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Individual.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Individual.kt @@ -33,34 +33,66 @@ import java.util.Objects @JsonSerialize(using = Individual.Serializer::class) class Individual private constructor( - private val unionMember0: UnionMember0? = null, + private val responseBody: IndividualResponseBody? = null, private val batchError: BatchError? = null, private val _json: JsonValue? = null, ) { - fun unionMember0(): UnionMember0? = unionMember0 + fun responseBody(): IndividualResponseBody? = 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(): IndividualResponseBody = 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? = individual.accept(object : Individual.Visitor { + * override fun visitResponseBody(responseBody: IndividualResponseBody): 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(): Individual = 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: IndividualResponseBody) { + 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: IndividualResponseBody) = + responseBody.validity() override fun visitBatchError(batchError: BatchError) = batchError.validity() @@ -110,15 +143,15 @@ private constructor( } return other is Individual && - 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 -> "Individual{unionMember0=$unionMember0}" + responseBody != null -> "Individual{responseBody=$responseBody}" batchError != null -> "Individual{batchError=$batchError}" _json != null -> "Individual{_unknown=$_json}" else -> throw IllegalStateException("Invalid Individual") @@ -126,7 +159,8 @@ private constructor( companion object { - fun ofUnionMember0(unionMember0: UnionMember0) = Individual(unionMember0 = unionMember0) + fun ofResponseBody(responseBody: IndividualResponseBody) = + Individual(responseBody = responseBody) fun ofBatchError(batchError: BatchError) = Individual(batchError = batchError) } @@ -134,7 +168,7 @@ private constructor( /** An interface that defines how to map each variant of [Individual] to a value of type [T]. */ interface Visitor { - fun visitUnionMember0(unionMember0: UnionMember0): T + fun visitResponseBody(responseBody: IndividualResponseBody): T fun visitBatchError(batchError: BatchError): T @@ -159,8 +193,8 @@ private constructor( val bestMatches = sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - Individual(unionMember0 = it, _json = json) + tryDeserialize(node, jacksonTypeRef())?.let { + Individual(responseBody = it, _json = json) }, tryDeserialize(node, jacksonTypeRef())?.let { Individual(batchError = it, _json = json) @@ -189,7 +223,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 Individual") @@ -197,7 +231,7 @@ private constructor( } } - class UnionMember0 + class IndividualResponseBody @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val id: JsonField, @@ -485,7 +519,7 @@ private constructor( companion object { /** - * Returns a mutable builder for constructing an instance of [UnionMember0]. + * Returns a mutable builder for constructing an instance of [IndividualResponseBody]. * * The following fields are required: * ```kotlin @@ -504,7 +538,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [UnionMember0]. */ + /** A builder for [IndividualResponseBody]. */ class Builder internal constructor() { private var id: JsonField? = null @@ -522,21 +556,21 @@ private constructor( private var ssn: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(unionMember0: UnionMember0) = apply { - id = unionMember0.id - dob = unionMember0.dob - ethnicity = unionMember0.ethnicity - firstName = unionMember0.firstName - gender = unionMember0.gender - lastName = unionMember0.lastName - middleName = unionMember0.middleName - phoneNumbers = unionMember0.phoneNumbers.map { it.toMutableList() } - preferredName = unionMember0.preferredName - residence = unionMember0.residence - emails = unionMember0.emails.map { it.toMutableList() } - encryptedSsn = unionMember0.encryptedSsn - ssn = unionMember0.ssn - additionalProperties = unionMember0.additionalProperties.toMutableMap() + internal fun from(individualResponseBody: IndividualResponseBody) = apply { + id = individualResponseBody.id + dob = individualResponseBody.dob + ethnicity = individualResponseBody.ethnicity + firstName = individualResponseBody.firstName + gender = individualResponseBody.gender + lastName = individualResponseBody.lastName + middleName = individualResponseBody.middleName + phoneNumbers = individualResponseBody.phoneNumbers.map { it.toMutableList() } + preferredName = individualResponseBody.preferredName + residence = individualResponseBody.residence + emails = individualResponseBody.emails.map { it.toMutableList() } + encryptedSsn = individualResponseBody.encryptedSsn + ssn = individualResponseBody.ssn + additionalProperties = individualResponseBody.additionalProperties.toMutableMap() } /** A stable Finch `id` (UUID v4) for an individual in the company. */ @@ -754,7 +788,7 @@ private constructor( } /** - * Returns an immutable instance of [UnionMember0]. + * Returns an immutable instance of [IndividualResponseBody]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -774,8 +808,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): UnionMember0 = - UnionMember0( + fun build(): IndividualResponseBody = + IndividualResponseBody( checkRequired("id", id), checkRequired("dob", dob), checkRequired("ethnicity", ethnicity), @@ -795,7 +829,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(): IndividualResponseBody = apply { if (validated) { return@apply } @@ -973,6 +1016,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(): Ethnicity = apply { if (validated) { return@apply @@ -1113,6 +1166,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(): Gender = apply { if (validated) { return@apply @@ -1297,6 +1360,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(): PhoneNumber = apply { if (validated) { return@apply @@ -1414,6 +1487,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 @@ -1618,6 +1701,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(): Email = apply { if (validated) { return@apply @@ -1735,6 +1828,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 @@ -1797,7 +1900,7 @@ private constructor( return true } - return other is UnionMember0 && + return other is IndividualResponseBody && id == other.id && dob == other.dob && ethnicity == other.ethnicity && @@ -1836,7 +1939,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "UnionMember0{id=$id, dob=$dob, ethnicity=$ethnicity, firstName=$firstName, gender=$gender, lastName=$lastName, middleName=$middleName, phoneNumbers=$phoneNumbers, preferredName=$preferredName, residence=$residence, emails=$emails, encryptedSsn=$encryptedSsn, ssn=$ssn, additionalProperties=$additionalProperties}" + "IndividualResponseBody{id=$id, dob=$dob, ethnicity=$ethnicity, firstName=$firstName, gender=$gender, lastName=$lastName, middleName=$middleName, phoneNumbers=$phoneNumbers, preferredName=$preferredName, residence=$residence, emails=$emails, encryptedSsn=$encryptedSsn, ssn=$ssn, additionalProperties=$additionalProperties}" } class BatchError @@ -2044,6 +2147,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/IndividualBenefit.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualBenefit.kt index 379a8f7fc..c9460a9b5 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualBenefit.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualBenefit.kt @@ -140,8 +140,9 @@ private constructor( */ fun body(body: JsonField) = apply { this.body = body } - /** Alias for calling [body] with `Body.ofUnionMember0(unionMember0)`. */ - fun body(unionMember0: Body.UnionMember0) = body(Body.ofUnionMember0(unionMember0)) + /** Alias for calling [body] with `Body.ofIndividualBenefit(individualBenefit)`. */ + fun body(individualBenefit: Body.InnerIndividualBenefit) = + body(Body.ofIndividualBenefit(individualBenefit)) /** Alias for calling [body] with `Body.ofBatchError(batchError)`. */ fun body(batchError: Body.BatchError) = body(Body.ofBatchError(batchError)) @@ -213,6 +214,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(): IndividualBenefit = apply { if (validated) { return@apply @@ -246,34 +255,68 @@ private constructor( @JsonSerialize(using = Body.Serializer::class) class Body private constructor( - private val unionMember0: UnionMember0? = null, + private val individualBenefit: InnerIndividualBenefit? = null, private val batchError: BatchError? = null, private val _json: JsonValue? = null, ) { - fun unionMember0(): UnionMember0? = unionMember0 + fun individualBenefit(): InnerIndividualBenefit? = individualBenefit fun batchError(): BatchError? = batchError - fun isUnionMember0(): Boolean = unionMember0 != null + fun isIndividualBenefit(): Boolean = individualBenefit != null fun isBatchError(): Boolean = batchError != null - fun asUnionMember0(): UnionMember0 = unionMember0.getOrThrow("unionMember0") + fun asIndividualBenefit(): InnerIndividualBenefit = + individualBenefit.getOrThrow("individualBenefit") 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? = body.accept(object : Body.Visitor { + * override fun visitIndividualBenefit(individualBenefit: InnerIndividualBenefit): String? = individualBenefit.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) + individualBenefit != null -> visitor.visitIndividualBenefit(individualBenefit) 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(): Body = apply { if (validated) { return@apply @@ -281,8 +324,8 @@ private constructor( accept( object : Visitor { - override fun visitUnionMember0(unionMember0: UnionMember0) { - unionMember0.validate() + override fun visitIndividualBenefit(individualBenefit: InnerIndividualBenefit) { + individualBenefit.validate() } override fun visitBatchError(batchError: BatchError) { @@ -310,8 +353,8 @@ private constructor( internal fun validity(): Int = accept( object : Visitor { - override fun visitUnionMember0(unionMember0: UnionMember0) = - unionMember0.validity() + override fun visitIndividualBenefit(individualBenefit: InnerIndividualBenefit) = + individualBenefit.validity() override fun visitBatchError(batchError: BatchError) = batchError.validity() @@ -325,15 +368,15 @@ private constructor( } return other is Body && - unionMember0 == other.unionMember0 && + individualBenefit == other.individualBenefit && batchError == other.batchError } - override fun hashCode(): Int = Objects.hash(unionMember0, batchError) + override fun hashCode(): Int = Objects.hash(individualBenefit, batchError) override fun toString(): String = when { - unionMember0 != null -> "Body{unionMember0=$unionMember0}" + individualBenefit != null -> "Body{individualBenefit=$individualBenefit}" batchError != null -> "Body{batchError=$batchError}" _json != null -> "Body{_unknown=$_json}" else -> throw IllegalStateException("Invalid Body") @@ -341,7 +384,8 @@ private constructor( companion object { - fun ofUnionMember0(unionMember0: UnionMember0) = Body(unionMember0 = unionMember0) + fun ofIndividualBenefit(individualBenefit: InnerIndividualBenefit) = + Body(individualBenefit = individualBenefit) fun ofBatchError(batchError: BatchError) = Body(batchError = batchError) } @@ -349,7 +393,7 @@ private constructor( /** An interface that defines how to map each variant of [Body] to a value of type [T]. */ interface Visitor { - fun visitUnionMember0(unionMember0: UnionMember0): T + fun visitIndividualBenefit(individualBenefit: InnerIndividualBenefit): T fun visitBatchError(batchError: BatchError): T @@ -374,8 +418,8 @@ private constructor( val bestMatches = sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - Body(unionMember0 = it, _json = json) + tryDeserialize(node, jacksonTypeRef())?.let { + Body(individualBenefit = it, _json = json) }, tryDeserialize(node, jacksonTypeRef())?.let { Body(batchError = it, _json = json) @@ -405,7 +449,8 @@ private constructor( provider: SerializerProvider, ) { when { - value.unionMember0 != null -> generator.writeObject(value.unionMember0) + value.individualBenefit != null -> + generator.writeObject(value.individualBenefit) value.batchError != null -> generator.writeObject(value.batchError) value._json != null -> generator.writeObject(value._json) else -> throw IllegalStateException("Invalid Body") @@ -413,7 +458,7 @@ private constructor( } } - class UnionMember0 + class InnerIndividualBenefit @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val annualMaximum: JsonField, @@ -559,7 +604,8 @@ private constructor( companion object { /** - * Returns a mutable builder for constructing an instance of [UnionMember0]. + * Returns a mutable builder for constructing an instance of + * [InnerIndividualBenefit]. * * The following fields are required: * ```kotlin @@ -572,7 +618,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [UnionMember0]. */ + /** A builder for [InnerIndividualBenefit]. */ class Builder internal constructor() { private var annualMaximum: JsonField? = null @@ -582,13 +628,14 @@ private constructor( private var hsaContributionLimit: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(unionMember0: UnionMember0) = apply { - annualMaximum = unionMember0.annualMaximum - catchUp = unionMember0.catchUp - companyContribution = unionMember0.companyContribution - employeeDeduction = unionMember0.employeeDeduction - hsaContributionLimit = unionMember0.hsaContributionLimit - additionalProperties = unionMember0.additionalProperties.toMutableMap() + internal fun from(innerIndividualBenefit: InnerIndividualBenefit) = apply { + annualMaximum = innerIndividualBenefit.annualMaximum + catchUp = innerIndividualBenefit.catchUp + companyContribution = innerIndividualBenefit.companyContribution + employeeDeduction = innerIndividualBenefit.employeeDeduction + hsaContributionLimit = innerIndividualBenefit.hsaContributionLimit + additionalProperties = + innerIndividualBenefit.additionalProperties.toMutableMap() } /** @@ -659,24 +706,24 @@ private constructor( /** * Alias for calling [companyContribution] with - * `CompanyContribution.ofInnerUnionMember0(innerUnionMember0)`. + * `CompanyContribution.ofFixed(fixed)`. */ - fun companyContribution(innerUnionMember0: CompanyContribution.InnerUnionMember0) = - companyContribution(CompanyContribution.ofInnerUnionMember0(innerUnionMember0)) + fun companyContribution(fixed: CompanyContribution.CompanyContributionFixed) = + companyContribution(CompanyContribution.ofFixed(fixed)) /** * Alias for calling [companyContribution] with - * `CompanyContribution.ofUnionMember1(unionMember1)`. + * `CompanyContribution.ofPercent(percent)`. */ - fun companyContribution(unionMember1: CompanyContribution.UnionMember1) = - companyContribution(CompanyContribution.ofUnionMember1(unionMember1)) + fun companyContribution(percent: CompanyContribution.CompanyContributionPercent) = + companyContribution(CompanyContribution.ofPercent(percent)) /** * Alias for calling [companyContribution] with - * `CompanyContribution.ofUnionMember2(unionMember2)`. + * `CompanyContribution.ofTiered(tiered)`. */ - fun companyContribution(unionMember2: CompanyContribution.UnionMember2) = - companyContribution(CompanyContribution.ofUnionMember2(unionMember2)) + fun companyContribution(tiered: CompanyContribution.CompanyContributionTiered) = + companyContribution(CompanyContribution.ofTiered(tiered)) /** * Employee deduction configuration. Supports both fixed amounts (in cents) and @@ -698,17 +745,19 @@ private constructor( /** * Alias for calling [employeeDeduction] with - * `EmployeeDeduction.ofInnerUnionMember0(innerUnionMember0)`. + * `EmployeeDeduction.ofContributionFixed(contributionFixed)`. */ - fun employeeDeduction(innerUnionMember0: EmployeeDeduction.InnerUnionMember0) = - employeeDeduction(EmployeeDeduction.ofInnerUnionMember0(innerUnionMember0)) + fun employeeDeduction( + contributionFixed: EmployeeDeduction.EmployeeDeductionContributionFixed + ) = employeeDeduction(EmployeeDeduction.ofContributionFixed(contributionFixed)) /** * Alias for calling [employeeDeduction] with - * `EmployeeDeduction.ofUnionMember1(unionMember1)`. + * `EmployeeDeduction.ofContributionPercent(contributionPercent)`. */ - fun employeeDeduction(unionMember1: EmployeeDeduction.UnionMember1) = - employeeDeduction(EmployeeDeduction.ofUnionMember1(unionMember1)) + fun employeeDeduction( + contributionPercent: EmployeeDeduction.EmployeeDeductionContributionPercent + ) = employeeDeduction(EmployeeDeduction.ofContributionPercent(contributionPercent)) /** Type for HSA contribution limit if the benefit is a HSA. */ fun hsaContributionLimit(hsaContributionLimit: HsaContributionLimit?) = @@ -749,7 +798,7 @@ private constructor( } /** - * Returns an immutable instance of [UnionMember0]. + * Returns an immutable instance of [InnerIndividualBenefit]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -763,8 +812,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): UnionMember0 = - UnionMember0( + fun build(): InnerIndividualBenefit = + InnerIndividualBenefit( checkRequired("annualMaximum", annualMaximum), checkRequired("catchUp", catchUp), checkRequired("companyContribution", companyContribution), @@ -776,7 +825,17 @@ 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(): InnerIndividualBenefit = apply { if (validated) { return@apply } @@ -819,44 +878,77 @@ private constructor( @JsonSerialize(using = CompanyContribution.Serializer::class) class CompanyContribution private constructor( - private val innerUnionMember0: InnerUnionMember0? = null, - private val unionMember1: UnionMember1? = null, - private val unionMember2: UnionMember2? = null, + private val fixed: CompanyContributionFixed? = null, + private val percent: CompanyContributionPercent? = null, + private val tiered: CompanyContributionTiered? = null, private val _json: JsonValue? = null, ) { - fun innerUnionMember0(): InnerUnionMember0? = innerUnionMember0 + fun fixed(): CompanyContributionFixed? = fixed - fun unionMember1(): UnionMember1? = unionMember1 + fun percent(): CompanyContributionPercent? = percent - fun unionMember2(): UnionMember2? = unionMember2 + fun tiered(): CompanyContributionTiered? = tiered - fun isInnerUnionMember0(): Boolean = innerUnionMember0 != 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 asInnerUnionMember0(): InnerUnionMember0 = - innerUnionMember0.getOrThrow("innerUnionMember0") + fun asFixed(): CompanyContributionFixed = fixed.getOrThrow("fixed") - fun asUnionMember1(): UnionMember1 = unionMember1.getOrThrow("unionMember1") + fun asPercent(): CompanyContributionPercent = percent.getOrThrow("percent") - fun asUnionMember2(): UnionMember2 = unionMember2.getOrThrow("unionMember2") + fun asTiered(): CompanyContributionTiered = 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? = companyContribution.accept(object : CompanyContribution.Visitor { + * override fun visitFixed(fixed: CompanyContributionFixed): 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 { - innerUnionMember0 != null -> - visitor.visitInnerUnionMember0(innerUnionMember0) - 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(): CompanyContribution = apply { if (validated) { return@apply @@ -864,18 +956,16 @@ private constructor( accept( object : Visitor { - override fun visitInnerUnionMember0( - innerUnionMember0: InnerUnionMember0 - ) { - innerUnionMember0.validate() + override fun visitFixed(fixed: CompanyContributionFixed) { + fixed.validate() } - override fun visitUnionMember1(unionMember1: UnionMember1) { - unionMember1.validate() + override fun visitPercent(percent: CompanyContributionPercent) { + percent.validate() } - override fun visitUnionMember2(unionMember2: UnionMember2) { - unionMember2.validate() + override fun visitTiered(tiered: CompanyContributionTiered) { + tiered.validate() } } ) @@ -899,15 +989,14 @@ private constructor( internal fun validity(): Int = accept( object : Visitor { - override fun visitInnerUnionMember0( - innerUnionMember0: InnerUnionMember0 - ) = innerUnionMember0.validity() + override fun visitFixed(fixed: CompanyContributionFixed) = + fixed.validity() - override fun visitUnionMember1(unionMember1: UnionMember1) = - unionMember1.validity() + override fun visitPercent(percent: CompanyContributionPercent) = + percent.validity() - override fun visitUnionMember2(unionMember2: UnionMember2) = - unionMember2.validity() + override fun visitTiered(tiered: CompanyContributionTiered) = + tiered.validity() override fun unknown(json: JsonValue?) = 0 } @@ -919,34 +1008,32 @@ private constructor( } return other is CompanyContribution && - innerUnionMember0 == other.innerUnionMember0 && - unionMember1 == other.unionMember1 && - unionMember2 == other.unionMember2 + fixed == other.fixed && + percent == other.percent && + tiered == other.tiered } - override fun hashCode(): Int = - Objects.hash(innerUnionMember0, unionMember1, unionMember2) + override fun hashCode(): Int = Objects.hash(fixed, percent, tiered) override fun toString(): String = when { - innerUnionMember0 != null -> - "CompanyContribution{innerUnionMember0=$innerUnionMember0}" - unionMember1 != null -> "CompanyContribution{unionMember1=$unionMember1}" - unionMember2 != null -> "CompanyContribution{unionMember2=$unionMember2}" + fixed != null -> "CompanyContribution{fixed=$fixed}" + percent != null -> "CompanyContribution{percent=$percent}" + tiered != null -> "CompanyContribution{tiered=$tiered}" _json != null -> "CompanyContribution{_unknown=$_json}" else -> throw IllegalStateException("Invalid CompanyContribution") } companion object { - fun ofInnerUnionMember0(innerUnionMember0: InnerUnionMember0) = - CompanyContribution(innerUnionMember0 = innerUnionMember0) + fun ofFixed(fixed: CompanyContributionFixed) = + CompanyContribution(fixed = fixed) - fun ofUnionMember1(unionMember1: UnionMember1) = - CompanyContribution(unionMember1 = unionMember1) + fun ofPercent(percent: CompanyContributionPercent) = + CompanyContribution(percent = percent) - fun ofUnionMember2(unionMember2: UnionMember2) = - CompanyContribution(unionMember2 = unionMember2) + fun ofTiered(tiered: CompanyContributionTiered) = + CompanyContribution(tiered = tiered) } /** @@ -955,11 +1042,11 @@ private constructor( */ interface Visitor { - fun visitInnerUnionMember0(innerUnionMember0: InnerUnionMember0): T + fun visitFixed(fixed: CompanyContributionFixed): T - fun visitUnionMember1(unionMember1: UnionMember1): T + fun visitPercent(percent: CompanyContributionPercent): T - fun visitUnionMember2(unionMember2: UnionMember2): T + fun visitTiered(tiered: CompanyContributionTiered): T /** * Maps an unknown variant of [CompanyContribution] to a value of type [T]. @@ -984,15 +1071,18 @@ private constructor( val bestMatches = sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - CompanyContribution(innerUnionMember0 = it, _json = json) - }, - tryDeserialize(node, jacksonTypeRef())?.let { - CompanyContribution(unionMember1 = it, _json = json) - }, - tryDeserialize(node, jacksonTypeRef())?.let { - CompanyContribution(unionMember2 = it, _json = json) - }, + tryDeserialize(node, jacksonTypeRef()) + ?.let { CompanyContribution(fixed = it, _json = json) }, + tryDeserialize( + node, + jacksonTypeRef(), + ) + ?.let { CompanyContribution(percent = it, _json = json) }, + tryDeserialize( + node, + jacksonTypeRef(), + ) + ?.let { CompanyContribution(tiered = it, _json = json) }, ) .filterNotNull() .allMaxBy { it.validity() } @@ -1020,17 +1110,16 @@ private constructor( provider: SerializerProvider, ) { when { - value.innerUnionMember0 != null -> - generator.writeObject(value.innerUnionMember0) - 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 CompanyContribution") } } } - class InnerUnionMember0 + class CompanyContributionFixed @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val amount: JsonField, @@ -1100,7 +1189,7 @@ private constructor( /** * Returns a mutable builder for constructing an instance of - * [InnerUnionMember0]. + * [CompanyContributionFixed]. * * The following fields are required: * ```kotlin @@ -1111,7 +1200,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [InnerUnionMember0]. */ + /** A builder for [CompanyContributionFixed]. */ class Builder internal constructor() { private var amount: JsonField? = null @@ -1119,12 +1208,13 @@ private constructor( private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(innerUnionMember0: InnerUnionMember0) = apply { - amount = innerUnionMember0.amount - type = innerUnionMember0.type - additionalProperties = - innerUnionMember0.additionalProperties.toMutableMap() - } + internal fun from(companyContributionFixed: CompanyContributionFixed) = + apply { + amount = companyContributionFixed.amount + type = companyContributionFixed.type + additionalProperties = + companyContributionFixed.additionalProperties.toMutableMap() + } /** * Contribution amount in cents (for type=fixed) or basis points (for @@ -1179,7 +1269,7 @@ private constructor( } /** - * Returns an immutable instance of [InnerUnionMember0]. + * Returns an immutable instance of [CompanyContributionFixed]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -1191,8 +1281,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): InnerUnionMember0 = - InnerUnionMember0( + fun build(): CompanyContributionFixed = + CompanyContributionFixed( checkRequired("amount", amount), checkRequired("type", type), additionalProperties.toMutableMap(), @@ -1201,7 +1291,17 @@ private constructor( private var validated: Boolean = false - fun validate(): InnerUnionMember0 = 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(): CompanyContributionFixed = apply { if (validated) { return@apply } @@ -1321,6 +1421,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 @@ -1364,7 +1474,7 @@ private constructor( return true } - return other is InnerUnionMember0 && + return other is CompanyContributionFixed && amount == other.amount && type == other.type && additionalProperties == other.additionalProperties @@ -1377,10 +1487,10 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "InnerUnionMember0{amount=$amount, type=$type, additionalProperties=$additionalProperties}" + "CompanyContributionFixed{amount=$amount, type=$type, additionalProperties=$additionalProperties}" } - class UnionMember1 + class CompanyContributionPercent @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val amount: JsonField, @@ -1449,7 +1559,8 @@ private constructor( companion object { /** - * Returns a mutable builder for constructing an instance of [UnionMember1]. + * Returns a mutable builder for constructing an instance of + * [CompanyContributionPercent]. * * The following fields are required: * ```kotlin @@ -1460,7 +1571,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [UnionMember1]. */ + /** A builder for [CompanyContributionPercent]. */ class Builder internal constructor() { private var amount: JsonField? = null @@ -1468,11 +1579,13 @@ private constructor( private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(unionMember1: UnionMember1) = apply { - amount = unionMember1.amount - type = unionMember1.type - additionalProperties = unionMember1.additionalProperties.toMutableMap() - } + internal fun from(companyContributionPercent: CompanyContributionPercent) = + apply { + amount = companyContributionPercent.amount + type = companyContributionPercent.type + additionalProperties = + companyContributionPercent.additionalProperties.toMutableMap() + } /** * Contribution amount in cents (for type=fixed) or basis points (for @@ -1527,7 +1640,7 @@ private constructor( } /** - * Returns an immutable instance of [UnionMember1]. + * Returns an immutable instance of [CompanyContributionPercent]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -1539,8 +1652,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): UnionMember1 = - UnionMember1( + fun build(): CompanyContributionPercent = + CompanyContributionPercent( checkRequired("amount", amount), checkRequired("type", type), additionalProperties.toMutableMap(), @@ -1549,7 +1662,17 @@ 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(): CompanyContributionPercent = apply { if (validated) { return@apply } @@ -1669,6 +1792,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 @@ -1712,7 +1845,7 @@ private constructor( return true } - return other is UnionMember1 && + return other is CompanyContributionPercent && amount == other.amount && type == other.type && additionalProperties == other.additionalProperties @@ -1725,10 +1858,10 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "UnionMember1{amount=$amount, type=$type, additionalProperties=$additionalProperties}" + "CompanyContributionPercent{amount=$amount, type=$type, additionalProperties=$additionalProperties}" } - class UnionMember2 + class CompanyContributionTiered @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val tiers: JsonField>, @@ -1799,7 +1932,8 @@ private constructor( companion object { /** - * Returns a mutable builder for constructing an instance of [UnionMember2]. + * Returns a mutable builder for constructing an instance of + * [CompanyContributionTiered]. * * The following fields are required: * ```kotlin @@ -1810,7 +1944,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [UnionMember2]. */ + /** A builder for [CompanyContributionTiered]. */ class Builder internal constructor() { private var tiers: JsonField>? = null @@ -1818,11 +1952,13 @@ private constructor( 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(companyContributionTiered: CompanyContributionTiered) = + apply { + tiers = companyContributionTiered.tiers.map { it.toMutableList() } + type = companyContributionTiered.type + additionalProperties = + companyContributionTiered.additionalProperties.toMutableMap() + } /** * Array of tier objects defining employer match tiers based on employee @@ -1892,7 +2028,7 @@ private constructor( } /** - * Returns an immutable instance of [UnionMember2]. + * Returns an immutable instance of [CompanyContributionTiered]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -1904,8 +2040,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): UnionMember2 = - UnionMember2( + fun build(): CompanyContributionTiered = + CompanyContributionTiered( checkRequired("tiers", tiers).map { it.toImmutable() }, checkRequired("type", type), additionalProperties.toMutableMap(), @@ -1914,7 +2050,17 @@ 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(): CompanyContributionTiered = apply { if (validated) { return@apply } @@ -2102,6 +2248,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 @@ -2244,6 +2400,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 @@ -2287,7 +2453,7 @@ private constructor( return true } - return other is UnionMember2 && + return other is CompanyContributionTiered && tiers == other.tiers && type == other.type && additionalProperties == other.additionalProperties @@ -2300,7 +2466,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "UnionMember2{tiers=$tiers, type=$type, additionalProperties=$additionalProperties}" + "CompanyContributionTiered{tiers=$tiers, type=$type, additionalProperties=$additionalProperties}" } } @@ -2312,36 +2478,74 @@ private constructor( @JsonSerialize(using = EmployeeDeduction.Serializer::class) class EmployeeDeduction private constructor( - private val innerUnionMember0: InnerUnionMember0? = null, - private val unionMember1: UnionMember1? = null, + private val contributionFixed: EmployeeDeductionContributionFixed? = null, + private val contributionPercent: EmployeeDeductionContributionPercent? = null, private val _json: JsonValue? = null, ) { - fun innerUnionMember0(): InnerUnionMember0? = innerUnionMember0 + fun contributionFixed(): EmployeeDeductionContributionFixed? = contributionFixed - fun unionMember1(): UnionMember1? = unionMember1 + fun contributionPercent(): EmployeeDeductionContributionPercent? = + contributionPercent - fun isInnerUnionMember0(): Boolean = innerUnionMember0 != null + fun isContributionFixed(): Boolean = contributionFixed != null - fun isUnionMember1(): Boolean = unionMember1 != null + fun isContributionPercent(): Boolean = contributionPercent != null - fun asInnerUnionMember0(): InnerUnionMember0 = - innerUnionMember0.getOrThrow("innerUnionMember0") + fun asContributionFixed(): EmployeeDeductionContributionFixed = + contributionFixed.getOrThrow("contributionFixed") - fun asUnionMember1(): UnionMember1 = unionMember1.getOrThrow("unionMember1") + fun asContributionPercent(): EmployeeDeductionContributionPercent = + contributionPercent.getOrThrow("contributionPercent") 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? = employeeDeduction.accept(object : EmployeeDeduction.Visitor { + * override fun visitContributionFixed(contributionFixed: EmployeeDeductionContributionFixed): String? = contributionFixed.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 { - innerUnionMember0 != null -> - visitor.visitInnerUnionMember0(innerUnionMember0) - unionMember1 != null -> visitor.visitUnionMember1(unionMember1) + contributionFixed != null -> + visitor.visitContributionFixed(contributionFixed) + contributionPercent != null -> + visitor.visitContributionPercent(contributionPercent) 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(): EmployeeDeduction = apply { if (validated) { return@apply @@ -2349,14 +2553,16 @@ private constructor( accept( object : Visitor { - override fun visitInnerUnionMember0( - innerUnionMember0: InnerUnionMember0 + override fun visitContributionFixed( + contributionFixed: EmployeeDeductionContributionFixed ) { - innerUnionMember0.validate() + contributionFixed.validate() } - override fun visitUnionMember1(unionMember1: UnionMember1) { - unionMember1.validate() + override fun visitContributionPercent( + contributionPercent: EmployeeDeductionContributionPercent + ) { + contributionPercent.validate() } } ) @@ -2380,12 +2586,13 @@ private constructor( internal fun validity(): Int = accept( object : Visitor { - override fun visitInnerUnionMember0( - innerUnionMember0: InnerUnionMember0 - ) = innerUnionMember0.validity() + override fun visitContributionFixed( + contributionFixed: EmployeeDeductionContributionFixed + ) = contributionFixed.validity() - override fun visitUnionMember1(unionMember1: UnionMember1) = - unionMember1.validity() + override fun visitContributionPercent( + contributionPercent: EmployeeDeductionContributionPercent + ) = contributionPercent.validity() override fun unknown(json: JsonValue?) = 0 } @@ -2397,28 +2604,30 @@ private constructor( } return other is EmployeeDeduction && - innerUnionMember0 == other.innerUnionMember0 && - unionMember1 == other.unionMember1 + contributionFixed == other.contributionFixed && + contributionPercent == other.contributionPercent } - override fun hashCode(): Int = Objects.hash(innerUnionMember0, unionMember1) + override fun hashCode(): Int = Objects.hash(contributionFixed, contributionPercent) override fun toString(): String = when { - innerUnionMember0 != null -> - "EmployeeDeduction{innerUnionMember0=$innerUnionMember0}" - unionMember1 != null -> "EmployeeDeduction{unionMember1=$unionMember1}" + contributionFixed != null -> + "EmployeeDeduction{contributionFixed=$contributionFixed}" + contributionPercent != null -> + "EmployeeDeduction{contributionPercent=$contributionPercent}" _json != null -> "EmployeeDeduction{_unknown=$_json}" else -> throw IllegalStateException("Invalid EmployeeDeduction") } companion object { - fun ofInnerUnionMember0(innerUnionMember0: InnerUnionMember0) = - EmployeeDeduction(innerUnionMember0 = innerUnionMember0) + fun ofContributionFixed(contributionFixed: EmployeeDeductionContributionFixed) = + EmployeeDeduction(contributionFixed = contributionFixed) - fun ofUnionMember1(unionMember1: UnionMember1) = - EmployeeDeduction(unionMember1 = unionMember1) + fun ofContributionPercent( + contributionPercent: EmployeeDeductionContributionPercent + ) = EmployeeDeduction(contributionPercent = contributionPercent) } /** @@ -2427,9 +2636,13 @@ private constructor( */ interface Visitor { - fun visitInnerUnionMember0(innerUnionMember0: InnerUnionMember0): T + fun visitContributionFixed( + contributionFixed: EmployeeDeductionContributionFixed + ): T - fun visitUnionMember1(unionMember1: UnionMember1): T + fun visitContributionPercent( + contributionPercent: EmployeeDeductionContributionPercent + ): T /** * Maps an unknown variant of [EmployeeDeduction] to a value of type [T]. @@ -2454,12 +2667,23 @@ private constructor( val bestMatches = sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - EmployeeDeduction(innerUnionMember0 = it, _json = json) - }, - tryDeserialize(node, jacksonTypeRef())?.let { - EmployeeDeduction(unionMember1 = it, _json = json) - }, + tryDeserialize( + node, + jacksonTypeRef(), + ) + ?.let { + EmployeeDeduction(contributionFixed = it, _json = json) + }, + tryDeserialize( + node, + jacksonTypeRef(), + ) + ?.let { + EmployeeDeduction( + contributionPercent = it, + _json = json, + ) + }, ) .filterNotNull() .allMaxBy { it.validity() } @@ -2487,16 +2711,17 @@ private constructor( provider: SerializerProvider, ) { when { - value.innerUnionMember0 != null -> - generator.writeObject(value.innerUnionMember0) - value.unionMember1 != null -> generator.writeObject(value.unionMember1) + value.contributionFixed != null -> + generator.writeObject(value.contributionFixed) + value.contributionPercent != null -> + generator.writeObject(value.contributionPercent) value._json != null -> generator.writeObject(value._json) else -> throw IllegalStateException("Invalid EmployeeDeduction") } } } - class InnerUnionMember0 + class EmployeeDeductionContributionFixed @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val amount: JsonField, @@ -2566,7 +2791,7 @@ private constructor( /** * Returns a mutable builder for constructing an instance of - * [InnerUnionMember0]. + * [EmployeeDeductionContributionFixed]. * * The following fields are required: * ```kotlin @@ -2577,7 +2802,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [InnerUnionMember0]. */ + /** A builder for [EmployeeDeductionContributionFixed]. */ class Builder internal constructor() { private var amount: JsonField? = null @@ -2585,11 +2810,14 @@ private constructor( private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(innerUnionMember0: InnerUnionMember0) = apply { - amount = innerUnionMember0.amount - type = innerUnionMember0.type + internal fun from( + employeeDeductionContributionFixed: EmployeeDeductionContributionFixed + ) = apply { + amount = employeeDeductionContributionFixed.amount + type = employeeDeductionContributionFixed.type additionalProperties = - innerUnionMember0.additionalProperties.toMutableMap() + employeeDeductionContributionFixed.additionalProperties + .toMutableMap() } /** @@ -2645,7 +2873,7 @@ private constructor( } /** - * Returns an immutable instance of [InnerUnionMember0]. + * Returns an immutable instance of [EmployeeDeductionContributionFixed]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -2657,8 +2885,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): InnerUnionMember0 = - InnerUnionMember0( + fun build(): EmployeeDeductionContributionFixed = + EmployeeDeductionContributionFixed( checkRequired("amount", amount), checkRequired("type", type), additionalProperties.toMutableMap(), @@ -2667,7 +2895,17 @@ private constructor( private var validated: Boolean = false - fun validate(): InnerUnionMember0 = 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(): EmployeeDeductionContributionFixed = apply { if (validated) { return@apply } @@ -2787,6 +3025,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 @@ -2830,7 +3078,7 @@ private constructor( return true } - return other is InnerUnionMember0 && + return other is EmployeeDeductionContributionFixed && amount == other.amount && type == other.type && additionalProperties == other.additionalProperties @@ -2843,10 +3091,10 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "InnerUnionMember0{amount=$amount, type=$type, additionalProperties=$additionalProperties}" + "EmployeeDeductionContributionFixed{amount=$amount, type=$type, additionalProperties=$additionalProperties}" } - class UnionMember1 + class EmployeeDeductionContributionPercent @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val amount: JsonField, @@ -2915,7 +3163,8 @@ private constructor( companion object { /** - * Returns a mutable builder for constructing an instance of [UnionMember1]. + * Returns a mutable builder for constructing an instance of + * [EmployeeDeductionContributionPercent]. * * The following fields are required: * ```kotlin @@ -2926,7 +3175,7 @@ private constructor( fun builder() = Builder() } - /** A builder for [UnionMember1]. */ + /** A builder for [EmployeeDeductionContributionPercent]. */ class Builder internal constructor() { private var amount: JsonField? = null @@ -2934,10 +3183,15 @@ private constructor( private var additionalProperties: MutableMap = mutableMapOf() - internal fun from(unionMember1: UnionMember1) = apply { - amount = unionMember1.amount - type = unionMember1.type - additionalProperties = unionMember1.additionalProperties.toMutableMap() + internal fun from( + employeeDeductionContributionPercent: + EmployeeDeductionContributionPercent + ) = apply { + amount = employeeDeductionContributionPercent.amount + type = employeeDeductionContributionPercent.type + additionalProperties = + employeeDeductionContributionPercent.additionalProperties + .toMutableMap() } /** @@ -2993,7 +3247,7 @@ private constructor( } /** - * Returns an immutable instance of [UnionMember1]. + * Returns an immutable instance of [EmployeeDeductionContributionPercent]. * * Further updates to this [Builder] will not mutate the returned instance. * @@ -3005,8 +3259,8 @@ private constructor( * * @throws IllegalStateException if any required field is unset. */ - fun build(): UnionMember1 = - UnionMember1( + fun build(): EmployeeDeductionContributionPercent = + EmployeeDeductionContributionPercent( checkRequired("amount", amount), checkRequired("type", type), additionalProperties.toMutableMap(), @@ -3015,7 +3269,17 @@ 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(): EmployeeDeductionContributionPercent = apply { if (validated) { return@apply } @@ -3135,6 +3399,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 @@ -3178,7 +3452,7 @@ private constructor( return true } - return other is UnionMember1 && + return other is EmployeeDeductionContributionPercent && amount == other.amount && type == other.type && additionalProperties == other.additionalProperties @@ -3191,7 +3465,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "UnionMember1{amount=$amount, type=$type, additionalProperties=$additionalProperties}" + "EmployeeDeductionContributionPercent{amount=$amount, type=$type, additionalProperties=$additionalProperties}" } } @@ -3291,6 +3565,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(): HsaContributionLimit = apply { if (validated) { return@apply @@ -3334,7 +3618,7 @@ private constructor( return true } - return other is UnionMember0 && + return other is InnerIndividualBenefit && annualMaximum == other.annualMaximum && catchUp == other.catchUp && companyContribution == other.companyContribution && @@ -3357,7 +3641,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "UnionMember0{annualMaximum=$annualMaximum, catchUp=$catchUp, companyContribution=$companyContribution, employeeDeduction=$employeeDeduction, hsaContributionLimit=$hsaContributionLimit, additionalProperties=$additionalProperties}" + "InnerIndividualBenefit{annualMaximum=$annualMaximum, catchUp=$catchUp, companyContribution=$companyContribution, employeeDeduction=$employeeDeduction, hsaContributionLimit=$hsaContributionLimit, additionalProperties=$additionalProperties}" } class BatchError @@ -3576,6 +3860,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(): BatchError = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEnrolledIdsResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEnrolledIdsResponse.kt index 15166849d..6a3f909c7 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEnrolledIdsResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEnrolledIdsResponse.kt @@ -182,6 +182,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(): IndividualEnrolledIdsResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEvent.kt index 222c143ae..7462c22e6 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEvent.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualEvent.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 = individualEvent.accountId companyId = individualEvent.companyId connectionId = individualEvent.connectionId + entityId = individualEvent.entityId data = individualEvent.data eventType = individualEvent.eventType additionalProperties = individualEvent.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(): IndividualEvent = 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() = - "IndividualEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}" + "IndividualEvent{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/IndividualInDirectory.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualInDirectory.kt index af3a2dca9..30a48e03d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualInDirectory.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualInDirectory.kt @@ -344,6 +344,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(): IndividualInDirectory = apply { if (validated) { return@apply @@ -479,6 +487,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 @@ -635,6 +652,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(): Manager = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualResponse.kt index 20e715efc..178a2dd97 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualResponse.kt @@ -126,9 +126,9 @@ private constructor( */ fun body(body: JsonField) = apply { this.body = body } - /** Alias for calling [body] with `Individual.ofUnionMember0(unionMember0)`. */ - fun body(unionMember0: Individual.UnionMember0) = - body(Individual.ofUnionMember0(unionMember0)) + /** Alias for calling [body] with `Individual.ofResponseBody(responseBody)`. */ + fun body(responseBody: Individual.IndividualResponseBody) = + body(Individual.ofResponseBody(responseBody)) /** Alias for calling [body] with `Individual.ofBatchError(batchError)`. */ fun body(batchError: Individual.BatchError) = body(Individual.ofBatchError(batchError)) @@ -200,6 +200,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(): IndividualResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualUpdateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualUpdateResponse.kt index 842bb8dee..3bf4b0e9e 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualUpdateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/IndividualUpdateResponse.kt @@ -564,6 +564,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(): IndividualUpdateResponse = apply { if (validated) { return@apply @@ -735,6 +743,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(): Email = apply { if (validated) { return@apply @@ -849,6 +866,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 @@ -1031,6 +1058,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(): Ethnicity = apply { if (validated) { return@apply @@ -1168,6 +1204,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(): Gender = apply { if (validated) { return@apply @@ -1328,6 +1373,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(): PhoneNumber = apply { if (validated) { return@apply @@ -1442,6 +1496,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/Introspection.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Introspection.kt index d205146ec..6724164e8 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Introspection.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Introspection.kt @@ -868,6 +868,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(): Introspection = apply { if (validated) { return@apply @@ -1022,6 +1030,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 @@ -1259,6 +1276,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(): ConnectionStatusDetail = apply { if (validated) { return@apply @@ -1313,6 +1339,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? = lastSuccessfulSync.accept(object : LastSuccessfulSync.Visitor { + * override fun visitOffsetDateTime(offsetDateTime: OffsetDateTime): String? = offsetDateTime.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 { offsetDateTime != null -> visitor.visitOffsetDateTime(offsetDateTime) @@ -1322,6 +1373,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(): LastSuccessfulSync = apply { if (validated) { return@apply @@ -1581,6 +1642,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 @@ -1824,6 +1894,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(): AuthenticationMethodDetail = apply { if (validated) { return@apply @@ -1960,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(): Type = apply { if (validated) { return@apply @@ -2206,6 +2295,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(): ConnectionStatusDetail = apply { if (validated) { return@apply @@ -2260,6 +2359,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? = lastSuccessfulSync.accept(object : LastSuccessfulSync.Visitor { + * override fun visitOffsetDateTime(offsetDateTime: OffsetDateTime): String? = offsetDateTime.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 { offsetDateTime != null -> visitor.visitOffsetDateTime(offsetDateTime) @@ -2269,6 +2393,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(): LastSuccessfulSync = apply { if (validated) { return@apply @@ -2464,6 +2598,7 @@ private constructor( private val id: JsonField, private val name: JsonField, private val sourceId: JsonField, + private val status: JsonField, private val additionalProperties: MutableMap, ) { @@ -2474,7 +2609,10 @@ private constructor( @JsonProperty("source_id") @ExcludeMissing sourceId: JsonField = JsonMissing.of(), - ) : this(id, name, sourceId, mutableMapOf()) + @JsonProperty("status") + @ExcludeMissing + status: JsonField = JsonMissing.of(), + ) : this(id, name, sourceId, status, mutableMapOf()) /** * The connection account ID for this entity @@ -2500,6 +2638,14 @@ private constructor( */ fun sourceId(): String? = sourceId.getNullable("source_id") + /** + * The status of the entity connection + * + * @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). + */ + fun status(): EntityConnectionStatus = status.getRequired("status") + /** * Returns the raw JSON value of [id]. * @@ -2521,6 +2667,15 @@ private constructor( */ @JsonProperty("source_id") @ExcludeMissing fun _sourceId(): JsonField = sourceId + /** + * Returns the raw JSON value of [status]. + * + * Unlike [status], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("status") + @ExcludeMissing + fun _status(): JsonField = status + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -2543,6 +2698,7 @@ private constructor( * .id() * .name() * .sourceId() + * .status() * ``` */ fun builder() = Builder() @@ -2554,12 +2710,14 @@ private constructor( private var id: JsonField? = null private var name: JsonField? = null private var sourceId: JsonField? = null + private var status: JsonField? = null private var additionalProperties: MutableMap = mutableMapOf() internal fun from(multiAccountEntity: MultiAccountEntity) = apply { id = multiAccountEntity.id name = multiAccountEntity.name sourceId = multiAccountEntity.sourceId + status = multiAccountEntity.status additionalProperties = multiAccountEntity.additionalProperties.toMutableMap() } @@ -2599,6 +2757,18 @@ private constructor( */ fun sourceId(sourceId: JsonField) = apply { this.sourceId = sourceId } + /** The status of the entity connection */ + fun status(status: EntityConnectionStatus) = status(JsonField.of(status)) + + /** + * Sets [Builder.status] to an arbitrary JSON value. + * + * You should usually call [Builder.status] with a well-typed [EntityConnectionStatus] + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun status(status: JsonField) = apply { this.status = status } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -2628,6 +2798,7 @@ private constructor( * .id() * .name() * .sourceId() + * .status() * ``` * * @throws IllegalStateException if any required field is unset. @@ -2637,12 +2808,22 @@ private constructor( checkRequired("id", id), checkRequired("name", name), checkRequired("sourceId", sourceId), + checkRequired("status", status), 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(): MultiAccountEntity = apply { if (validated) { return@apply @@ -2651,6 +2832,7 @@ private constructor( id() name() sourceId() + status().validate() validated = true } @@ -2671,7 +2853,182 @@ private constructor( internal fun validity(): Int = (if (id.asKnown() == null) 0 else 1) + (if (name.asKnown() == null) 0 else 1) + - (if (sourceId.asKnown() == null) 0 else 1) + (if (sourceId.asKnown() == null) 0 else 1) + + (status.asKnown()?.validity() ?: 0) + + /** The status of the entity connection */ + class EntityConnectionStatus + @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 PENDING = of("pending") + + val PROCESSING = of("processing") + + val CONNECTED = of("connected") + + val ERROR_NO_ACCOUNT_SETUP = of("error_no_account_setup") + + val ERROR_PERMISSIONS = of("error_permissions") + + val REAUTH = of("reauth") + + val DISCONNECTED = of("disconnected") + + fun of(value: String) = EntityConnectionStatus(JsonField.of(value)) + } + + /** An enum containing [EntityConnectionStatus]'s known values. */ + enum class Known { + PENDING, + PROCESSING, + CONNECTED, + ERROR_NO_ACCOUNT_SETUP, + ERROR_PERMISSIONS, + REAUTH, + DISCONNECTED, + } + + /** + * An enum containing [EntityConnectionStatus]'s known values, as well as an [_UNKNOWN] + * member. + * + * An instance of [EntityConnectionStatus] 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 { + PENDING, + PROCESSING, + CONNECTED, + ERROR_NO_ACCOUNT_SETUP, + ERROR_PERMISSIONS, + REAUTH, + DISCONNECTED, + /** + * An enum member indicating that [EntityConnectionStatus] 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) { + PENDING -> Value.PENDING + PROCESSING -> Value.PROCESSING + CONNECTED -> Value.CONNECTED + ERROR_NO_ACCOUNT_SETUP -> Value.ERROR_NO_ACCOUNT_SETUP + ERROR_PERMISSIONS -> Value.ERROR_PERMISSIONS + REAUTH -> Value.REAUTH + DISCONNECTED -> Value.DISCONNECTED + 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) { + PENDING -> Known.PENDING + PROCESSING -> Known.PROCESSING + CONNECTED -> Known.CONNECTED + ERROR_NO_ACCOUNT_SETUP -> Known.ERROR_NO_ACCOUNT_SETUP + ERROR_PERMISSIONS -> Known.ERROR_PERMISSIONS + REAUTH -> Known.REAUTH + DISCONNECTED -> Known.DISCONNECTED + else -> + throw FinchInvalidDataException("Unknown EntityConnectionStatus: $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(): EntityConnectionStatus = 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 EntityConnectionStatus && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -2682,15 +3039,18 @@ private constructor( id == other.id && name == other.name && sourceId == other.sourceId && + status == other.status && additionalProperties == other.additionalProperties } - private val hashCode: Int by lazy { Objects.hash(id, name, sourceId, additionalProperties) } + private val hashCode: Int by lazy { + Objects.hash(id, name, sourceId, status, additionalProperties) + } override fun hashCode(): Int = hashCode override fun toString() = - "MultiAccountEntity{id=$id, name=$name, sourceId=$sourceId, additionalProperties=$additionalProperties}" + "MultiAccountEntity{id=$id, name=$name, sourceId=$sourceId, status=$status, additionalProperties=$additionalProperties}" } override fun equals(other: Any?): Boolean { diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobAutomatedCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobAutomatedCreateParams.kt index 7f8f206b3..47832323e 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobAutomatedCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobAutomatedCreateParams.kt @@ -45,12 +45,12 @@ import java.util.Objects */ class JobAutomatedCreateParams private constructor( - private val body: Body?, + private val body: Body, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { - fun body(): Body? = body + fun body(): Body = body /** Additional headers to send with the request. */ fun _additionalHeaders(): Headers = additionalHeaders @@ -62,9 +62,14 @@ private constructor( companion object { - fun none(): JobAutomatedCreateParams = builder().build() - - /** Returns a mutable builder for constructing an instance of [JobAutomatedCreateParams]. */ + /** + * Returns a mutable builder for constructing an instance of [JobAutomatedCreateParams]. + * + * The following fields are required: + * ```kotlin + * .body() + * ``` + */ fun builder() = Builder() } @@ -81,7 +86,7 @@ private constructor( additionalQueryParams = jobAutomatedCreateParams.additionalQueryParams.toBuilder() } - fun body(body: Body?) = apply { this.body = body } + fun body(body: Body) = apply { this.body = body } /** Alias for calling [body] with `Body.ofDataSyncAll()`. */ fun bodyDataSyncAll() = body(Body.ofDataSyncAll()) @@ -203,12 +208,23 @@ private constructor( * Returns an immutable instance of [JobAutomatedCreateParams]. * * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```kotlin + * .body() + * ``` + * + * @throws IllegalStateException if any required field is unset. */ fun build(): JobAutomatedCreateParams = - JobAutomatedCreateParams(body, additionalHeaders.build(), additionalQueryParams.build()) + JobAutomatedCreateParams( + checkRequired("body", body), + additionalHeaders.build(), + additionalQueryParams.build(), + ) } - fun _body(): Body? = body + fun _body(): Body = body override fun _headers(): Headers = additionalHeaders @@ -238,6 +254,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? = body.accept(object : Body.Visitor { + * override fun visitDataSyncAll(dataSyncAll: JsonValue): String? = dataSyncAll.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 { dataSyncAll != null -> visitor.visitDataSyncAll(dataSyncAll) @@ -247,6 +287,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(): Body = apply { if (validated) { return@apply @@ -541,6 +590,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(): W4FormEmployeeSync = apply { if (validated) { return@apply @@ -700,6 +759,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(): Params = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCompletionEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCompletionEvent.kt index bbf331e96..23b69c1cb 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCompletionEvent.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCompletionEvent.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 = jobCompletionEvent.accountId companyId = jobCompletionEvent.companyId connectionId = jobCompletionEvent.connectionId + entityId = jobCompletionEvent.entityId data = jobCompletionEvent.data eventType = jobCompletionEvent.eventType additionalProperties = jobCompletionEvent.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(): JobCompletionEvent = 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) @@ -469,6 +511,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 @@ -541,6 +592,13 @@ private constructor( val JOB_DATA_SYNC_ALL_COMPLETED = of("job.data_sync_all.completed") + val JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED = of("job.w4_form_employee_sync.completed") + + val JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED = of("job.initial_data_sync_org.succeeded") + + val JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED = + of("job.initial_data_sync_payroll.succeeded") + fun of(value: String) = EventType(JsonField.of(value)) } @@ -552,6 +610,9 @@ private constructor( JOB_BENEFIT_UNENROLL_COMPLETED, JOB_BENEFIT_UPDATE_COMPLETED, JOB_DATA_SYNC_ALL_COMPLETED, + JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED, + JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED, + JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED, } /** @@ -570,6 +631,9 @@ private constructor( JOB_BENEFIT_UNENROLL_COMPLETED, JOB_BENEFIT_UPDATE_COMPLETED, JOB_DATA_SYNC_ALL_COMPLETED, + JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED, + JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED, + JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED, /** * An enum member indicating that [EventType] was instantiated with an unknown value. */ @@ -591,6 +655,10 @@ private constructor( JOB_BENEFIT_UNENROLL_COMPLETED -> Value.JOB_BENEFIT_UNENROLL_COMPLETED JOB_BENEFIT_UPDATE_COMPLETED -> Value.JOB_BENEFIT_UPDATE_COMPLETED JOB_DATA_SYNC_ALL_COMPLETED -> Value.JOB_DATA_SYNC_ALL_COMPLETED + JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED -> Value.JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED + JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED -> Value.JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED + JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED -> + Value.JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED else -> Value._UNKNOWN } @@ -610,6 +678,10 @@ private constructor( JOB_BENEFIT_UNENROLL_COMPLETED -> Known.JOB_BENEFIT_UNENROLL_COMPLETED JOB_BENEFIT_UPDATE_COMPLETED -> Known.JOB_BENEFIT_UPDATE_COMPLETED JOB_DATA_SYNC_ALL_COMPLETED -> Known.JOB_DATA_SYNC_ALL_COMPLETED + JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED -> Known.JOB_W4_FORM_EMPLOYEE_SYNC_COMPLETED + JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED -> Known.JOB_INITIAL_DATA_SYNC_ORG_SUCCEEDED + JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED -> + Known.JOB_INITIAL_DATA_SYNC_PAYROLL_SUCCEEDED else -> throw FinchInvalidDataException("Unknown EventType: $value") } @@ -627,6 +699,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 @@ -674,17 +755,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() = - "JobCompletionEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}" + "JobCompletionEvent{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/JobCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCreateResponse.kt index 50c472273..f93b83a83 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCreateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/JobCreateResponse.kt @@ -246,6 +246,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(): JobCreateResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Location.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Location.kt index 88ad51251..a0ed82185 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Location.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Location.kt @@ -353,6 +353,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(): Location = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ManualAsyncJob.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ManualAsyncJob.kt index 9b32a8e4b..ff56e79b9 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ManualAsyncJob.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ManualAsyncJob.kt @@ -207,6 +207,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(): ManualAsyncJob = apply { if (validated) { return@apply @@ -334,6 +342,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 diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Money.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Money.kt index c03d56cbd..e8950b8a5 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Money.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Money.kt @@ -166,6 +166,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(): Money = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupport.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupport.kt index ec82c9ae7..7139b4abe 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupport.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupport.kt @@ -116,6 +116,14 @@ class OperationSupport @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(): OperationSupport = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupportMatrix.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupportMatrix.kt index 9d1a0dbdc..58fbc1a7d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupportMatrix.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/OperationSupportMatrix.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(): OperationSupportMatrix = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Paging.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Paging.kt index b00fa2781..e484c5338 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Paging.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Paging.kt @@ -156,6 +156,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(): Paging = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupListResponse.kt index ba7448aba..3955746a0 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupListResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupListResponse.kt @@ -218,6 +218,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(): PayGroupListResponse = apply { if (validated) { return@apply @@ -378,6 +386,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(): PayFrequency = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupRetrieveResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupRetrieveResponse.kt index e447012cb..746618b99 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupRetrieveResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayGroupRetrieveResponse.kt @@ -267,6 +267,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(): PayGroupRetrieveResponse = apply { if (validated) { return@apply @@ -429,6 +437,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(): PayFrequency = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatement.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatement.kt index c73813a4c..db3322f96 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatement.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatement.kt @@ -533,6 +533,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(): PayStatement = apply { if (validated) { return@apply @@ -882,6 +890,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(): Earning = apply { if (validated) { return@apply @@ -1072,6 +1089,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 @@ -1239,6 +1266,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(): Attributes = apply { if (validated) { return@apply @@ -1328,6 +1365,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(): Metadata = apply { if (validated) { return@apply @@ -1715,6 +1762,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(): EmployeeDeduction = apply { if (validated) { return@apply @@ -1880,6 +1936,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(): Attributes = apply { if (validated) { return@apply @@ -1969,6 +2035,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(): Metadata = apply { if (validated) { return@apply @@ -2313,6 +2389,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(): EmployerContribution = apply { if (validated) { return@apply @@ -2476,6 +2561,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(): Attributes = apply { if (validated) { return@apply @@ -2565,6 +2660,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(): Metadata = apply { if (validated) { return@apply @@ -2746,6 +2851,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(): PaymentMethod = apply { if (validated) { return@apply @@ -3086,6 +3200,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(): Tax = apply { if (validated) { return@apply @@ -3222,6 +3345,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 @@ -3389,6 +3522,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(): Attributes = apply { if (validated) { return@apply @@ -3478,6 +3621,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(): Metadata = apply { if (validated) { return@apply @@ -3656,6 +3809,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/PayStatementDataSyncInProgress.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementDataSyncInProgress.kt index f25d2bcf0..5a5c7b0a7 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementDataSyncInProgress.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementDataSyncInProgress.kt @@ -222,6 +222,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(): PayStatementDataSyncInProgress = apply { if (validated) { return@apply @@ -331,6 +339,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(): Code = apply { if (validated) { return@apply @@ -451,6 +468,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(): FinchCode = apply { if (validated) { return@apply @@ -576,6 +602,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(): Message = apply { if (validated) { return@apply @@ -694,6 +729,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(): Name = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementEvent.kt index cc0e83d18..70265849c 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementEvent.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementEvent.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 = payStatementEvent.accountId companyId = payStatementEvent.companyId connectionId = payStatementEvent.connectionId + entityId = payStatementEvent.entityId data = payStatementEvent.data eventType = payStatementEvent.eventType additionalProperties = payStatementEvent.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(): PayStatementEvent = 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) @@ -457,6 +499,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 @@ -600,6 +651,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 @@ -647,17 +707,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() = - "PayStatementEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}" + "PayStatementEvent{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/PayStatementItemListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementItemListResponse.kt index ef25ce39c..f0f00a92b 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementItemListResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementItemListResponse.kt @@ -203,6 +203,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(): PayStatementItemListResponse = apply { if (validated) { return@apply @@ -473,6 +481,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(): Attributes = apply { if (validated) { return@apply @@ -569,6 +586,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(): Metadata = apply { if (validated) { return@apply @@ -731,6 +758,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(): Category = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponse.kt index 16bcd210c..bbe9b23c0 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponse.kt @@ -212,6 +212,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(): PayStatementResponse = apply { if (validated) { return@apply @@ -274,6 +282,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? = body.accept(object : Body.Visitor { + * override fun visitPayStatementResponse(payStatementResponse: PayStatementResponseBody): String? = payStatementResponse.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 { payStatementResponse != null -> @@ -286,6 +318,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(): Body = apply { if (validated) { return@apply @@ -672,6 +713,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(): BatchError = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponseBody.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponseBody.kt index 55d1f4d0a..1b897bdce 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponseBody.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PayStatementResponseBody.kt @@ -178,6 +178,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(): PayStatementResponseBody = apply { if (validated) { return@apply @@ -348,6 +356,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(): Paging = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Payment.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Payment.kt index f74c08dc1..1221b1d58 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Payment.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Payment.kt @@ -574,6 +574,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(): Payment = apply { if (validated) { return@apply @@ -752,6 +760,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(): PayFrequency = apply { if (validated) { return@apply @@ -936,6 +953,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(): PayPeriod = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentCreateResponse.kt index 174922098..d5cbd9c00 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentCreateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentCreateResponse.kt @@ -163,6 +163,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(): PaymentCreateResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentEvent.kt index e4c86f862..275ae6a56 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentEvent.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/PaymentEvent.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,19 +35,21 @@ 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() /** @@ -75,6 +78,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). @@ -116,6 +127,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]. * @@ -162,6 +180,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() @@ -170,6 +189,7 @@ private constructor( accountId = paymentEvent.accountId companyId = paymentEvent.companyId connectionId = paymentEvent.connectionId + entityId = paymentEvent.entityId data = paymentEvent.data eventType = paymentEvent.eventType additionalProperties = paymentEvent.additionalProperties.toMutableMap() @@ -223,6 +243,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: PaymentIdentifiers) = data(JsonField.of(data)) /** @@ -282,6 +313,7 @@ private constructor( checkRequired("accountId", accountId), checkRequired("companyId", companyId), connectionId, + entityId, data, eventType, additionalProperties.toMutableMap(), @@ -290,6 +322,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(): PaymentEvent = apply { if (validated) { return@apply @@ -298,6 +338,7 @@ private constructor( accountId() companyId() connectionId() + entityId() data()?.validate() eventType()?.validate() validated = true @@ -320,6 +361,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) @@ -474,6 +516,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(): PaymentIdentifiers = apply { if (validated) { return@apply @@ -614,6 +665,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 @@ -661,17 +721,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() = - "PaymentEvent{accountId=$accountId, companyId=$companyId, connectionId=$connectionId, data=$data, eventType=$eventType, additionalProperties=$additionalProperties}" + "PaymentEvent{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/Provider.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Provider.kt index d4f6d31bb..b0dd89255 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Provider.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/Provider.kt @@ -494,6 +494,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(): Provider = apply { if (validated) { return@apply @@ -732,6 +740,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(): AuthenticationMethod = apply { if (validated) { return@apply @@ -874,6 +891,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 @@ -973,6 +1000,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(): BenefitsSupport = apply { if (validated) { return@apply @@ -1075,6 +1112,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 diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ProviderListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ProviderListResponse.kt index 7bf55c3d7..d4d726fe2 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ProviderListResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/ProviderListResponse.kt @@ -495,6 +495,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(): ProviderListResponse = apply { if (validated) { return@apply @@ -733,6 +741,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(): AuthenticationMethod = apply { if (validated) { return@apply @@ -875,6 +892,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 @@ -974,6 +1001,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(): BenefitsSupport = apply { if (validated) { return@apply @@ -1076,6 +1113,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 diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardParams.kt index c7067710d..5b7d486ef 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardParams.kt @@ -660,6 +660,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(): ForwardRequest = apply { if (validated) { return@apply @@ -779,6 +788,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 @@ -881,6 +899,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(): RequestHeaders = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardResponse.kt index 2fba9378e..5b439cc73 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RequestForwardingForwardResponse.kt @@ -256,6 +256,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(): RequestForwardingForwardResponse = apply { if (validated) { return@apply @@ -545,6 +553,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(): Request = apply { if (validated) { return@apply @@ -603,6 +620,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? = data.accept(object : Data.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) @@ -612,6 +654,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(): Data = apply { if (validated) { return@apply @@ -810,6 +862,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(): UnionMember1 = apply { if (validated) { return@apply @@ -915,6 +977,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(): Headers = apply { if (validated) { return@apply @@ -1016,6 +1088,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(): Params = apply { if (validated) { return@apply @@ -1141,6 +1223,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(): Headers = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleCreateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleCreateResponse.kt index 23a06692c..288f25f16 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleCreateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleCreateResponse.kt @@ -431,6 +431,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(): RuleCreateResponse = apply { if (validated) { return@apply @@ -576,6 +584,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(): Attributes = apply { if (validated) { return@apply @@ -665,6 +682,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(): Metadata = apply { if (validated) { return@apply @@ -885,6 +912,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(): Condition = apply { if (validated) { return@apply @@ -1000,6 +1036,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(): Operator = apply { if (validated) { return@apply @@ -1143,6 +1189,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(): EntityType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleDeleteResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleDeleteResponse.kt index 77a8a7e7e..1101ed941 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleDeleteResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleDeleteResponse.kt @@ -468,6 +468,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(): RuleDeleteResponse = apply { if (validated) { return@apply @@ -615,6 +623,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(): Attributes = apply { if (validated) { return@apply @@ -704,6 +721,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(): Metadata = apply { if (validated) { return@apply @@ -924,6 +951,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(): Condition = apply { if (validated) { return@apply @@ -1039,6 +1075,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(): Operator = apply { if (validated) { return@apply @@ -1182,6 +1228,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(): EntityType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleListResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleListResponse.kt index e33fa390c..a49e25b6d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleListResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleListResponse.kt @@ -431,6 +431,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(): RuleListResponse = apply { if (validated) { return@apply @@ -576,6 +584,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(): Attributes = apply { if (validated) { return@apply @@ -665,6 +682,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(): Metadata = apply { if (validated) { return@apply @@ -885,6 +912,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(): Condition = apply { if (validated) { return@apply @@ -1000,6 +1036,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(): Operator = apply { if (validated) { return@apply @@ -1143,6 +1189,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(): EntityType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleUpdateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleUpdateResponse.kt index d4bdd71da..66aadd01c 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleUpdateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/RuleUpdateResponse.kt @@ -431,6 +431,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(): RuleUpdateResponse = apply { if (validated) { return@apply @@ -576,6 +584,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(): Attributes = apply { if (validated) { return@apply @@ -665,6 +682,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(): Metadata = apply { if (validated) { return@apply @@ -885,6 +912,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(): Condition = apply { if (validated) { return@apply @@ -1000,6 +1036,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(): Operator = apply { if (validated) { return@apply @@ -1143,6 +1189,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(): EntityType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxCompanyUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxCompanyUpdateParams.kt index d28246e83..c6bc78540 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxCompanyUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxCompanyUpdateParams.kt @@ -918,6 +918,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(): CompanyWithoutId = apply { if (validated) { return@apply @@ -1271,6 +1280,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 @@ -1397,6 +1415,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 @@ -1594,6 +1622,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 @@ -1722,6 +1759,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 @@ -1913,6 +1960,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 @@ -2037,6 +2093,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 @@ -2193,6 +2259,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/SandboxConnectionAccountCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountCreateParams.kt index 2b3a61f87..644044d93 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountCreateParams.kt @@ -588,6 +588,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(): Body = apply { if (validated) { return@apply @@ -746,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(): AuthenticationType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountUpdateParams.kt index 5f46e0818..a12920f5d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionAccountUpdateParams.kt @@ -342,6 +342,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(): Body = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionCreateParams.kt index e58854f04..4b1211290 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxConnectionCreateParams.kt @@ -590,6 +590,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(): Body = apply { if (validated) { return@apply @@ -754,6 +763,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/SandboxDirectoryCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParams.kt index 47748d64b..20c3de8f4 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParams.kt @@ -6,13 +6,24 @@ 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.Params +import com.tryfinch.api.core.allMaxBy import com.tryfinch.api.core.checkKnown +import com.tryfinch.api.core.getOrThrow import com.tryfinch.api.core.http.Headers import com.tryfinch.api.core.http.QueryParams import com.tryfinch.api.core.toImmutable @@ -211,6 +222,7 @@ private constructor( private val endDate: JsonField, private val ethnicity: JsonField, private val firstName: JsonField, + private val flsaStatus: JsonField, private val gender: JsonField, private val income: JsonField, private val incomeHistory: JsonField>, @@ -261,6 +273,9 @@ private constructor( @JsonProperty("first_name") @ExcludeMissing firstName: JsonField = JsonMissing.of(), + @JsonProperty("flsa_status") + @ExcludeMissing + flsaStatus: JsonField = JsonMissing.of(), @JsonProperty("gender") @ExcludeMissing gender: JsonField = JsonMissing.of(), @JsonProperty("income") @ExcludeMissing income: JsonField = JsonMissing.of(), @JsonProperty("income_history") @@ -311,6 +326,7 @@ private constructor( endDate, ethnicity, firstName, + flsaStatus, gender, income, incomeHistory, @@ -416,6 +432,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 gender of the individual. * @@ -630,6 +654,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 [gender]. * @@ -790,6 +823,7 @@ private constructor( private var endDate: JsonField = JsonMissing.of() private var ethnicity: JsonField = JsonMissing.of() private var firstName: JsonField = JsonMissing.of() + private var flsaStatus: JsonField = JsonMissing.of() private var gender: JsonField = JsonMissing.of() private var income: JsonField = JsonMissing.of() private var incomeHistory: JsonField>? = null @@ -820,6 +854,7 @@ private constructor( endDate = individualOrEmployment.endDate ethnicity = individualOrEmployment.ethnicity firstName = individualOrEmployment.firstName + flsaStatus = individualOrEmployment.flsaStatus gender = individualOrEmployment.gender income = individualOrEmployment.income incomeHistory = individualOrEmployment.incomeHistory.map { it.toMutableList() } @@ -1014,6 +1049,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 + } + /** The gender of the individual. */ fun gender(gender: Gender?) = gender(JsonField.ofNullable(gender)) @@ -1289,6 +1341,7 @@ private constructor( endDate, ethnicity, firstName, + flsaStatus, gender, income, (incomeHistory ?: JsonMissing.of()).map { it.toImmutable() }, @@ -1311,6 +1364,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(): IndividualOrEmployment = apply { if (validated) { return@apply @@ -1327,6 +1389,7 @@ private constructor( endDate() ethnicity()?.validate() firstName() + flsaStatus()?.validate() gender()?.validate() income()?.validate() incomeHistory()?.forEach { it?.validate() } @@ -1372,6 +1435,7 @@ private constructor( (if (endDate.asKnown() == null) 0 else 1) + (ethnicity.asKnown()?.validity() ?: 0) + (if (firstName.asKnown() == null) 0 else 1) + + (flsaStatus.asKnown()?.validity() ?: 0) + (gender.asKnown()?.validity() ?: 0) + (income.asKnown()?.validity() ?: 0) + (incomeHistory.asKnown()?.sumOf { (it?.validity() ?: 0).toInt() } ?: 0) + @@ -1393,14 +1457,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()) /** @@ -1410,13 +1474,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]. @@ -1425,6 +1486,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) @@ -1447,7 +1515,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 { @@ -1467,7 +1535,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() @@ -1502,12 +1595,23 @@ 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 } @@ -1525,7 +1629,283 @@ 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? = null, + private val json: JsonValue? = null, + private val double: Double? = null, + private val boolean: Boolean? = null, + private val _json: JsonValue? = null, + ) { + + fun string(): String? = string + + fun jsonValues(): List? = jsonValues + + fun json(): JsonValue? = json + + fun double(): Double? = double + + fun boolean(): Boolean? = boolean + + fun isString(): Boolean = string != null + + fun isJsonValues(): Boolean = jsonValues != null + + fun isJson(): Boolean = json != null + + fun isDouble(): Boolean = double != null + + fun isBoolean(): Boolean = boolean != null + + fun asString(): String = string.getOrThrow("string") + + fun asJsonValues(): List = jsonValues.getOrThrow("jsonValues") + + fun asJson(): JsonValue = json.getOrThrow("json") + + fun asDouble(): Double = double.getOrThrow("double") + + fun asBoolean(): Boolean = boolean.getOrThrow("boolean") + + 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) + jsonValues != null -> visitor.visitJsonValues(jsonValues) + json != null -> visitor.visitJson(json) + double != null -> visitor.visitDouble(double) + boolean != null -> visitor.visitBoolean(boolean) + 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(): Value = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitString(string: String) {} + + override fun visitJsonValues(jsonValues: List) {} + + override fun visitJson(json: JsonValue) {} + + override fun visitDouble(double: Double) {} + + override fun visitBoolean(boolean: Boolean) {} + } + ) + 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 = + accept( + object : Visitor { + override fun visitString(string: String) = 1 + + override fun visitJsonValues(jsonValues: List) = + jsonValues.size + + override fun visitJson(json: JsonValue) = 1 + + override fun visitDouble(double: Double) = 1 + + override fun visitBoolean(boolean: Boolean) = 1 + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Value && + string == other.string && + jsonValues == other.jsonValues && + json == other.json && + double == other.double && + boolean == other.boolean + } + + override fun hashCode(): Int = + Objects.hash(string, jsonValues, json, double, boolean) + + override fun toString(): String = + when { + string != null -> "Value{string=$string}" + jsonValues != null -> "Value{jsonValues=$jsonValues}" + json != null -> "Value{json=$json}" + double != null -> "Value{double=$double}" + boolean != null -> "Value{boolean=$boolean}" + _json != null -> "Value{_unknown=$_json}" + else -> throw IllegalStateException("Invalid Value") + } + + companion object { + + fun ofString(string: String) = Value(string = string) + + fun ofJsonValues(jsonValues: List) = + Value(jsonValues = jsonValues.toImmutable()) + + fun ofJson(json: JsonValue) = Value(json = json) + + fun ofDouble(double: Double) = Value(double = double) + + fun ofBoolean(boolean: Boolean) = Value(boolean = boolean) + } + + /** + * An interface that defines how to map each variant of [Value] to a value of type + * [T]. + */ + interface Visitor { + + fun visitString(string: String): T + + fun visitJsonValues(jsonValues: List): T + + fun visitJson(json: JsonValue): T + + fun visitDouble(double: Double): T + + fun visitBoolean(boolean: Boolean): T + + /** + * Maps an unknown variant of [Value] to a value of type [T]. + * + * An instance of [Value] can contain an unknown variant if it was deserialized + * from data that doesn't match any known variant. For example, if the SDK is on + * an older version than the API, then the API may respond with new variants + * that the SDK is unaware of. + * + * @throws FinchInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw FinchInvalidDataException("Unknown Value: $json") + } + } + + internal class Deserializer : BaseDeserializer(Value::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): Value { + val json = JsonValue.fromJsonNode(node) + + val bestMatches = + sequenceOf( + tryDeserialize(node, jacksonTypeRef())?.let { + Value(string = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef>())?.let { + Value(jsonValues = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(double = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(boolean = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(json = it, _json = json) + }, + ) + .filterNotNull() + .allMaxBy { it.validity() } + .toList() + return when (bestMatches.size) { + // This can happen if what we're deserializing is completely + // incompatible with all the possible variants. + 0 -> Value(_json = json) + 1 -> bestMatches.single() + // If there's more than one match with the highest validity, then use + // the first completely valid match, or simply the first match if none + // are completely valid. + else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first() + } + } + } + + internal class Serializer : BaseSerializer(Value::class) { + + override fun serialize( + value: Value, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.string != null -> generator.writeObject(value.string) + value.jsonValues != null -> generator.writeObject(value.jsonValues) + value.json != null -> generator.writeObject(value.json) + value.double != null -> generator.writeObject(value.double) + value.boolean != null -> generator.writeObject(value.boolean) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid Value") + } + } + } + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -1647,6 +2027,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 @@ -1815,6 +2205,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(): Email = apply { if (validated) { return@apply @@ -1932,6 +2332,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 @@ -2128,6 +2538,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 @@ -2274,6 +2694,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 @@ -2403,6 +2833,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 @@ -2584,6 +3024,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 @@ -2750,6 +3200,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(): Ethnicity = apply { if (validated) { return@apply @@ -2788,6 +3248,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 gender of the individual. */ class Gender @JsonCreator private constructor(private val value: JsonField) : Enum { @@ -2890,6 +3498,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(): Gender = apply { if (validated) { return@apply @@ -3029,6 +3647,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 @@ -3197,6 +3825,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(): PhoneNumber = apply { if (validated) { return@apply @@ -3314,6 +3952,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 @@ -3388,6 +4036,7 @@ private constructor( endDate == other.endDate && ethnicity == other.ethnicity && firstName == other.firstName && + flsaStatus == other.flsaStatus && gender == other.gender && income == other.income && incomeHistory == other.incomeHistory && @@ -3420,6 +4069,7 @@ private constructor( endDate, ethnicity, firstName, + flsaStatus, gender, income, incomeHistory, @@ -3443,7 +4093,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "IndividualOrEmployment{classCode=$classCode, customFields=$customFields, department=$department, dob=$dob, emails=$emails, employment=$employment, employmentStatus=$employmentStatus, encryptedSsn=$encryptedSsn, endDate=$endDate, ethnicity=$ethnicity, firstName=$firstName, gender=$gender, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, phoneNumbers=$phoneNumbers, preferredName=$preferredName, residence=$residence, sourceId=$sourceId, ssn=$ssn, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" + "IndividualOrEmployment{classCode=$classCode, customFields=$customFields, department=$department, dob=$dob, emails=$emails, employment=$employment, employmentStatus=$employmentStatus, encryptedSsn=$encryptedSsn, endDate=$endDate, ethnicity=$ethnicity, firstName=$firstName, flsaStatus=$flsaStatus, gender=$gender, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, phoneNumbers=$phoneNumbers, preferredName=$preferredName, residence=$residence, sourceId=$sourceId, ssn=$ssn, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" } override fun equals(other: Any?): Boolean { diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParams.kt index 49c744235..7ce69ab1c 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParams.kt @@ -6,13 +6,24 @@ 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.Params +import com.tryfinch.api.core.allMaxBy import com.tryfinch.api.core.checkKnown +import com.tryfinch.api.core.getOrThrow import com.tryfinch.api.core.http.Headers import com.tryfinch.api.core.http.QueryParams import com.tryfinch.api.core.toImmutable @@ -86,6 +97,14 @@ private constructor( */ fun firstName(): String? = body.firstName() + /** + * 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? = body.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 information the @@ -220,6 +239,13 @@ private constructor( */ fun _firstName(): JsonField = body._firstName() + /** + * Returns the raw JSON value of [flsaStatus]. + * + * Unlike [flsaStatus], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _flsaStatus(): JsonField = body._flsaStatus() + /** * Returns the raw JSON value of [income]. * @@ -450,6 +476,20 @@ private constructor( */ fun firstName(firstName: JsonField) = apply { body.firstName(firstName) } + /** + * The FLSA status of the individual. Available options: `exempt`, `non_exempt`, `unknown`. + */ + fun flsaStatus(flsaStatus: FlsaStatus?) = apply { body.flsaStatus(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 { body.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 @@ -753,6 +793,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, @@ -788,6 +829,9 @@ private constructor( @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 @@ -823,6 +867,7 @@ private constructor( employmentStatus, endDate, firstName, + flsaStatus, income, incomeHistory, isActive, @@ -893,6 +938,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 @@ -1036,6 +1089,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]. * @@ -1149,6 +1211,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() @@ -1170,6 +1233,7 @@ private constructor( employmentStatus = employmentWithoutId.employmentStatus endDate = employmentWithoutId.endDate firstName = employmentWithoutId.firstName + flsaStatus = employmentWithoutId.flsaStatus income = employmentWithoutId.income incomeHistory = employmentWithoutId.incomeHistory.map { it.toMutableList() } isActive = employmentWithoutId.isActive @@ -1292,6 +1356,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 + } + /** * 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 @@ -1483,6 +1564,7 @@ private constructor( employmentStatus, endDate, firstName, + flsaStatus, income, (incomeHistory ?: JsonMissing.of()).map { it.toImmutable() }, isActive, @@ -1500,6 +1582,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(): EmploymentWithoutId = apply { if (validated) { return@apply @@ -1512,6 +1603,7 @@ private constructor( employmentStatus()?.validate() endDate() firstName() + flsaStatus()?.validate() income()?.validate() incomeHistory()?.forEach { it?.validate() } isActive() @@ -1548,6 +1640,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) + @@ -1573,6 +1666,7 @@ private constructor( employmentStatus == other.employmentStatus && endDate == other.endDate && firstName == other.firstName && + flsaStatus == other.flsaStatus && income == other.income && incomeHistory == other.incomeHistory && isActive == other.isActive && @@ -1596,6 +1690,7 @@ private constructor( employmentStatus, endDate, firstName, + flsaStatus, income, incomeHistory, isActive, @@ -1614,21 +1709,21 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "EmploymentWithoutId{classCode=$classCode, customFields=$customFields, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, sourceId=$sourceId, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" + "EmploymentWithoutId{classCode=$classCode, customFields=$customFields, department=$department, employment=$employment, employmentStatus=$employmentStatus, endDate=$endDate, firstName=$firstName, flsaStatus=$flsaStatus, income=$income, incomeHistory=$incomeHistory, isActive=$isActive, lastName=$lastName, latestRehireDate=$latestRehireDate, location=$location, manager=$manager, middleName=$middleName, sourceId=$sourceId, startDate=$startDate, title=$title, additionalProperties=$additionalProperties}" } class CustomField @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()) /** @@ -1638,12 +1733,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]. @@ -1652,6 +1745,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) @@ -1674,7 +1774,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 { @@ -1694,7 +1794,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() @@ -1725,12 +1850,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 } @@ -1748,7 +1883,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? = null, + private val json: JsonValue? = null, + private val double: Double? = null, + private val boolean: Boolean? = null, + private val _json: JsonValue? = null, + ) { + + fun string(): String? = string + + fun jsonValues(): List? = jsonValues + + fun json(): JsonValue? = json + + fun double(): Double? = double + + fun boolean(): Boolean? = boolean + + fun isString(): Boolean = string != null + + fun isJsonValues(): Boolean = jsonValues != null + + fun isJson(): Boolean = json != null + + fun isDouble(): Boolean = double != null + + fun isBoolean(): Boolean = boolean != null + + fun asString(): String = string.getOrThrow("string") + + fun asJsonValues(): List = jsonValues.getOrThrow("jsonValues") + + fun asJson(): JsonValue = json.getOrThrow("json") + + fun asDouble(): Double = double.getOrThrow("double") + + fun asBoolean(): Boolean = boolean.getOrThrow("boolean") + + 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) + jsonValues != null -> visitor.visitJsonValues(jsonValues) + json != null -> visitor.visitJson(json) + double != null -> visitor.visitDouble(double) + boolean != null -> visitor.visitBoolean(boolean) + 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(): Value = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitString(string: String) {} + + override fun visitJsonValues(jsonValues: List) {} + + override fun visitJson(json: JsonValue) {} + + override fun visitDouble(double: Double) {} + + override fun visitBoolean(boolean: Boolean) {} + } + ) + 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 = + accept( + object : Visitor { + override fun visitString(string: String) = 1 + + override fun visitJsonValues(jsonValues: List) = jsonValues.size + + override fun visitJson(json: JsonValue) = 1 + + override fun visitDouble(double: Double) = 1 + + override fun visitBoolean(boolean: Boolean) = 1 + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Value && + string == other.string && + jsonValues == other.jsonValues && + json == other.json && + double == other.double && + boolean == other.boolean + } + + override fun hashCode(): Int = Objects.hash(string, jsonValues, json, double, boolean) + + override fun toString(): String = + when { + string != null -> "Value{string=$string}" + jsonValues != null -> "Value{jsonValues=$jsonValues}" + json != null -> "Value{json=$json}" + double != null -> "Value{double=$double}" + boolean != null -> "Value{boolean=$boolean}" + _json != null -> "Value{_unknown=$_json}" + else -> throw IllegalStateException("Invalid Value") + } + + companion object { + + fun ofString(string: String) = Value(string = string) + + fun ofJsonValues(jsonValues: List) = + Value(jsonValues = jsonValues.toImmutable()) + + fun ofJson(json: JsonValue) = Value(json = json) + + fun ofDouble(double: Double) = Value(double = double) + + fun ofBoolean(boolean: Boolean) = Value(boolean = boolean) + } + + /** + * An interface that defines how to map each variant of [Value] to a value of type [T]. + */ + interface Visitor { + + fun visitString(string: String): T + + fun visitJsonValues(jsonValues: List): T + + fun visitJson(json: JsonValue): T + + fun visitDouble(double: Double): T + + fun visitBoolean(boolean: Boolean): T + + /** + * Maps an unknown variant of [Value] to a value of type [T]. + * + * An instance of [Value] can contain an unknown variant if it was deserialized from + * data that doesn't match any known variant. For example, if the SDK is on an older + * version than the API, then the API may respond with new variants that the SDK is + * unaware of. + * + * @throws FinchInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw FinchInvalidDataException("Unknown Value: $json") + } + } + + internal class Deserializer : BaseDeserializer(Value::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): Value { + val json = JsonValue.fromJsonNode(node) + + val bestMatches = + sequenceOf( + tryDeserialize(node, jacksonTypeRef())?.let { + Value(string = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef>())?.let { + Value(jsonValues = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(double = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(boolean = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Value(json = it, _json = json) + }, + ) + .filterNotNull() + .allMaxBy { it.validity() } + .toList() + return when (bestMatches.size) { + // This can happen if what we're deserializing is completely incompatible + // with all the possible variants. + 0 -> Value(_json = json) + 1 -> bestMatches.single() + // If there's more than one match with the highest validity, then use the + // first completely valid match, or simply the first match if none are + // completely valid. + else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first() + } + } + } + + internal class Serializer : BaseSerializer(Value::class) { + + override fun serialize( + value: Value, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.string != null -> generator.writeObject(value.string) + value.jsonValues != null -> generator.writeObject(value.jsonValues) + value.json != null -> generator.writeObject(value.json) + value.double != null -> generator.writeObject(value.double) + value.boolean != null -> generator.writeObject(value.boolean) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid Value") + } + } + } + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -1867,6 +2275,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 @@ -2043,6 +2460,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(): Employment = apply { if (validated) { return@apply @@ -2188,6 +2614,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 @@ -2314,6 +2750,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 @@ -2492,6 +2938,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(): EmploymentStatus = apply { if (validated) { return@apply @@ -2530,6 +2985,148 @@ 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) @@ -2628,6 +3225,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(): Manager = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxIndividualUpdateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxIndividualUpdateParams.kt index 2dc7e8e49..51686ff3a 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxIndividualUpdateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxIndividualUpdateParams.kt @@ -1101,6 +1101,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(): IndividualWithoutId = apply { if (validated) { return@apply @@ -1316,6 +1325,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(): Email = apply { if (validated) { return@apply @@ -1430,6 +1448,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 @@ -1612,6 +1640,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(): Ethnicity = apply { if (validated) { return@apply @@ -1749,6 +1786,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(): Gender = apply { if (validated) { return@apply @@ -1909,6 +1955,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(): PhoneNumber = apply { if (validated) { return@apply @@ -2023,6 +2078,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/SandboxJobConfiguration.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobConfiguration.kt index 2add6ca4b..0f0e0b8a6 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobConfiguration.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobConfiguration.kt @@ -166,6 +166,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(): SandboxJobConfiguration = apply { if (validated) { return@apply @@ -294,6 +302,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(): CompletionStatus = apply { if (validated) { return@apply @@ -412,6 +429,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/SandboxJobCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobCreateParams.kt index 443fef006..395daaa66 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxJobCreateParams.kt @@ -353,6 +353,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(): Body = apply { if (validated) { return@apply @@ -476,6 +485,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/SandboxPaymentCreateParams.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxPaymentCreateParams.kt index 2dcab82e9..c285b8bd6 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxPaymentCreateParams.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SandboxPaymentCreateParams.kt @@ -492,6 +492,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(): CreateSandboxPaymentRequest = apply { if (validated) { return@apply @@ -1025,6 +1034,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(): PayStatement = apply { if (validated) { return@apply @@ -1251,6 +1269,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(): Earning = apply { if (validated) { return@apply @@ -1439,6 +1467,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 @@ -1695,6 +1733,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(): EmployeeDeduction = apply { if (validated) { return@apply @@ -1919,6 +1967,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 @@ -2139,6 +2197,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(): EmployerContribution = apply { if (validated) { return@apply @@ -2361,6 +2429,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 @@ -2518,6 +2596,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(): PaymentMethod = apply { if (validated) { return@apply @@ -2741,6 +2829,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(): Tax = apply { if (validated) { return@apply @@ -2875,6 +2973,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 @@ -3029,6 +3137,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/SessionNewResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionNewResponse.kt index d1b5f746d..1d9da7739 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionNewResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionNewResponse.kt @@ -166,6 +166,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(): SessionNewResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionReauthenticateResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionReauthenticateResponse.kt index 7b21c4e8e..9a406d868 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionReauthenticateResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SessionReauthenticateResponse.kt @@ -167,6 +167,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(): SessionReauthenticateResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportPerBenefitType.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportPerBenefitType.kt index a3ee73467..3195dc579 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportPerBenefitType.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportPerBenefitType.kt @@ -157,6 +157,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(): SupportPerBenefitType = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportedBenefit.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportedBenefit.kt index af5e007ff..a44fa6060 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportedBenefit.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/SupportedBenefit.kt @@ -455,6 +455,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(): SupportedBenefit = apply { if (validated) { return@apply @@ -589,6 +597,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(): CompanyContribution = apply { if (validated) { return@apply @@ -717,6 +734,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(): EmployeeDeduction = apply { if (validated) { return@apply @@ -847,6 +873,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(): HsaContributionLimit = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UnenrolledIndividualBenefitResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UnenrolledIndividualBenefitResponse.kt index 61d811d6e..3597d6f3d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UnenrolledIndividualBenefitResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UnenrolledIndividualBenefitResponse.kt @@ -130,6 +130,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(): UnenrolledIndividualBenefitResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UpdateCompanyBenefitResponse.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UpdateCompanyBenefitResponse.kt index aba08d956..6712fa61f 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UpdateCompanyBenefitResponse.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/UpdateCompanyBenefitResponse.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(): UpdateCompanyBenefitResponse = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42005.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42005.kt index 921944b78..099d7aa80 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42005.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42005.kt @@ -200,6 +200,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(): W42005 = apply { if (validated) { return@apply @@ -525,6 +533,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 @@ -651,6 +668,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(): Exemption = apply { if (validated) { return@apply @@ -790,6 +817,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(): FilingStatus = apply { if (validated) { return@apply @@ -940,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(): Type = apply { if (validated) { return@apply diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42020.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42020.kt index e5353f409..887ed9985 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42020.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/W42020.kt @@ -200,6 +200,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(): W42020 = apply { if (validated) { return@apply @@ -665,6 +673,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 @@ -806,6 +823,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(): FilingStatus = apply { if (validated) { return@apply @@ -962,6 +989,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/WebhookEvent.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/WebhookEvent.kt index abb489b0e..304efcb92 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/WebhookEvent.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/models/WebhookEvent.kt @@ -82,6 +82,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? = webhookEvent.accept(object : WebhookEvent.Visitor { + * override fun visitAccountUpdated(accountUpdated: AccountUpdateEvent): String? = accountUpdated.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 { accountUpdated != null -> visitor.visitAccountUpdated(accountUpdated) @@ -97,6 +121,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(): WebhookEvent = apply { if (validated) { return@apply @@ -284,18 +316,26 @@ private constructor( override fun ObjectCodec.deserialize(node: JsonNode): WebhookEvent { val json = JsonValue.fromJsonNode(node) + val eventType = json.asObject()?.get("event_type")?.asString() + + when (eventType) { + "account.updated" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + WebhookEvent(accountUpdated = it, _json = json) + } ?: WebhookEvent(_json = json) + } + "company.updated" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + WebhookEvent(companyUpdated = it, _json = json) + } ?: WebhookEvent(_json = json) + } + } val bestMatches = sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - WebhookEvent(accountUpdated = it, _json = json) - }, tryDeserialize(node, jacksonTypeRef())?.let { WebhookEvent(jobCompletion = it, _json = json) }, - tryDeserialize(node, jacksonTypeRef())?.let { - WebhookEvent(companyUpdated = it, _json = json) - }, tryDeserialize(node, jacksonTypeRef())?.let { WebhookEvent(directory = it, _json = json) }, diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsync.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsync.kt index 17973d2c2..66351f3e1 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsync.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsync.kt @@ -43,13 +43,26 @@ interface AutomatedServiceAsync { * endpoint, please contact your Finch account manager. */ suspend fun create( - params: JobAutomatedCreateParams = JobAutomatedCreateParams.none(), + params: JobAutomatedCreateParams, requestOptions: RequestOptions = RequestOptions.none(), ): AutomatedCreateResponse /** @see create */ - suspend fun create(requestOptions: RequestOptions): AutomatedCreateResponse = - create(JobAutomatedCreateParams.none(), requestOptions) + suspend fun create( + body: JobAutomatedCreateParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): AutomatedCreateResponse = + create(JobAutomatedCreateParams.builder().body(body).build(), requestOptions) + + /** @see create */ + suspend fun create( + w4FormEmployeeSync: JobAutomatedCreateParams.Body.W4FormEmployeeSync, + requestOptions: RequestOptions = RequestOptions.none(), + ): AutomatedCreateResponse = + create( + JobAutomatedCreateParams.Body.ofW4FormEmployeeSync(w4FormEmployeeSync), + requestOptions, + ) /** Get an automated job by `job_id`. */ suspend fun retrieve( @@ -102,16 +115,28 @@ interface AutomatedServiceAsync { */ @MustBeClosed suspend fun create( - params: JobAutomatedCreateParams = JobAutomatedCreateParams.none(), + params: JobAutomatedCreateParams, requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor /** @see create */ @MustBeClosed suspend fun create( - requestOptions: RequestOptions + body: JobAutomatedCreateParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + create(JobAutomatedCreateParams.builder().body(body).build(), requestOptions) + + /** @see create */ + @MustBeClosed + suspend fun create( + w4FormEmployeeSync: JobAutomatedCreateParams.Body.W4FormEmployeeSync, + requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor = - create(JobAutomatedCreateParams.none(), requestOptions) + create( + JobAutomatedCreateParams.Body.ofW4FormEmployeeSync(w4FormEmployeeSync), + requestOptions, + ) /** * Returns a raw HTTP response for `get /jobs/automated/{job_id}`, but is otherwise the same diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncImpl.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncImpl.kt index 00e2c0c4a..aa1a4035d 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncImpl.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncImpl.kt @@ -81,7 +81,7 @@ class AutomatedServiceAsyncImpl internal constructor(private val clientOptions: .method(HttpMethod.POST) .baseUrl(clientOptions.baseUrl()) .addPathSegments("jobs", "automated") - .apply { params._body()?.let { body(json(clientOptions.jsonMapper, it)) } } + .body(json(clientOptions.jsonMapper, params._body())) .build() .prepareAsync(clientOptions, params) val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsync.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsync.kt index a11f2f43f..2d2738828 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsync.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsync.kt @@ -29,6 +29,13 @@ interface DirectoryServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): List + /** @see create */ + suspend fun create( + body: List, + requestOptions: RequestOptions = RequestOptions.none(), + ): List = + create(SandboxDirectoryCreateParams.builder().body(body).build(), requestOptions) + /** @see create */ suspend fun create(requestOptions: RequestOptions): List = create(SandboxDirectoryCreateParams.none(), requestOptions) @@ -57,6 +64,14 @@ interface DirectoryServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor> + /** @see create */ + @MustBeClosed + suspend fun create( + body: List, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor> = + create(SandboxDirectoryCreateParams.builder().body(body).build(), requestOptions) + /** @see create */ @MustBeClosed suspend fun create( diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedService.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedService.kt index 07598a8d1..c478efbff 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedService.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedService.kt @@ -43,13 +43,26 @@ interface AutomatedService { * endpoint, please contact your Finch account manager. */ fun create( - params: JobAutomatedCreateParams = JobAutomatedCreateParams.none(), + params: JobAutomatedCreateParams, requestOptions: RequestOptions = RequestOptions.none(), ): AutomatedCreateResponse /** @see create */ - fun create(requestOptions: RequestOptions): AutomatedCreateResponse = - create(JobAutomatedCreateParams.none(), requestOptions) + fun create( + body: JobAutomatedCreateParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): AutomatedCreateResponse = + create(JobAutomatedCreateParams.builder().body(body).build(), requestOptions) + + /** @see create */ + fun create( + w4FormEmployeeSync: JobAutomatedCreateParams.Body.W4FormEmployeeSync, + requestOptions: RequestOptions = RequestOptions.none(), + ): AutomatedCreateResponse = + create( + JobAutomatedCreateParams.Body.ofW4FormEmployeeSync(w4FormEmployeeSync), + requestOptions, + ) /** Get an automated job by `job_id`. */ fun retrieve( @@ -98,14 +111,28 @@ interface AutomatedService { */ @MustBeClosed fun create( - params: JobAutomatedCreateParams = JobAutomatedCreateParams.none(), + params: JobAutomatedCreateParams, requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor /** @see create */ @MustBeClosed - fun create(requestOptions: RequestOptions): HttpResponseFor = - create(JobAutomatedCreateParams.none(), requestOptions) + fun create( + body: JobAutomatedCreateParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + create(JobAutomatedCreateParams.builder().body(body).build(), requestOptions) + + /** @see create */ + @MustBeClosed + fun create( + w4FormEmployeeSync: JobAutomatedCreateParams.Body.W4FormEmployeeSync, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + create( + JobAutomatedCreateParams.Body.ofW4FormEmployeeSync(w4FormEmployeeSync), + requestOptions, + ) /** * Returns a raw HTTP response for `get /jobs/automated/{job_id}`, but is otherwise the same diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceImpl.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceImpl.kt index a3da93b34..0c60c4c71 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceImpl.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceImpl.kt @@ -81,7 +81,7 @@ class AutomatedServiceImpl internal constructor(private val clientOptions: Clien .method(HttpMethod.POST) .baseUrl(clientOptions.baseUrl()) .addPathSegments("jobs", "automated") - .apply { params._body()?.let { body(json(clientOptions.jsonMapper, it)) } } + .body(json(clientOptions.jsonMapper, params._body())) .build() .prepare(clientOptions, params) val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) diff --git a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryService.kt b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryService.kt index b7b376077..c2294b1db 100644 --- a/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryService.kt +++ b/finch-kotlin-core/src/main/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryService.kt @@ -29,6 +29,13 @@ interface DirectoryService { requestOptions: RequestOptions = RequestOptions.none(), ): List + /** @see create */ + fun create( + body: List, + requestOptions: RequestOptions = RequestOptions.none(), + ): List = + create(SandboxDirectoryCreateParams.builder().body(body).build(), requestOptions) + /** @see create */ fun create(requestOptions: RequestOptions): List = create(SandboxDirectoryCreateParams.none(), requestOptions) @@ -53,6 +60,14 @@ interface DirectoryService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor> + /** @see create */ + @MustBeClosed + fun create( + body: List, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor> = + create(SandboxDirectoryCreateParams.builder().body(body).build(), requestOptions) + /** @see create */ @MustBeClosed fun create(requestOptions: RequestOptions): HttpResponseFor> = diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/TestServerExtension.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/TestServerExtension.kt index 5e740846a..3c60724ca 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/TestServerExtension.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/TestServerExtension.kt @@ -15,25 +15,12 @@ class TestServerExtension : BeforeAllCallback, ExecutionCondition { } catch (e: Exception) { throw RuntimeException( """ - The test suite will not run without a mock Prism server running against your OpenAPI spec. + The test suite will not run without a mock server running against your OpenAPI spec. You can set the environment variable `SKIP_MOCK_TESTS` to `true` to skip running any tests that require the mock server. - To fix: - - 1. Install Prism (requires Node 16+): - - With npm: - $ npm install -g @stoplight/prism-cli - - With yarn: - $ yarn global add @stoplight/prism-cli - - 2. Run the mock server - - To run the server, pass in the path of your OpenAPI spec to the prism command: - $ prism mock path/to/your.openapi.yml + To fix run `./scripts/mock` in a separate terminal. """ .trimIndent(), e, diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/ClientOptionsTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/ClientOptionsTest.kt index 893b3b428..62df61358 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/ClientOptionsTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/ClientOptionsTest.kt @@ -16,6 +16,20 @@ internal class ClientOptionsTest { private val httpClient = mock() + @Test + fun putHeader_canOverwriteDefaultHeader() { + val clientOptions = + ClientOptions.builder() + .httpClient(httpClient) + .putHeader("User-Agent", "My User Agent") + .accessToken("My Access Token") + .clientId("4ab15e51-11ad-49f4-acae-f343b7794375") + .clientSecret("My Client Secret") + .build() + + assertThat(clientOptions.headers.values("User-Agent")).containsExactly("My User Agent") + } + @Test fun toBuilder_whenOriginalClientOptionsGarbageCollected_doesNotCloseOriginalClient() { var clientOptions = diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/HttpRequestBodiesTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/HttpRequestBodiesTest.kt new file mode 100644 index 000000000..1f55aa5e4 --- /dev/null +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/HttpRequestBodiesTest.kt @@ -0,0 +1,739 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.tryfinch.api.core.http + +import com.tryfinch.api.core.MultipartField +import com.tryfinch.api.core.jsonMapper +import java.io.ByteArrayOutputStream +import java.io.InputStream +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class HttpRequestBodiesTest { + + @Test + fun multipartFormData_serializesFieldWithFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "file" to + MultipartField.builder() + .value("hello") + .filename("hello.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = body.contentType()!!.substringAfter("multipart/form-data; boundary=") + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="file"; filename="hello.txt" + |Content-Type: text/plain + | + |hello + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesFieldWithoutFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("value") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStream() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "stream content".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |stream content + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesByteArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "binary" to + MultipartField.builder() + .value("abc".toByteArray()) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="binary" + |Content-Type: application/octet-stream + | + |abc + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesBooleanValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "flag" to + MultipartField.builder() + .value(true) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="flag" + |Content-Type: text/plain + | + |true + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNumberValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "count" to + MultipartField.builder().value(42).contentType("text/plain").build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="count" + |Content-Type: text/plain + | + |42 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNullValueAsNoParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "present" to + MultipartField.builder() + .value("yes") + .contentType("text/plain") + .build(), + "absent" to + MultipartField.builder() + .value(null as String?) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="present" + |Content-Type: text/plain + | + |yes + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "items" to + MultipartField.builder>() + .value(listOf("alpha", "beta", "gamma")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="items[]" + |Content-Type: text/plain + | + |alpha + |--$boundary + |Content-Disposition: form-data; name="items[]" + |Content-Type: text/plain + | + |beta + |--$boundary + |Content-Disposition: form-data; name="items[]" + |Content-Type: text/plain + | + |gamma + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesObjectAsNestedParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "meta" to + MultipartField.builder>() + .value(mapOf("key1" to "val1", "key2" to "val2")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="meta[key1]" + |Content-Type: text/plain + | + |val1 + |--$boundary + |Content-Disposition: form-data; name="meta[key2]" + |Content-Type: text/plain + | + |val2 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesMultipleFields() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "name" to + MultipartField.builder() + .value("Alice") + .contentType("text/plain") + .build(), + "age" to + MultipartField.builder().value(30).contentType("text/plain").build(), + "file" to + MultipartField.builder() + .value("file contents") + .filename("doc.txt") + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="name" + |Content-Type: text/plain + | + |Alice + |--$boundary + |Content-Disposition: form-data; name="age" + |Content-Type: text/plain + | + |30 + |--$boundary + |Content-Disposition: form-data; name="file"; filename="doc.txt" + |Content-Type: text/plain + | + |file contents + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_quotesSpecialCharactersInNameAndFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field\nname" to + MultipartField.builder() + .value("value") + .filename("file\r\"name.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field%0Aname"; filename="file%0D%22name.txt" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_writeIsRepeatable() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("repeatable") + .contentType("text/plain") + .build() + ), + ) + + val output1 = ByteArrayOutputStream() + body.writeTo(output1) + val output2 = ByteArrayOutputStream() + body.writeTo(output2) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output1.size().toLong()) + val boundary = boundary(body) + val expected = + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |repeatable + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + assertThat(output1.toString("UTF-8")).isEqualTo(expected) + assertThat(output2.toString("UTF-8")).isEqualTo(expected) + } + + @Test + fun multipartFormData_serializesByteArrayInputStream() { + // ByteArrayInputStream is specifically handled as repeatable with known content length. + val inputStream = "byte array stream".byteInputStream() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |byte array stream + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStreamWithFilename() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "file data".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "upload" to + MultipartField.builder() + .value(inputStream) + .filename("upload.bin") + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="upload"; filename="upload.bin" + |Content-Type: application/octet-stream + | + |file data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNestedArrayInObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder>>() + .value(mapOf("tags" to listOf("a", "b"))) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data[tags][]" + |Content-Type: text/plain + | + |a + |--$boundary + |Content-Disposition: form-data; name="data[tags][]" + |Content-Type: text/plain + | + |b + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_contentLengthIsUnknownWhenInputStreamPresent() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "text" to + MultipartField.builder() + .value("hello") + .contentType("text/plain") + .build(), + "stream" to + MultipartField.builder() + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the + // non-repeatable code path. + .value("data".byteInputStream().buffered()) + .contentType("application/octet-stream") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="text" + |Content-Type: text/plain + | + |hello + |--$boundary + |Content-Disposition: form-data; name="stream" + |Content-Type: application/octet-stream + | + |data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "items" to + MultipartField.builder>() + .value(emptyList()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "meta" to + MultipartField.builder>() + .value(emptyMap()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + private fun boundary(body: HttpRequestBody): String = + body.contentType()!!.substringAfter("multipart/form-data; boundary=") +} diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/LoggingHttpClientTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/LoggingHttpClientTest.kt new file mode 100644 index 000000000..f662a2498 --- /dev/null +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/LoggingHttpClientTest.kt @@ -0,0 +1,962 @@ +// 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 java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.io.PrintStream +import java.nio.charset.StandardCharsets +import java.time.Clock +import java.time.Instant +import java.time.ZoneOffset +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.ResourceLock +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@ResourceLock("stderr") +internal class LoggingHttpClientTest { + + private lateinit var originalErr: PrintStream + private lateinit var errContent: ByteArrayOutputStream + + @BeforeEach + fun beforeEach() { + originalErr = System.err + errContent = ByteArrayOutputStream() + System.setErr(PrintStream(errContent)) + } + + @AfterEach + fun afterEach() { + System.setErr(originalErr) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun offLevel_noOutput(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.OFF) + + val response = client.execute(simpleGetRequest(), async).apply { body().readBytes() } + + assertThat(response.statusCode()).isEqualTo(200) + assertThat(stderrOutput()).isEmpty() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_logsGetRequest(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.INFO) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- 200 (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_logsPostRequestWithBodySize(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.INFO) + + client.execute(postRequestWithBody("""{"key":"value"}"""), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> POST https://api.example.com/v1/resources (15-byte body) + |<-- 200 (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_logsRequestWithUnknownLengthBody(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.INFO) + + client + .execute(postRequestWithBody("""{"key":"value"}""", contentLength = -1L), async) + .body() + .readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> POST https://api.example.com/v1/resources (unknown-length body) + |<-- 200 (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_logsResponseStatusAndDuration(async: Boolean) { + val clock = + clockFrom( + Instant.parse("1998-04-21T00:00:00Z"), + Instant.parse("1998-04-21T00:00:01.234Z"), + ) + val client = loggingClient(fakeHttpClient(statusCode = 201), LogLevel.INFO, clock) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- 201 (1s 234ms) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_logsResponseContentLength(async: Boolean) { + val headers = + Headers.builder().put("Content-Length", "42").put("Content-Type", "text/plain").build() + val client = loggingClient(fakeHttpClient(responseHeaders = headers), LogLevel.INFO) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- 200 (0s, 42-byte body) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_doesNotLogHeaders(async: Boolean) { + val headers = Headers.builder().put("X-Custom", "visible").build() + val client = loggingClient(fakeHttpClient(responseHeaders = headers), LogLevel.INFO) + + client + .execute( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("v1") + .putHeader("X-Request-Custom", "req-value") + .build(), + async, + ) + .body() + .readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1 + |<-- 200 (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsGetWithEndMarker(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.DEBUG) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsRequestAndResponseHeaders(async: Boolean) { + val responseHeaders = + Headers.builder() + .put("X-Response-Id", "abc-123") + .put("Content-Type", "text/plain") + .build() + val client = + loggingClient(fakeHttpClient(responseHeaders = responseHeaders), LogLevel.DEBUG) + + client + .execute( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("test") + .putHeader("X-Custom", "my-value") + .build(), + async, + ) + .body() + .readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/test + |X-Custom: my-value + |--> END GET + | + |<-- 200 (0s) + |Content-Type: text/plain + |X-Response-Id: abc-123 + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_redactsSensitiveHeaders(async: Boolean) { + val client = + loggingClient( + fakeHttpClient(), + LogLevel.DEBUG, + redactedHeaders = setOf("Authorization", "X-Secret"), + ) + + client + .execute( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("test") + .putHeader("Authorization", "Bearer token-123") + .putHeader("X-Secret", "secret-value") + .putHeader("X-Public", "public-value") + .build(), + async, + ) + .body() + .readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/test + |Authorization: ██ + |X-Public: public-value + |X-Secret: ██ + |--> END GET + | + |<-- 200 (0s) + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_redactsHeadersCaseInsensitively(async: Boolean) { + val client = + loggingClient( + fakeHttpClient(), + LogLevel.DEBUG, + redactedHeaders = setOf("Authorization"), + ) + + client + .execute( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("test") + .putHeader("authorization", "Bearer secret") + .build(), + async, + ) + .body() + .readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/test + |authorization: ██ + |--> END GET + | + |<-- 200 (0s) + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsRequestBody(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.DEBUG) + val body = """{"name":"test","value":42}""" + + client.execute(postRequestWithBody(body), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> POST https://api.example.com/v1/resources (26-byte body) + | + |{"name":"test","value":42} + |--> END POST (26-byte body) + | + |<-- 200 (0s) + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsResponseBody(async: Boolean) { + val responseBody = """{"id":1,"status":"ok"}""" + val headers = Headers.builder().put("Content-Type", "application/json").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + val response = client.execute(simpleGetRequest(), async) + val body = response.body().readBytes().toString(StandardCharsets.UTF_8) + + assertThat(body).isEqualTo(responseBody) + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: application/json + | + |{"id":1,"status":"ok"} + |<-- END HTTP (22-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsBinaryResponseBodyAsOmitted(async: Boolean) { + val binaryBody = ByteArray(256) { it.toByte() } + val client = loggingClient(fakeHttpClient(responseBody = binaryBody), LogLevel.DEBUG) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + | + |(binary body omitted) + |<-- END HTTP (256-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsMultilineResponseBody(async: Boolean) { + val multilineBody = "line1\nline2\nline3" + val headers = Headers.builder().put("Content-Type", "text/plain; charset=utf-8").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = multilineBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: text/plain; charset=utf-8 + | + |line1 + |line2 + |line3 + |<-- END HTTP (17-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsResponseBodyWithExplicitCharset(async: Boolean) { + val responseBody = "héllo wörld" + val headers = Headers.builder().put("Content-Type", "text/plain; charset=utf-8").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: text/plain; charset=utf-8 + | + |héllo wörld + |<-- END HTTP (13-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsResponseBodyWithNoContentType(async: Boolean) { + val responseBody = "plain text body" + val client = + loggingClient( + fakeHttpClient(responseBody = responseBody.toByteArray(StandardCharsets.UTF_8)), + LogLevel.DEBUG, + ) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + | + |plain text body + |<-- END HTTP (15-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsEmptyResponseBody(async: Boolean) { + val client = loggingClient(fakeHttpClient(), LogLevel.DEBUG) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |<-- END HTTP (0-byte body) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsEndHttpMarkerOnEarlyClose(async: Boolean) { + val responseBody = """{"id":1,"status":"ok"}""" + val headers = Headers.builder().put("Content-Type", "application/json").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + val body = client.execute(simpleGetRequest(), async).body() + body.read(ByteArray(5)) + body.close() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: application/json + | + |{"id" + |<-- END HTTP (5-byte body, closed early) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsEndHttpMarkerOnCloseWithoutReading(async: Boolean) { + val responseBody = """{"id":1,"status":"ok"}""" + val headers = Headers.builder().put("Content-Type", "application/json").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + client.execute(simpleGetRequest(), async).body().close() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: application/json + |<-- END HTTP (0-byte body, closed early) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsEndHttpMarkerWhenResponseClosedAfterPartialRead(async: Boolean) { + val responseBody = """{"id":1,"status":"ok"}""" + val headers = Headers.builder().put("Content-Type", "application/json").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + val response = client.execute(simpleGetRequest(), async) + response.body().read(ByteArray(5)) + response.close() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: application/json + | + |{"id" + |<-- END HTTP (5-byte body, closed early) + | + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_doesNotLogEndHttpMarkerWhenResponseClosedWithoutBodyAccess(async: Boolean) { + val responseBody = """{"id":1,"status":"ok"}""" + val headers = Headers.builder().put("Content-Type", "application/json").build() + val client = + loggingClient( + fakeHttpClient( + responseHeaders = headers, + responseBody = responseBody.toByteArray(StandardCharsets.UTF_8), + ), + LogLevel.DEBUG, + ) + + client.execute(simpleGetRequest(), async).close() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- 200 (0s) + |Content-Type: application/json + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun errorLevel_logsRequestFailure(async: Boolean) { + val clock = + clockFrom( + Instant.parse("1998-04-21T00:00:00Z"), + Instant.parse("1998-04-21T00:00:01.234Z"), + ) + val client = + loggingClient( + failingHttpClient(IOException("Connection refused")), + LogLevel.ERROR, + clock, + ) + + assertThatThrownBy { client.execute(simpleGetRequest(), async) } + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- !! IOException: Connection refused (1s 234ms) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun infoLevel_doesNotLogRequestFailure(async: Boolean) { + val client = + loggingClient(failingHttpClient(IOException("Connection refused")), LogLevel.INFO) + + assertThatThrownBy { client.execute(simpleGetRequest(), async) } + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun debugLevel_logsRequestFailureAfterHeaders(async: Boolean) { + val client = + loggingClient(failingHttpClient(IOException("Connection refused")), LogLevel.DEBUG) + + assertThatThrownBy { client.execute(simpleGetRequest(), async) } + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |--> END GET + | + |<-- !! IOException: Connection refused (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun errorLevel_logsRequestFailureWithoutMessage(async: Boolean) { + val client = loggingClient(failingHttpClient(IOException()), LogLevel.ERROR) + + assertThatThrownBy { client.execute(simpleGetRequest(), async) } + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- !! IOException (0s) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun offLevel_doesNotLogRequestFailure(async: Boolean) { + val client = + loggingClient(failingHttpClient(IOException("Connection refused")), LogLevel.OFF) + + assertThatThrownBy { client.execute(simpleGetRequest(), async) } + + assertThat(stderrOutput()).isEmpty() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun durationFormat_seconds(async: Boolean) { + val clock = + clockFrom( + Instant.parse("1998-04-21T00:00:00Z"), + Instant.parse("1998-04-21T00:00:02.500Z"), + ) + val client = loggingClient(fakeHttpClient(), LogLevel.INFO, clock) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- 200 (2s 500ms) + |""" + .trimMargin() + ) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun durationFormat_minutesAndSeconds(async: Boolean) { + val clock = + clockFrom( + Instant.parse("1998-04-21T00:00:00Z"), + Instant.parse("1998-04-21T00:01:40.467Z"), + ) + val client = loggingClient(fakeHttpClient(), LogLevel.INFO, clock) + + client.execute(simpleGetRequest(), async).body().readBytes() + + assertThat(stderrOutput()) + .isEqualTo( + """ + |--> GET https://api.example.com/v1/resources + |<-- 200 (1m 40s 467ms) + |""" + .trimMargin() + ) + } + + @Test + fun builder_toBuilder_roundtrips() { + val delegate = fakeHttpClient() + val clock = Clock.fixed(Instant.parse("1998-04-21T00:00:00Z"), ZoneOffset.UTC) + val client = + LoggingHttpClient.builder() + .httpClient(delegate) + .level(LogLevel.DEBUG) + .redactedHeaders(setOf("X-Secret")) + .clock(clock) + .build() + + val rebuilt = client.toBuilder().build() + + assertThat(rebuilt.httpClient).isSameAs(delegate) + assertThat(rebuilt.level).isEqualTo(LogLevel.DEBUG) + assertThat(rebuilt.redactedHeaders).containsExactly("X-Secret") + assertThat(rebuilt.clock).isEqualTo(clock) + } + + @Test + fun close_delegatesToUnderlyingClient() { + var closed = false + val delegate = + object : HttpClient { + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse = throw UnsupportedOperationException() + + override suspend fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse = throw UnsupportedOperationException() + + override fun close() { + closed = true + } + } + val client = loggingClient(delegate, LogLevel.OFF) + + client.close() + + assertThat(closed).isTrue() + } + + private fun stderrOutput(): String = errContent.toString("UTF-8") + + private fun loggingClient( + httpClient: HttpClient, + level: LogLevel, + clock: Clock = clockFrom(Instant.parse("1998-04-21T00:00:00Z")), + redactedHeaders: Set = + setOf("authorization", "api-key", "x-api-key", "cookie", "set-cookie"), + ): LoggingHttpClient = + LoggingHttpClient.builder() + .httpClient(httpClient) + .level(level) + .clock(clock) + .redactedHeaders(redactedHeaders) + .build() + + private fun simpleGetRequest(): HttpRequest = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("v1") + .addPathSegment("resources") + .build() + + private fun postRequestWithBody( + body: String, + contentType: String = "application/json", + contentLength: Long? = null, + ): HttpRequest = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl("https://api.example.com") + .addPathSegment("v1") + .addPathSegment("resources") + .body( + object : HttpRequestBody { + private val bytes = body.toByteArray(StandardCharsets.UTF_8) + + override fun writeTo(outputStream: OutputStream) { + outputStream.write(bytes) + } + + override fun contentType(): String = contentType + + override fun contentLength(): Long = contentLength ?: bytes.size.toLong() + + override fun repeatable(): Boolean = true + + override fun close() {} + } + ) + .build() + + private fun fakeHttpClient( + statusCode: Int = 200, + responseHeaders: Headers = Headers.builder().build(), + responseBody: ByteArray = ByteArray(0), + ): HttpClient = + object : HttpClient { + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse { + // Consume the request body if present to trigger logging. + request.body?.let { + val out = ByteArrayOutputStream() + it.writeTo(out) + } + return fakeResponse(statusCode, responseHeaders, responseBody) + } + + override suspend fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse = execute(request, requestOptions) + + override fun close() {} + } + + private fun failingHttpClient(error: Throwable): HttpClient = + object : HttpClient { + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse { + request.body?.let { + val out = ByteArrayOutputStream() + it.writeTo(out) + } + throw error + } + + override suspend fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse = throw error + + override fun close() {} + } + + private fun fakeResponse(statusCode: Int, headers: Headers, body: ByteArray): HttpResponse = + object : HttpResponse { + override fun statusCode(): Int = statusCode + + override fun headers(): Headers = headers + + override fun body(): InputStream = ByteArrayInputStream(body) + + override fun close() {} + } + + private fun clockFrom(vararg instants: Instant): Clock = + object : Clock() { + private var index = 0 + + override fun getZone() = ZoneOffset.UTC + + override fun withZone(zone: java.time.ZoneId?) = this + + override fun instant(): Instant { + val instant = instants[index % instants.size] + index++ + return instant + } + } + + private fun HttpClient.execute(request: HttpRequest, async: Boolean): HttpResponse = + if (async) runBlocking { executeAsync(request) } else execute(request) +} diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/RetryingHttpClientTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/RetryingHttpClientTest.kt index 3feb1ad30..679b9db26 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/RetryingHttpClientTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/core/http/RetryingHttpClientTest.kt @@ -1,6 +1,17 @@ +// File generated from our OpenAPI spec by Stainless. + package com.tryfinch.api.core.http -import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.matching +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.resetAllScenarios +import com.github.tomakehurst.wiremock.client.WireMock.serviceUnavailable +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.verify import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest import com.github.tomakehurst.wiremock.stubbing.Scenario @@ -9,7 +20,11 @@ import com.tryfinch.api.core.RequestOptions import com.tryfinch.api.core.Sleeper import com.tryfinch.api.errors.FinchRetryableException import java.io.InputStream +import java.time.Clock import java.time.Duration +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -25,6 +40,20 @@ internal class RetryingHttpClientTest { private lateinit var baseUrl: String private lateinit var httpClient: HttpClient + private class RecordingSleeper : Sleeper { + val durations = mutableListOf() + + override fun sleep(duration: Duration) { + durations.add(duration) + } + + override suspend fun sleepAsync(duration: Duration) { + durations.add(duration) + } + + override fun close() {} + } + @BeforeEach fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { baseUrl = wmRuntimeInfo.httpBaseUrl @@ -74,7 +103,8 @@ internal class RetryingHttpClientTest { @ValueSource(booleans = [false, true]) fun execute(async: Boolean) { stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) - val retryingClient = retryingHttpClientBuilder().build() + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).build() val response = retryingClient.execute( @@ -88,6 +118,7 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() assertNoResponseLeaks() } @@ -99,8 +130,12 @@ internal class RetryingHttpClientTest { .withHeader("X-Some-Header", matching("stainless-java-retry-.+")) .willReturn(ok()) ) + val sleeper = RecordingSleeper() val retryingClient = - retryingHttpClientBuilder().maxRetries(2).idempotencyHeader("X-Some-Header").build() + retryingHttpClientBuilder(sleeper) + .maxRetries(2) + .idempotencyHeader("X-Some-Header") + .build() val response = retryingClient.execute( @@ -114,20 +149,20 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() assertNoResponseLeaks() } @ParameterizedTest @ValueSource(booleans = [false, true]) fun execute_withRetryAfterHeader(async: Boolean) { + val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT" stubFor( post(urlPathEqualTo("/something")) // First we fail with a retry after header given as a date .inScenario("foo") .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT") - ) + .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate)) .willSetStateTo("RETRY_AFTER_DATE") ) stubFor( @@ -146,7 +181,13 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(2).build() + // Fix the clock to 5 seconds before the Retry-After date so the date-based backoff is + // deterministic. + val retryAfterDateTime = + OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME) + val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build() val response = retryingClient.execute( @@ -174,19 +215,20 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("2")), ) + assertThat(sleeper.durations) + .containsExactly(Duration.ofSeconds(5), Duration.ofMillis(1234)) assertNoResponseLeaks() } @ParameterizedTest @ValueSource(booleans = [false, true]) fun execute_withOverwrittenRetryCountHeader(async: Boolean) { + val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT" stubFor( post(urlPathEqualTo("/something")) .inScenario("foo") // first we fail with a retry after header given as a date .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT") - ) + .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate)) .willSetStateTo("RETRY_AFTER_DATE") ) stubFor( @@ -196,7 +238,11 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(2).build() + val retryAfterDateTime = + OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME) + val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build() val response = retryingClient.execute( @@ -215,6 +261,7 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("42")), ) + assertThat(sleeper.durations).containsExactly(Duration.ofSeconds(5)) assertNoResponseLeaks() } @@ -235,7 +282,8 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(1).build() + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() val response = retryingClient.execute( @@ -249,6 +297,7 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(2, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(10)) assertNoResponseLeaks() } @@ -285,20 +334,12 @@ internal class RetryingHttpClientTest { override fun close() = httpClient.close() } + val sleeper = RecordingSleeper() val retryingClient = RetryingHttpClient.builder() .httpClient(failingHttpClient) .maxRetries(2) - .sleeper( - object : Sleeper { - - override fun sleep(duration: Duration) {} - - override suspend fun sleepAsync(duration: Duration) {} - - override fun close() {} - } - ) + .sleeper(sleeper) .build() val response = @@ -322,24 +363,153 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("0")), ) + // Exponential backoff with jitter: 0.5s * jitter where jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoff(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(3).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + // All retries exhausted; the last 503 response is returned. + assertThat(response.statusCode()).isEqualTo(503) + verify(4, postRequestedFor(urlPathEqualTo("/something"))) + // Exponential backoff with jitter: backoff = min(0.5 * 2^(retries-1), 8) * jitter where + // jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(3) + // retries=1: 0.5s * [0.75, 1.0] + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + // retries=2: 1s * [0.75, 1.0] + assertThat(sleeper.durations[1]).isBetween(Duration.ofMillis(750), Duration.ofMillis(1000)) + // retries=3: 2s * [0.75, 1.0] + assertThat(sleeper.durations[2]).isBetween(Duration.ofMillis(1500), Duration.ofMillis(2000)) assertNoResponseLeaks() } - private fun retryingHttpClientBuilder() = - RetryingHttpClient.builder() - .httpClient(httpClient) - // Use a no-op `Sleeper` to make the test fast. - .sleeper( - object : Sleeper { + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoffCap(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(6).build() - override fun sleep(duration: Duration) {} + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) - override suspend fun sleepAsync(duration: Duration) {} + assertThat(response.statusCode()).isEqualTo(503) + verify(7, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).hasSize(6) + // retries=5: backoff hits the 8s cap * [0.75, 1.0] + assertThat(sleeper.durations[4]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + // retries=6: still capped at 8s * [0.75, 1.0] + assertThat(sleeper.durations[5]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + assertNoResponseLeaks() + } - override fun close() {} - } + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterMsPriorityOverRetryAfter(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + serviceUnavailable() + .withHeader("Retry-After-Ms", "50") + .withHeader("Retry-After", "2") + ) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + // Retry-After-Ms (50ms) takes priority over Retry-After (2s). + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(50)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterUnparseable(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(serviceUnavailable().withHeader("Retry-After", "not-a-date-or-number")) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, ) + assertThat(response.statusCode()).isEqualTo(200) + // Unparseable Retry-After falls through to exponential backoff. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + assertNoResponseLeaks() + } + + private fun retryingHttpClientBuilder( + sleeper: RecordingSleeper, + clock: Clock = Clock.systemUTC(), + ) = RetryingHttpClient.builder().httpClient(httpClient).sleeper(sleeper).clock(clock) + private fun HttpClient.execute(request: HttpRequest, async: Boolean): HttpResponse = if (async) runBlocking { executeAsync(request) } else execute(request) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/AccountUpdateEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/AccountUpdateEventTest.kt index f0fb2fa3e..68d500754 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/AccountUpdateEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/AccountUpdateEventTest.kt @@ -16,6 +16,7 @@ internal class AccountUpdateEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( AccountUpdateEvent.Data.builder() .authenticationMethod( @@ -942,6 +943,7 @@ internal class AccountUpdateEventTest { assertThat(accountUpdateEvent.accountId()).isEqualTo("account_id") assertThat(accountUpdateEvent.companyId()).isEqualTo("company_id") assertThat(accountUpdateEvent.connectionId()).isEqualTo("connection_id") + assertThat(accountUpdateEvent.entityId()).isEqualTo("entity_id") assertThat(accountUpdateEvent.data()) .isEqualTo( AccountUpdateEvent.Data.builder() @@ -1814,6 +1816,7 @@ internal class AccountUpdateEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( AccountUpdateEvent.Data.builder() .authenticationMethod( diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BaseWebhookEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BaseWebhookEventTest.kt index ff70d4248..af6d64ef4 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BaseWebhookEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BaseWebhookEventTest.kt @@ -16,11 +16,13 @@ internal class BaseWebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .build() assertThat(baseWebhookEvent.accountId()).isEqualTo("account_id") assertThat(baseWebhookEvent.companyId()).isEqualTo("company_id") assertThat(baseWebhookEvent.connectionId()).isEqualTo("connection_id") + assertThat(baseWebhookEvent.entityId()).isEqualTo("entity_id") } @Test @@ -31,6 +33,7 @@ internal class BaseWebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .build() val roundtrippedBaseWebhookEvent = diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BenefitContributionTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BenefitContributionTest.kt index bf96a4d06..d22d4de47 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BenefitContributionTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/BenefitContributionTest.kt @@ -15,28 +15,28 @@ import org.junit.jupiter.params.provider.EnumSource internal class BenefitContributionTest { @Test - fun ofUnionMember0() { - val unionMember0 = - BenefitContribution.UnionMember0.builder() + fun ofFixed() { + val fixed = + BenefitContribution.BenefitContributionFixed.builder() .amount(0L) - .type(BenefitContribution.UnionMember0.Type.FIXED) + .type(BenefitContribution.BenefitContributionFixed.Type.FIXED) .build() - val benefitContribution = BenefitContribution.ofUnionMember0(unionMember0) + val benefitContribution = BenefitContribution.ofFixed(fixed) - assertThat(benefitContribution.unionMember0()).isEqualTo(unionMember0) - assertThat(benefitContribution.unionMember1()).isNull() - assertThat(benefitContribution.unionMember2()).isNull() + assertThat(benefitContribution.fixed()).isEqualTo(fixed) + assertThat(benefitContribution.percent()).isNull() + assertThat(benefitContribution.tiered()).isNull() } @Test - fun ofUnionMember0Roundtrip() { + fun ofFixedRoundtrip() { val jsonMapper = jsonMapper() val benefitContribution = - BenefitContribution.ofUnionMember0( - BenefitContribution.UnionMember0.builder() + BenefitContribution.ofFixed( + BenefitContribution.BenefitContributionFixed.builder() .amount(0L) - .type(BenefitContribution.UnionMember0.Type.FIXED) + .type(BenefitContribution.BenefitContributionFixed.Type.FIXED) .build() ) @@ -50,28 +50,28 @@ internal class BenefitContributionTest { } @Test - fun ofUnionMember1() { - val unionMember1 = - BenefitContribution.UnionMember1.builder() + fun ofPercent() { + val percent = + BenefitContribution.BenefitContributionPercent.builder() .amount(0L) - .type(BenefitContribution.UnionMember1.Type.PERCENT) + .type(BenefitContribution.BenefitContributionPercent.Type.PERCENT) .build() - val benefitContribution = BenefitContribution.ofUnionMember1(unionMember1) + val benefitContribution = BenefitContribution.ofPercent(percent) - assertThat(benefitContribution.unionMember0()).isNull() - assertThat(benefitContribution.unionMember1()).isEqualTo(unionMember1) - assertThat(benefitContribution.unionMember2()).isNull() + assertThat(benefitContribution.fixed()).isNull() + assertThat(benefitContribution.percent()).isEqualTo(percent) + assertThat(benefitContribution.tiered()).isNull() } @Test - fun ofUnionMember1Roundtrip() { + fun ofPercentRoundtrip() { val jsonMapper = jsonMapper() val benefitContribution = - BenefitContribution.ofUnionMember1( - BenefitContribution.UnionMember1.builder() + BenefitContribution.ofPercent( + BenefitContribution.BenefitContributionPercent.builder() .amount(0L) - .type(BenefitContribution.UnionMember1.Type.PERCENT) + .type(BenefitContribution.BenefitContributionPercent.Type.PERCENT) .build() ) @@ -85,35 +85,38 @@ internal class BenefitContributionTest { } @Test - fun ofUnionMember2() { - val unionMember2 = - BenefitContribution.UnionMember2.builder() + fun ofTiered() { + val tiered = + BenefitContribution.BenefitContributionTiered.builder() .addTier( - BenefitContribution.UnionMember2.Tier.builder().match(1L).threshold(1L).build() + BenefitContribution.BenefitContributionTiered.Tier.builder() + .match(1L) + .threshold(1L) + .build() ) - .type(BenefitContribution.UnionMember2.Type.TIERED) + .type(BenefitContribution.BenefitContributionTiered.Type.TIERED) .build() - val benefitContribution = BenefitContribution.ofUnionMember2(unionMember2) + val benefitContribution = BenefitContribution.ofTiered(tiered) - assertThat(benefitContribution.unionMember0()).isNull() - assertThat(benefitContribution.unionMember1()).isNull() - assertThat(benefitContribution.unionMember2()).isEqualTo(unionMember2) + assertThat(benefitContribution.fixed()).isNull() + assertThat(benefitContribution.percent()).isNull() + assertThat(benefitContribution.tiered()).isEqualTo(tiered) } @Test - fun ofUnionMember2Roundtrip() { + fun ofTieredRoundtrip() { val jsonMapper = jsonMapper() val benefitContribution = - BenefitContribution.ofUnionMember2( - BenefitContribution.UnionMember2.builder() + BenefitContribution.ofTiered( + BenefitContribution.BenefitContributionTiered.builder() .addTier( - BenefitContribution.UnionMember2.Tier.builder() + BenefitContribution.BenefitContributionTiered.Tier.builder() .match(1L) .threshold(1L) .build() ) - .type(BenefitContribution.UnionMember2.Type.TIERED) + .type(BenefitContribution.BenefitContributionTiered.Type.TIERED) .build() ) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CompanyEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CompanyEventTest.kt index 56e20c29a..9cf1e12a0 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CompanyEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CompanyEventTest.kt @@ -17,6 +17,7 @@ internal class CompanyEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( CompanyEvent.Data.builder() .putAdditionalProperty("foo", JsonValue.from("bar")) @@ -28,6 +29,7 @@ internal class CompanyEventTest { assertThat(companyEvent.accountId()).isEqualTo("account_id") assertThat(companyEvent.companyId()).isEqualTo("company_id") assertThat(companyEvent.connectionId()).isEqualTo("connection_id") + assertThat(companyEvent.entityId()).isEqualTo("entity_id") assertThat(companyEvent.data()) .isEqualTo( CompanyEvent.Data.builder() @@ -45,6 +47,7 @@ internal class CompanyEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( CompanyEvent.Data.builder() .putAdditionalProperty("foo", JsonValue.from("bar")) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CreateAccessTokenResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CreateAccessTokenResponseTest.kt index f9bc219f2..c515dcb20 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CreateAccessTokenResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/CreateAccessTokenResponseTest.kt @@ -24,6 +24,7 @@ internal class CreateAccessTokenResponseTest { .accountId("account_id") .companyId("company_id") .customerId("customer_id") + .customerName("customer_name") .build() assertThat(createAccessTokenResponse.accessToken()).isEqualTo("access_token") @@ -40,6 +41,7 @@ internal class CreateAccessTokenResponseTest { assertThat(createAccessTokenResponse.accountId()).isEqualTo("account_id") assertThat(createAccessTokenResponse.companyId()).isEqualTo("company_id") assertThat(createAccessTokenResponse.customerId()).isEqualTo("customer_id") + assertThat(createAccessTokenResponse.customerName()).isEqualTo("customer_name") } @Test @@ -58,6 +60,7 @@ internal class CreateAccessTokenResponseTest { .accountId("account_id") .companyId("company_id") .customerId("customer_id") + .customerName("customer_name") .build() val roundtrippedCreateAccessTokenResponse = diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/DirectoryEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/DirectoryEventTest.kt index 1e6b70470..e4f001556 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/DirectoryEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/DirectoryEventTest.kt @@ -16,6 +16,7 @@ internal class DirectoryEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(DirectoryEvent.Data.builder().individualId("individual_id").build()) .eventType(DirectoryEvent.EventType.DIRECTORY_CREATED) .build() @@ -23,6 +24,7 @@ internal class DirectoryEventTest { assertThat(directoryEvent.accountId()).isEqualTo("account_id") assertThat(directoryEvent.companyId()).isEqualTo("company_id") assertThat(directoryEvent.connectionId()).isEqualTo("connection_id") + assertThat(directoryEvent.entityId()).isEqualTo("entity_id") assertThat(directoryEvent.data()) .isEqualTo(DirectoryEvent.Data.builder().individualId("individual_id").build()) assertThat(directoryEvent.eventType()).isEqualTo(DirectoryEvent.EventType.DIRECTORY_CREATED) @@ -36,6 +38,7 @@ internal class DirectoryEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(DirectoryEvent.Data.builder().individualId("individual_id").build()) .eventType(DirectoryEvent.EventType.DIRECTORY_CREATED) .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataResponseTest.kt index 1fe2d86cd..5d61fcf48 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataResponseTest.kt @@ -15,21 +15,32 @@ internal class EmploymentDataResponseTest { val employmentDataResponse = EmploymentDataResponse.builder() .body( - EmploymentData.UnionMember0.builder() + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder().name("name").build() + EmploymentData.EmploymentDataResponseBody.Department.builder() + .name("name") + .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() - .subtype(EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) + EmploymentData.EmploymentDataResponseBody.Employment.builder() + .subtype( + EmploymentData.EmploymentDataResponseBody.Employment.Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment.Type + .EMPLOYEE + ) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus( + EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE + ) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -46,7 +57,7 @@ internal class EmploymentDataResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -54,7 +65,7 @@ internal class EmploymentDataResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -85,22 +96,33 @@ internal class EmploymentDataResponseTest { assertThat(employmentDataResponse.body()) .isEqualTo( - EmploymentData.ofUnionMember0( - EmploymentData.UnionMember0.builder() + EmploymentData.ofResponseBody( + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder().name("name").build() + EmploymentData.EmploymentDataResponseBody.Department.builder() + .name("name") + .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() - .subtype(EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) + EmploymentData.EmploymentDataResponseBody.Employment.builder() + .subtype( + EmploymentData.EmploymentDataResponseBody.Employment.Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment.Type + .EMPLOYEE + ) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus( + EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE + ) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -117,7 +139,7 @@ internal class EmploymentDataResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -125,7 +147,7 @@ internal class EmploymentDataResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -162,21 +184,32 @@ internal class EmploymentDataResponseTest { val employmentDataResponse = EmploymentDataResponse.builder() .body( - EmploymentData.UnionMember0.builder() + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder().name("name").build() + EmploymentData.EmploymentDataResponseBody.Department.builder() + .name("name") + .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() - .subtype(EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) + EmploymentData.EmploymentDataResponseBody.Employment.builder() + .subtype( + EmploymentData.EmploymentDataResponseBody.Employment.Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment.Type + .EMPLOYEE + ) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus( + EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE + ) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -193,7 +226,7 @@ internal class EmploymentDataResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -201,7 +234,7 @@ internal class EmploymentDataResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataTest.kt index ecb2b6448..3aa44aa62 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentDataTest.kt @@ -16,21 +16,28 @@ import org.junit.jupiter.params.provider.EnumSource internal class EmploymentDataTest { @Test - fun ofUnionMember0() { - val unionMember0 = - EmploymentData.UnionMember0.builder() + fun ofResponseBody() { + val responseBody = + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") - .department(EmploymentData.UnionMember0.Department.builder().name("name").build()) + .department( + EmploymentData.EmploymentDataResponseBody.Department.builder() + .name("name") + .build() + ) .employment( - EmploymentData.UnionMember0.Employment.builder() - .subtype(EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) + EmploymentData.EmploymentDataResponseBody.Employment.builder() + .subtype( + EmploymentData.EmploymentDataResponseBody.Employment.Subtype.FULL_TIME + ) + .type(EmploymentData.EmploymentDataResponseBody.Employment.Type.EMPLOYEE) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus(EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -47,7 +54,7 @@ internal class EmploymentDataTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -55,7 +62,7 @@ internal class EmploymentDataTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -80,32 +87,42 @@ internal class EmploymentDataTest { .workId("work_id") .build() - val employmentData = EmploymentData.ofUnionMember0(unionMember0) + val employmentData = EmploymentData.ofResponseBody(responseBody) - assertThat(employmentData.unionMember0()).isEqualTo(unionMember0) + assertThat(employmentData.responseBody()).isEqualTo(responseBody) assertThat(employmentData.batchError()).isNull() } @Test - fun ofUnionMember0Roundtrip() { + fun ofResponseBodyRoundtrip() { val jsonMapper = jsonMapper() val employmentData = - EmploymentData.ofUnionMember0( - EmploymentData.UnionMember0.builder() + EmploymentData.ofResponseBody( + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder().name("name").build() + EmploymentData.EmploymentDataResponseBody.Department.builder() + .name("name") + .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() - .subtype(EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) + EmploymentData.EmploymentDataResponseBody.Employment.builder() + .subtype( + EmploymentData.EmploymentDataResponseBody.Employment.Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment.Type.EMPLOYEE + ) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus( + EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE + ) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -122,7 +139,7 @@ internal class EmploymentDataTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -130,7 +147,7 @@ internal class EmploymentDataTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -177,7 +194,7 @@ internal class EmploymentDataTest { val employmentData = EmploymentData.ofBatchError(batchError) - assertThat(employmentData.unionMember0()).isNull() + assertThat(employmentData.responseBody()).isNull() assertThat(employmentData.batchError()).isEqualTo(batchError) } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentEventTest.kt index 55475aba3..b7d268d79 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentEventTest.kt @@ -16,6 +16,7 @@ internal class EmploymentEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(EmploymentEvent.Data.builder().individualId("individual_id").build()) .eventType(EmploymentEvent.EventType.EMPLOYMENT_CREATED) .build() @@ -23,6 +24,7 @@ internal class EmploymentEventTest { assertThat(employmentEvent.accountId()).isEqualTo("account_id") assertThat(employmentEvent.companyId()).isEqualTo("company_id") assertThat(employmentEvent.connectionId()).isEqualTo("connection_id") + assertThat(employmentEvent.entityId()).isEqualTo("entity_id") assertThat(employmentEvent.data()) .isEqualTo(EmploymentEvent.Data.builder().individualId("individual_id").build()) assertThat(employmentEvent.eventType()) @@ -37,6 +39,7 @@ internal class EmploymentEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(EmploymentEvent.Data.builder().individualId("individual_id").build()) .eventType(EmploymentEvent.EventType.EMPLOYMENT_CREATED) .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentUpdateResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentUpdateResponseTest.kt index 4b6da7540..443be6f5d 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentUpdateResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/EmploymentUpdateResponseTest.kt @@ -3,7 +3,6 @@ package com.tryfinch.api.models import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.tryfinch.api.core.JsonValue import com.tryfinch.api.core.jsonMapper import java.time.LocalDate import org.assertj.core.api.Assertions.assertThat @@ -20,7 +19,7 @@ internal class EmploymentUpdateResponseTest { .addCustomField( EmploymentUpdateResponse.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department(EmploymentUpdateResponse.Department.builder().name("name").build()) @@ -33,6 +32,7 @@ internal class EmploymentUpdateResponseTest { .employmentStatus(EmploymentUpdateResponse.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentUpdateResponse.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) @@ -79,10 +79,7 @@ internal class EmploymentUpdateResponseTest { assertThat(employmentUpdateResponse.classCode()).isEqualTo("class_code") assertThat(employmentUpdateResponse.customFields()) .containsExactly( - EmploymentUpdateResponse.CustomField.builder() - .name("name") - .value(JsonValue.from(mapOf())) - .build() + EmploymentUpdateResponse.CustomField.builder().name("name").value("string").build() ) assertThat(employmentUpdateResponse.department()) .isEqualTo(EmploymentUpdateResponse.Department.builder().name("name").build()) @@ -97,6 +94,8 @@ internal class EmploymentUpdateResponseTest { .isEqualTo(EmploymentUpdateResponse.EmploymentStatus.ACTIVE) assertThat(employmentUpdateResponse.endDate()).isEqualTo("end_date") assertThat(employmentUpdateResponse.firstName()).isEqualTo("first_name") + assertThat(employmentUpdateResponse.flsaStatus()) + .isEqualTo(EmploymentUpdateResponse.FlsaStatus.EXEMPT) assertThat(employmentUpdateResponse.income()) .isEqualTo( Income.builder() @@ -153,7 +152,7 @@ internal class EmploymentUpdateResponseTest { .addCustomField( EmploymentUpdateResponse.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department(EmploymentUpdateResponse.Department.builder().name("name").build()) @@ -166,6 +165,7 @@ internal class EmploymentUpdateResponseTest { .employmentStatus(EmploymentUpdateResponse.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentUpdateResponse.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponseTest.kt index 837c94a48..ca53f56f7 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisEmploymentRetrieveManyPageResponseTest.kt @@ -17,27 +17,37 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .addResponse( EmploymentDataResponse.builder() .body( - EmploymentData.UnionMember0.builder() + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder() + EmploymentData.EmploymentDataResponseBody.Department.builder() .name("name") .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() + EmploymentData.EmploymentDataResponseBody.Employment.builder() .subtype( - EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME + EmploymentData.EmploymentDataResponseBody.Employment + .Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment + .Type + .EMPLOYEE ) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) .build() ) .employmentStatus( - EmploymentData.UnionMember0.EmploymentStatus.ACTIVE + EmploymentData.EmploymentDataResponseBody.EmploymentStatus + .ACTIVE ) .endDate("end_date") .firstName("first_name") + .flsaStatus( + EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT + ) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -54,7 +64,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -62,7 +72,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -97,25 +107,32 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .containsExactly( EmploymentDataResponse.builder() .body( - EmploymentData.UnionMember0.builder() + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder() + EmploymentData.EmploymentDataResponseBody.Department.builder() .name("name") .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() + EmploymentData.EmploymentDataResponseBody.Employment.builder() .subtype( - EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME + EmploymentData.EmploymentDataResponseBody.Employment.Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment.Type + .EMPLOYEE ) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) .build() ) - .employmentStatus(EmploymentData.UnionMember0.EmploymentStatus.ACTIVE) + .employmentStatus( + EmploymentData.EmploymentDataResponseBody.EmploymentStatus.ACTIVE + ) .endDate("end_date") .firstName("first_name") + .flsaStatus(EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -132,7 +149,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -140,7 +157,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() @@ -179,27 +196,37 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .addResponse( EmploymentDataResponse.builder() .body( - EmploymentData.UnionMember0.builder() + EmploymentData.EmploymentDataResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .classCode("class_code") .department( - EmploymentData.UnionMember0.Department.builder() + EmploymentData.EmploymentDataResponseBody.Department.builder() .name("name") .build() ) .employment( - EmploymentData.UnionMember0.Employment.builder() + EmploymentData.EmploymentDataResponseBody.Employment.builder() .subtype( - EmploymentData.UnionMember0.Employment.Subtype.FULL_TIME + EmploymentData.EmploymentDataResponseBody.Employment + .Subtype + .FULL_TIME + ) + .type( + EmploymentData.EmploymentDataResponseBody.Employment + .Type + .EMPLOYEE ) - .type(EmploymentData.UnionMember0.Employment.Type.EMPLOYEE) .build() ) .employmentStatus( - EmploymentData.UnionMember0.EmploymentStatus.ACTIVE + EmploymentData.EmploymentDataResponseBody.EmploymentStatus + .ACTIVE ) .endDate("end_date") .firstName("first_name") + .flsaStatus( + EmploymentData.EmploymentDataResponseBody.FlsaStatus.EXEMPT + ) .isActive(true) .lastName("last_name") .latestRehireDate("latest_rehire_date") @@ -216,7 +243,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .build() ) .manager( - EmploymentData.UnionMember0.Manager.builder() + EmploymentData.EmploymentDataResponseBody.Manager.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .build() ) @@ -224,7 +251,7 @@ internal class HrisEmploymentRetrieveManyPageResponseTest { .startDate("start_date") .title("title") .addCustomField( - EmploymentData.UnionMember0.CustomField.builder() + EmploymentData.EmploymentDataResponseBody.CustomField.builder() .name("name") .value("string") .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponseTest.kt index 281945102..8127fd804 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/HrisIndividualRetrieveManyPageResponseTest.kt @@ -16,18 +16,20 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .addResponse( IndividualResponse.builder() .body( - Individual.UnionMember0.builder() + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type( + Individual.IndividualResponseBody.PhoneNumber.Type.WORK + ) .build() ) .preferredName("preferred_name") @@ -44,9 +46,9 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") @@ -63,18 +65,18 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .containsExactly( IndividualResponse.builder() .body( - Individual.UnionMember0.builder() + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -91,9 +93,9 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") @@ -114,18 +116,20 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .addResponse( IndividualResponse.builder() .body( - Individual.UnionMember0.builder() + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type( + Individual.IndividualResponseBody.PhoneNumber.Type.WORK + ) .build() ) .preferredName("preferred_name") @@ -142,9 +146,9 @@ internal class HrisIndividualRetrieveManyPageResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualBenefitTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualBenefitTest.kt index d928b3741..0a8bf5376 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualBenefitTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualBenefitTest.kt @@ -14,36 +14,39 @@ internal class IndividualBenefitTest { val individualBenefit = IndividualBenefit.builder() .body( - IndividualBenefit.Body.UnionMember0.builder() + IndividualBenefit.Body.InnerIndividualBenefit.builder() .annualMaximum(0L) .catchUp(true) .companyContribution( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.CompanyContribution + .CompanyContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit + .CompanyContribution + .CompanyContributionFixed .Type .FIXED ) .build() ) .employeeDeduction( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction.InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .Type .FIXED ) .build() ) .hsaContributionLimit( - IndividualBenefit.Body.UnionMember0.HsaContributionLimit.INDIVIDUAL + IndividualBenefit.Body.InnerIndividualBenefit.HsaContributionLimit + .INDIVIDUAL ) .build() ) @@ -53,37 +56,40 @@ internal class IndividualBenefitTest { assertThat(individualBenefit.body()) .isEqualTo( - IndividualBenefit.Body.ofUnionMember0( - IndividualBenefit.Body.UnionMember0.builder() + IndividualBenefit.Body.ofIndividualBenefit( + IndividualBenefit.Body.InnerIndividualBenefit.builder() .annualMaximum(0L) .catchUp(true) .companyContribution( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.CompanyContribution + .CompanyContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit + .CompanyContribution + .CompanyContributionFixed .Type .FIXED ) .build() ) .employeeDeduction( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction.InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .Type .FIXED ) .build() ) .hsaContributionLimit( - IndividualBenefit.Body.UnionMember0.HsaContributionLimit.INDIVIDUAL + IndividualBenefit.Body.InnerIndividualBenefit.HsaContributionLimit + .INDIVIDUAL ) .build() ) @@ -98,36 +104,39 @@ internal class IndividualBenefitTest { val individualBenefit = IndividualBenefit.builder() .body( - IndividualBenefit.Body.UnionMember0.builder() + IndividualBenefit.Body.InnerIndividualBenefit.builder() .annualMaximum(0L) .catchUp(true) .companyContribution( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.CompanyContribution + .CompanyContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.CompanyContribution - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit + .CompanyContribution + .CompanyContributionFixed .Type .FIXED ) .build() ) .employeeDeduction( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction.InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .builder() .amount(0L) .type( - IndividualBenefit.Body.UnionMember0.EmployeeDeduction - .InnerUnionMember0 + IndividualBenefit.Body.InnerIndividualBenefit.EmployeeDeduction + .EmployeeDeductionContributionFixed .Type .FIXED ) .build() ) .hsaContributionLimit( - IndividualBenefit.Body.UnionMember0.HsaContributionLimit.INDIVIDUAL + IndividualBenefit.Body.InnerIndividualBenefit.HsaContributionLimit + .INDIVIDUAL ) .build() ) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualEventTest.kt index 9f7a00384..d90d241ec 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualEventTest.kt @@ -16,6 +16,7 @@ internal class IndividualEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(IndividualEvent.Data.builder().individualId("individual_id").build()) .eventType(IndividualEvent.EventType.INDIVIDUAL_CREATED) .build() @@ -23,6 +24,7 @@ internal class IndividualEventTest { assertThat(individualEvent.accountId()).isEqualTo("account_id") assertThat(individualEvent.companyId()).isEqualTo("company_id") assertThat(individualEvent.connectionId()).isEqualTo("connection_id") + assertThat(individualEvent.entityId()).isEqualTo("entity_id") assertThat(individualEvent.data()) .isEqualTo(IndividualEvent.Data.builder().individualId("individual_id").build()) assertThat(individualEvent.eventType()) @@ -37,6 +39,7 @@ internal class IndividualEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(IndividualEvent.Data.builder().individualId("individual_id").build()) .eventType(IndividualEvent.EventType.INDIVIDUAL_CREATED) .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualResponseTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualResponseTest.kt index 3f480bc39..bc0698bd2 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualResponseTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualResponseTest.kt @@ -14,18 +14,18 @@ internal class IndividualResponseTest { val individualResponse = IndividualResponse.builder() .body( - Individual.UnionMember0.builder() + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -42,9 +42,9 @@ internal class IndividualResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") @@ -57,19 +57,19 @@ internal class IndividualResponseTest { assertThat(individualResponse.body()) .isEqualTo( - Individual.ofUnionMember0( - Individual.UnionMember0.builder() + Individual.ofResponseBody( + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -86,9 +86,9 @@ internal class IndividualResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") @@ -106,18 +106,18 @@ internal class IndividualResponseTest { val individualResponse = IndividualResponse.builder() .body( - Individual.UnionMember0.builder() + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -134,9 +134,9 @@ internal class IndividualResponseTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualTest.kt index 431910999..62d1c5d06 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IndividualTest.kt @@ -15,20 +15,20 @@ import org.junit.jupiter.params.provider.EnumSource internal class IndividualTest { @Test - fun ofUnionMember0() { - val unionMember0 = - Individual.UnionMember0.builder() + fun ofResponseBody() { + val responseBody = + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -45,38 +45,38 @@ internal class IndividualTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") .ssn("ssn") .build() - val individual = Individual.ofUnionMember0(unionMember0) + val individual = Individual.ofResponseBody(responseBody) - assertThat(individual.unionMember0()).isEqualTo(unionMember0) + assertThat(individual.responseBody()).isEqualTo(responseBody) assertThat(individual.batchError()).isNull() } @Test - fun ofUnionMember0Roundtrip() { + fun ofResponseBodyRoundtrip() { val jsonMapper = jsonMapper() val individual = - Individual.ofUnionMember0( - Individual.UnionMember0.builder() + Individual.ofResponseBody( + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -93,9 +93,9 @@ internal class IndividualTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") @@ -124,7 +124,7 @@ internal class IndividualTest { val individual = Individual.ofBatchError(batchError) - assertThat(individual.unionMember0()).isNull() + assertThat(individual.responseBody()).isNull() assertThat(individual.batchError()).isEqualTo(batchError) } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IntrospectionTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IntrospectionTest.kt index 5497e8098..bcc9e62ef 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IntrospectionTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/IntrospectionTest.kt @@ -54,6 +54,7 @@ internal class IntrospectionTest { .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .name("name") .sourceId("source_id") + .status(Introspection.MultiAccountEntity.EntityConnectionStatus.PENDING) .build() ) .manual(true) @@ -101,6 +102,7 @@ internal class IntrospectionTest { .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .name("name") .sourceId("source_id") + .status(Introspection.MultiAccountEntity.EntityConnectionStatus.PENDING) .build() ) assertThat(introspection.manual()).isEqualTo(true) @@ -153,6 +155,7 @@ internal class IntrospectionTest { .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .name("name") .sourceId("source_id") + .status(Introspection.MultiAccountEntity.EntityConnectionStatus.PENDING) .build() ) .manual(true) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobAutomatedCreateParamsTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobAutomatedCreateParamsTest.kt index 4372764c2..c8f444655 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobAutomatedCreateParamsTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobAutomatedCreateParamsTest.kt @@ -20,11 +20,4 @@ internal class JobAutomatedCreateParamsTest { assertThat(body).isEqualTo(JobAutomatedCreateParams.Body.ofDataSyncAll()) } - - @Test - fun bodyWithoutOptionalFields() { - val params = JobAutomatedCreateParams.builder().build() - - val body = params._body() - } } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobCompletionEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobCompletionEventTest.kt index 5fefea4a1..d15ae6348 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobCompletionEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/JobCompletionEventTest.kt @@ -16,6 +16,7 @@ internal class JobCompletionEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(JobCompletionEvent.Data.builder().jobId("job_id").jobUrl("job_url").build()) .eventType(JobCompletionEvent.EventType.JOB_BENEFIT_CREATE_COMPLETED) .build() @@ -23,6 +24,7 @@ internal class JobCompletionEventTest { assertThat(jobCompletionEvent.accountId()).isEqualTo("account_id") assertThat(jobCompletionEvent.companyId()).isEqualTo("company_id") assertThat(jobCompletionEvent.connectionId()).isEqualTo("connection_id") + assertThat(jobCompletionEvent.entityId()).isEqualTo("entity_id") assertThat(jobCompletionEvent.data()) .isEqualTo(JobCompletionEvent.Data.builder().jobId("job_id").jobUrl("job_url").build()) assertThat(jobCompletionEvent.eventType()) @@ -37,6 +39,7 @@ internal class JobCompletionEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(JobCompletionEvent.Data.builder().jobId("job_id").jobUrl("job_url").build()) .eventType(JobCompletionEvent.EventType.JOB_BENEFIT_CREATE_COMPLETED) .build() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PayStatementEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PayStatementEventTest.kt index d8b4fa254..9dfa7c292 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PayStatementEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PayStatementEventTest.kt @@ -16,6 +16,7 @@ internal class PayStatementEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PayStatementEvent.Data.builder() .individualId("individual_id") @@ -28,6 +29,7 @@ internal class PayStatementEventTest { assertThat(payStatementEvent.accountId()).isEqualTo("account_id") assertThat(payStatementEvent.companyId()).isEqualTo("company_id") assertThat(payStatementEvent.connectionId()).isEqualTo("connection_id") + assertThat(payStatementEvent.entityId()).isEqualTo("entity_id") assertThat(payStatementEvent.data()) .isEqualTo( PayStatementEvent.Data.builder() @@ -47,6 +49,7 @@ internal class PayStatementEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PayStatementEvent.Data.builder() .individualId("individual_id") diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PaymentEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PaymentEventTest.kt index 2d0033bcc..0ead5dc26 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PaymentEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/PaymentEventTest.kt @@ -16,6 +16,7 @@ internal class PaymentEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PaymentEvent.PaymentIdentifiers.builder() .payDate("pay_date") @@ -28,6 +29,7 @@ internal class PaymentEventTest { assertThat(paymentEvent.accountId()).isEqualTo("account_id") assertThat(paymentEvent.companyId()).isEqualTo("company_id") assertThat(paymentEvent.connectionId()).isEqualTo("connection_id") + assertThat(paymentEvent.entityId()).isEqualTo("entity_id") assertThat(paymentEvent.data()) .isEqualTo( PaymentEvent.PaymentIdentifiers.builder() @@ -46,6 +48,7 @@ internal class PaymentEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PaymentEvent.PaymentIdentifiers.builder() .payDate("pay_date") diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParamsTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParamsTest.kt index 89697892e..c92e28c3d 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParamsTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxDirectoryCreateParamsTest.kt @@ -2,7 +2,6 @@ package com.tryfinch.api.models -import com.tryfinch.api.core.JsonValue import java.time.LocalDate import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -18,7 +17,7 @@ internal class SandboxDirectoryCreateParamsTest { .addCustomField( SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department( @@ -55,6 +54,9 @@ internal class SandboxDirectoryCreateParamsTest { .endDate("end_date") .ethnicity(SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN) .firstName("first_name") + .flsaStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.FlsaStatus.EXEMPT + ) .gender(SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE) .income( Income.builder() @@ -135,7 +137,7 @@ internal class SandboxDirectoryCreateParamsTest { SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField .builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department( @@ -177,6 +179,9 @@ internal class SandboxDirectoryCreateParamsTest { SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN ) .firstName("first_name") + .flsaStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.FlsaStatus.EXEMPT + ) .gender(SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE) .income( Income.builder() @@ -256,7 +261,7 @@ internal class SandboxDirectoryCreateParamsTest { .addCustomField( SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department( @@ -293,6 +298,9 @@ internal class SandboxDirectoryCreateParamsTest { .endDate("end_date") .ethnicity(SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN) .firstName("first_name") + .flsaStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.FlsaStatus.EXEMPT + ) .gender(SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE) .income( Income.builder() diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParamsTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParamsTest.kt index d1602a844..6bf03c354 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParamsTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/SandboxEmploymentUpdateParamsTest.kt @@ -2,7 +2,6 @@ package com.tryfinch.api.models -import com.tryfinch.api.core.JsonValue import java.time.LocalDate import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -17,7 +16,7 @@ internal class SandboxEmploymentUpdateParamsTest { .addCustomField( SandboxEmploymentUpdateParams.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department(SandboxEmploymentUpdateParams.Department.builder().name("name").build()) @@ -30,6 +29,7 @@ internal class SandboxEmploymentUpdateParamsTest { .employmentStatus(SandboxEmploymentUpdateParams.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(SandboxEmploymentUpdateParams.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) @@ -68,7 +68,7 @@ internal class SandboxEmploymentUpdateParamsTest { ) .middleName("middle_name") .sourceId("source_id") - .startDate("start_date") + .startDate("3/4/2020") .title("title") .build() } @@ -91,7 +91,7 @@ internal class SandboxEmploymentUpdateParamsTest { .addCustomField( SandboxEmploymentUpdateParams.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department(SandboxEmploymentUpdateParams.Department.builder().name("name").build()) @@ -104,6 +104,7 @@ internal class SandboxEmploymentUpdateParamsTest { .employmentStatus(SandboxEmploymentUpdateParams.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(SandboxEmploymentUpdateParams.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) @@ -142,7 +143,7 @@ internal class SandboxEmploymentUpdateParamsTest { ) .middleName("middle_name") .sourceId("source_id") - .startDate("start_date") + .startDate("3/4/2020") .title("title") .build() @@ -153,7 +154,7 @@ internal class SandboxEmploymentUpdateParamsTest { .containsExactly( SandboxEmploymentUpdateParams.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) assertThat(body.department()) @@ -169,6 +170,7 @@ internal class SandboxEmploymentUpdateParamsTest { .isEqualTo(SandboxEmploymentUpdateParams.EmploymentStatus.ACTIVE) assertThat(body.endDate()).isEqualTo("end_date") assertThat(body.firstName()).isEqualTo("first_name") + assertThat(body.flsaStatus()).isEqualTo(SandboxEmploymentUpdateParams.FlsaStatus.EXEMPT) assertThat(body.income()) .isEqualTo( Income.builder() @@ -211,7 +213,7 @@ internal class SandboxEmploymentUpdateParamsTest { ) assertThat(body.middleName()).isEqualTo("middle_name") assertThat(body.sourceId()).isEqualTo("source_id") - assertThat(body.startDate()).isEqualTo("start_date") + assertThat(body.startDate()).isEqualTo("3/4/2020") assertThat(body.title()).isEqualTo("title") } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/WebhookEventTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/WebhookEventTest.kt index 7ced3d22d..2ed71bdd3 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/WebhookEventTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/models/WebhookEventTest.kt @@ -21,6 +21,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( AccountUpdateEvent.Data.builder() .authenticationMethod( @@ -965,6 +966,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( AccountUpdateEvent.Data.builder() .authenticationMethod( @@ -2123,6 +2125,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(JobCompletionEvent.Data.builder().jobId("job_id").jobUrl("job_url").build()) .eventType(JobCompletionEvent.EventType.JOB_BENEFIT_CREATE_COMPLETED) .build() @@ -2148,6 +2151,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( JobCompletionEvent.Data.builder().jobId("job_id").jobUrl("job_url").build() ) @@ -2171,6 +2175,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( CompanyEvent.Data.builder() .putAdditionalProperty("foo", JsonValue.from("bar")) @@ -2200,6 +2205,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( CompanyEvent.Data.builder() .putAdditionalProperty("foo", JsonValue.from("bar")) @@ -2225,6 +2231,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(DirectoryEvent.Data.builder().individualId("individual_id").build()) .eventType(DirectoryEvent.EventType.DIRECTORY_CREATED) .build() @@ -2250,6 +2257,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(DirectoryEvent.Data.builder().individualId("individual_id").build()) .eventType(DirectoryEvent.EventType.DIRECTORY_CREATED) .build() @@ -2271,6 +2279,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(EmploymentEvent.Data.builder().individualId("individual_id").build()) .eventType(EmploymentEvent.EventType.EMPLOYMENT_CREATED) .build() @@ -2296,6 +2305,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(EmploymentEvent.Data.builder().individualId("individual_id").build()) .eventType(EmploymentEvent.EventType.EMPLOYMENT_CREATED) .build() @@ -2317,6 +2327,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(IndividualEvent.Data.builder().individualId("individual_id").build()) .eventType(IndividualEvent.EventType.INDIVIDUAL_CREATED) .build() @@ -2342,6 +2353,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data(IndividualEvent.Data.builder().individualId("individual_id").build()) .eventType(IndividualEvent.EventType.INDIVIDUAL_CREATED) .build() @@ -2363,6 +2375,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PaymentEvent.PaymentIdentifiers.builder() .payDate("pay_date") @@ -2393,6 +2406,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PaymentEvent.PaymentIdentifiers.builder() .payDate("pay_date") @@ -2419,6 +2433,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PayStatementEvent.Data.builder() .individualId("individual_id") @@ -2449,6 +2464,7 @@ internal class WebhookEventTest { .accountId("account_id") .companyId("company_id") .connectionId("connection_id") + .entityId("entity_id") .data( PayStatementEvent.Data.builder() .individualId("individual_id") diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncTest.kt index 57e258190..081186c87 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/jobs/AutomatedServiceAsyncTest.kt @@ -21,10 +21,7 @@ internal class AutomatedServiceAsyncTest { .build() val automatedServiceAsync = client.jobs().automated() - val automated = - automatedServiceAsync.create( - JobAutomatedCreateParams.builder().bodyDataSyncAll().build() - ) + val automated = automatedServiceAsync.create(JobAutomatedCreateParams.Body.ofDataSyncAll()) automated.validate() } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsyncTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsyncTest.kt index c6c90ec71..4811e8905 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsyncTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/DirectoryServiceAsyncTest.kt @@ -4,7 +4,6 @@ package com.tryfinch.api.services.async.sandbox import com.tryfinch.api.TestServerExtension import com.tryfinch.api.client.okhttp.FinchOkHttpClientAsync -import com.tryfinch.api.core.JsonValue import com.tryfinch.api.models.Income import com.tryfinch.api.models.Location import com.tryfinch.api.models.SandboxDirectoryCreateParams @@ -26,134 +25,126 @@ internal class DirectoryServiceAsyncTest { val directories = directoryServiceAsync.create( - SandboxDirectoryCreateParams.builder() - .addBody( - SandboxDirectoryCreateParams.IndividualOrEmployment.builder() - .classCode("class_code") - .addCustomField( - SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField - .builder() - .name("name") - .value(JsonValue.from(mapOf())) - .build() - ) - .department( - SandboxDirectoryCreateParams.IndividualOrEmployment.Department - .builder() - .name("name") - .build() - ) - .dob("dob") - .addEmail( - SandboxDirectoryCreateParams.IndividualOrEmployment.Email.builder() - .data("data") - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment.Email - .Type - .WORK - ) - .build() - ) - .employment( - SandboxDirectoryCreateParams.IndividualOrEmployment.Employment - .builder() - .subtype( - SandboxDirectoryCreateParams.IndividualOrEmployment - .Employment - .Subtype - .FULL_TIME - ) - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment - .Employment - .Type - .EMPLOYEE - ) - .build() - ) - .employmentStatus( - SandboxDirectoryCreateParams.IndividualOrEmployment.EmploymentStatus - .ACTIVE - ) - .encryptedSsn("encrypted_ssn") - .endDate("end_date") - .ethnicity( - SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN - ) - .firstName("first_name") - .gender( - SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE - ) - .income( - Income.builder() - .amount(0L) - .currency("currency") - .effectiveDate(LocalDate.parse("2019-12-27")) - .unit(Income.Unit.YEARLY) - .build() - ) - .addIncomeHistory( - Income.builder() - .amount(0L) - .currency("currency") - .effectiveDate(LocalDate.parse("2019-12-27")) - .unit(Income.Unit.YEARLY) - .build() - ) - .isActive(true) - .lastName("last_name") - .latestRehireDate("latest_rehire_date") - .location( - Location.builder() - .city("city") - .country("country") - .line1("line1") - .line2("line2") - .postalCode("postal_code") - .state("state") - .name("name") - .sourceId("source_id") - .build() - ) - .manager( - SandboxDirectoryCreateParams.IndividualOrEmployment.Manager - .builder() - .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") - .build() - ) - .middleName("middle_name") - .addPhoneNumber( - SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber - .builder() - .data("data") - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment - .PhoneNumber - .Type - .WORK - ) - .build() - ) - .preferredName("preferred_name") - .residence( - Location.builder() - .city("city") - .country("country") - .line1("line1") - .line2("line2") - .postalCode("postal_code") - .state("state") - .name("name") - .sourceId("source_id") - .build() - ) - .sourceId("source_id") - .ssn("ssn") - .startDate("start_date") - .title("title") - .build() - ) - .build() + listOf( + SandboxDirectoryCreateParams.IndividualOrEmployment.builder() + .classCode("class_code") + .addCustomField( + SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField + .builder() + .name("name") + .value("string") + .build() + ) + .department( + SandboxDirectoryCreateParams.IndividualOrEmployment.Department.builder() + .name("name") + .build() + ) + .dob("dob") + .addEmail( + SandboxDirectoryCreateParams.IndividualOrEmployment.Email.builder() + .data("data") + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.Email.Type + .WORK + ) + .build() + ) + .employment( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment.builder() + .subtype( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment + .Subtype + .FULL_TIME + ) + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment + .Type + .EMPLOYEE + ) + .build() + ) + .employmentStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.EmploymentStatus + .ACTIVE + ) + .encryptedSsn("encrypted_ssn") + .endDate("end_date") + .ethnicity( + SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN + ) + .firstName("first_name") + .flsaStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.FlsaStatus.EXEMPT + ) + .gender(SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE) + .income( + Income.builder() + .amount(0L) + .currency("currency") + .effectiveDate(LocalDate.parse("2019-12-27")) + .unit(Income.Unit.YEARLY) + .build() + ) + .addIncomeHistory( + Income.builder() + .amount(0L) + .currency("currency") + .effectiveDate(LocalDate.parse("2019-12-27")) + .unit(Income.Unit.YEARLY) + .build() + ) + .isActive(true) + .lastName("last_name") + .latestRehireDate("latest_rehire_date") + .location( + Location.builder() + .city("city") + .country("country") + .line1("line1") + .line2("line2") + .postalCode("postal_code") + .state("state") + .name("name") + .sourceId("source_id") + .build() + ) + .manager( + SandboxDirectoryCreateParams.IndividualOrEmployment.Manager.builder() + .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + ) + .middleName("middle_name") + .addPhoneNumber( + SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber + .builder() + .data("data") + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber + .Type + .WORK + ) + .build() + ) + .preferredName("preferred_name") + .residence( + Location.builder() + .city("city") + .country("country") + .line1("line1") + .line2("line2") + .postalCode("postal_code") + .state("state") + .name("name") + .sourceId("source_id") + .build() + ) + .sourceId("source_id") + .ssn("ssn") + .startDate("start_date") + .title("title") + .build() + ) ) directories.forEach { it.validate() } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/EmploymentServiceAsyncTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/EmploymentServiceAsyncTest.kt index d3e924ef8..62a93160f 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/EmploymentServiceAsyncTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/async/sandbox/EmploymentServiceAsyncTest.kt @@ -4,7 +4,6 @@ package com.tryfinch.api.services.async.sandbox import com.tryfinch.api.TestServerExtension import com.tryfinch.api.client.okhttp.FinchOkHttpClientAsync -import com.tryfinch.api.core.JsonValue import com.tryfinch.api.models.Income import com.tryfinch.api.models.Location import com.tryfinch.api.models.SandboxEmploymentUpdateParams @@ -32,7 +31,7 @@ internal class EmploymentServiceAsyncTest { .addCustomField( SandboxEmploymentUpdateParams.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department( @@ -47,6 +46,7 @@ internal class EmploymentServiceAsyncTest { .employmentStatus(SandboxEmploymentUpdateParams.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(SandboxEmploymentUpdateParams.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) @@ -85,7 +85,7 @@ internal class EmploymentServiceAsyncTest { ) .middleName("middle_name") .sourceId("source_id") - .startDate("start_date") + .startDate("3/4/2020") .title("title") .build() ) diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceTest.kt index e4121bc5b..e4798add5 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/jobs/AutomatedServiceTest.kt @@ -21,8 +21,7 @@ internal class AutomatedServiceTest { .build() val automatedService = client.jobs().automated() - val automated = - automatedService.create(JobAutomatedCreateParams.builder().bodyDataSyncAll().build()) + val automated = automatedService.create(JobAutomatedCreateParams.Body.ofDataSyncAll()) automated.validate() } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryServiceTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryServiceTest.kt index b14c55a92..43c9a960f 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryServiceTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/DirectoryServiceTest.kt @@ -4,7 +4,6 @@ package com.tryfinch.api.services.blocking.sandbox import com.tryfinch.api.TestServerExtension import com.tryfinch.api.client.okhttp.FinchOkHttpClient -import com.tryfinch.api.core.JsonValue import com.tryfinch.api.models.Income import com.tryfinch.api.models.Location import com.tryfinch.api.models.SandboxDirectoryCreateParams @@ -26,134 +25,126 @@ internal class DirectoryServiceTest { val directories = directoryService.create( - SandboxDirectoryCreateParams.builder() - .addBody( - SandboxDirectoryCreateParams.IndividualOrEmployment.builder() - .classCode("class_code") - .addCustomField( - SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField - .builder() - .name("name") - .value(JsonValue.from(mapOf())) - .build() - ) - .department( - SandboxDirectoryCreateParams.IndividualOrEmployment.Department - .builder() - .name("name") - .build() - ) - .dob("dob") - .addEmail( - SandboxDirectoryCreateParams.IndividualOrEmployment.Email.builder() - .data("data") - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment.Email - .Type - .WORK - ) - .build() - ) - .employment( - SandboxDirectoryCreateParams.IndividualOrEmployment.Employment - .builder() - .subtype( - SandboxDirectoryCreateParams.IndividualOrEmployment - .Employment - .Subtype - .FULL_TIME - ) - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment - .Employment - .Type - .EMPLOYEE - ) - .build() - ) - .employmentStatus( - SandboxDirectoryCreateParams.IndividualOrEmployment.EmploymentStatus - .ACTIVE - ) - .encryptedSsn("encrypted_ssn") - .endDate("end_date") - .ethnicity( - SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN - ) - .firstName("first_name") - .gender( - SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE - ) - .income( - Income.builder() - .amount(0L) - .currency("currency") - .effectiveDate(LocalDate.parse("2019-12-27")) - .unit(Income.Unit.YEARLY) - .build() - ) - .addIncomeHistory( - Income.builder() - .amount(0L) - .currency("currency") - .effectiveDate(LocalDate.parse("2019-12-27")) - .unit(Income.Unit.YEARLY) - .build() - ) - .isActive(true) - .lastName("last_name") - .latestRehireDate("latest_rehire_date") - .location( - Location.builder() - .city("city") - .country("country") - .line1("line1") - .line2("line2") - .postalCode("postal_code") - .state("state") - .name("name") - .sourceId("source_id") - .build() - ) - .manager( - SandboxDirectoryCreateParams.IndividualOrEmployment.Manager - .builder() - .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") - .build() - ) - .middleName("middle_name") - .addPhoneNumber( - SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber - .builder() - .data("data") - .type( - SandboxDirectoryCreateParams.IndividualOrEmployment - .PhoneNumber - .Type - .WORK - ) - .build() - ) - .preferredName("preferred_name") - .residence( - Location.builder() - .city("city") - .country("country") - .line1("line1") - .line2("line2") - .postalCode("postal_code") - .state("state") - .name("name") - .sourceId("source_id") - .build() - ) - .sourceId("source_id") - .ssn("ssn") - .startDate("start_date") - .title("title") - .build() - ) - .build() + listOf( + SandboxDirectoryCreateParams.IndividualOrEmployment.builder() + .classCode("class_code") + .addCustomField( + SandboxDirectoryCreateParams.IndividualOrEmployment.CustomField + .builder() + .name("name") + .value("string") + .build() + ) + .department( + SandboxDirectoryCreateParams.IndividualOrEmployment.Department.builder() + .name("name") + .build() + ) + .dob("dob") + .addEmail( + SandboxDirectoryCreateParams.IndividualOrEmployment.Email.builder() + .data("data") + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.Email.Type + .WORK + ) + .build() + ) + .employment( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment.builder() + .subtype( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment + .Subtype + .FULL_TIME + ) + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.Employment + .Type + .EMPLOYEE + ) + .build() + ) + .employmentStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.EmploymentStatus + .ACTIVE + ) + .encryptedSsn("encrypted_ssn") + .endDate("end_date") + .ethnicity( + SandboxDirectoryCreateParams.IndividualOrEmployment.Ethnicity.ASIAN + ) + .firstName("first_name") + .flsaStatus( + SandboxDirectoryCreateParams.IndividualOrEmployment.FlsaStatus.EXEMPT + ) + .gender(SandboxDirectoryCreateParams.IndividualOrEmployment.Gender.FEMALE) + .income( + Income.builder() + .amount(0L) + .currency("currency") + .effectiveDate(LocalDate.parse("2019-12-27")) + .unit(Income.Unit.YEARLY) + .build() + ) + .addIncomeHistory( + Income.builder() + .amount(0L) + .currency("currency") + .effectiveDate(LocalDate.parse("2019-12-27")) + .unit(Income.Unit.YEARLY) + .build() + ) + .isActive(true) + .lastName("last_name") + .latestRehireDate("latest_rehire_date") + .location( + Location.builder() + .city("city") + .country("country") + .line1("line1") + .line2("line2") + .postalCode("postal_code") + .state("state") + .name("name") + .sourceId("source_id") + .build() + ) + .manager( + SandboxDirectoryCreateParams.IndividualOrEmployment.Manager.builder() + .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + ) + .middleName("middle_name") + .addPhoneNumber( + SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber + .builder() + .data("data") + .type( + SandboxDirectoryCreateParams.IndividualOrEmployment.PhoneNumber + .Type + .WORK + ) + .build() + ) + .preferredName("preferred_name") + .residence( + Location.builder() + .city("city") + .country("country") + .line1("line1") + .line2("line2") + .postalCode("postal_code") + .state("state") + .name("name") + .sourceId("source_id") + .build() + ) + .sourceId("source_id") + .ssn("ssn") + .startDate("start_date") + .title("title") + .build() + ) ) directories.forEach { it.validate() } diff --git a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/EmploymentServiceTest.kt b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/EmploymentServiceTest.kt index 235b740cb..9890da4f5 100644 --- a/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/EmploymentServiceTest.kt +++ b/finch-kotlin-core/src/test/kotlin/com/tryfinch/api/services/blocking/sandbox/EmploymentServiceTest.kt @@ -4,7 +4,6 @@ package com.tryfinch.api.services.blocking.sandbox import com.tryfinch.api.TestServerExtension import com.tryfinch.api.client.okhttp.FinchOkHttpClient -import com.tryfinch.api.core.JsonValue import com.tryfinch.api.models.Income import com.tryfinch.api.models.Location import com.tryfinch.api.models.SandboxEmploymentUpdateParams @@ -32,7 +31,7 @@ internal class EmploymentServiceTest { .addCustomField( SandboxEmploymentUpdateParams.CustomField.builder() .name("name") - .value(JsonValue.from(mapOf())) + .value("string") .build() ) .department( @@ -47,6 +46,7 @@ internal class EmploymentServiceTest { .employmentStatus(SandboxEmploymentUpdateParams.EmploymentStatus.ACTIVE) .endDate("end_date") .firstName("first_name") + .flsaStatus(SandboxEmploymentUpdateParams.FlsaStatus.EXEMPT) .income( Income.builder() .amount(0L) @@ -85,7 +85,7 @@ internal class EmploymentServiceTest { ) .middleName("middle_name") .sourceId("source_id") - .startDate("start_date") + .startDate("3/4/2020") .title("title") .build() ) diff --git a/finch-kotlin-proguard-test/build.gradle.kts b/finch-kotlin-proguard-test/build.gradle.kts index 07b89ca9e..84cc96d20 100644 --- a/finch-kotlin-proguard-test/build.gradle.kts +++ b/finch-kotlin-proguard-test/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { testImplementation(project(":finch-kotlin")) testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("org.assertj:assertj-core:3.27.7") testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } diff --git a/finch-kotlin-proguard-test/src/test/kotlin/com/tryfinch/api/proguard/ProGuardCompatibilityTest.kt b/finch-kotlin-proguard-test/src/test/kotlin/com/tryfinch/api/proguard/ProGuardCompatibilityTest.kt index 1bcd4b6a2..ca55ce198 100644 --- a/finch-kotlin-proguard-test/src/test/kotlin/com/tryfinch/api/proguard/ProGuardCompatibilityTest.kt +++ b/finch-kotlin-proguard-test/src/test/kotlin/com/tryfinch/api/proguard/ProGuardCompatibilityTest.kt @@ -93,19 +93,19 @@ internal class ProGuardCompatibilityTest { fun individualRoundtrip() { val jsonMapper = jsonMapper() val individual = - Individual.ofUnionMember0( - Individual.UnionMember0.builder() + Individual.ofResponseBody( + Individual.IndividualResponseBody.builder() .id("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .dob("dob") - .ethnicity(Individual.UnionMember0.Ethnicity.ASIAN) + .ethnicity(Individual.IndividualResponseBody.Ethnicity.ASIAN) .firstName("first_name") - .gender(Individual.UnionMember0.Gender.FEMALE) + .gender(Individual.IndividualResponseBody.Gender.FEMALE) .lastName("last_name") .middleName("middle_name") .addPhoneNumber( - Individual.UnionMember0.PhoneNumber.builder() + Individual.IndividualResponseBody.PhoneNumber.builder() .data("data") - .type(Individual.UnionMember0.PhoneNumber.Type.WORK) + .type(Individual.IndividualResponseBody.PhoneNumber.Type.WORK) .build() ) .preferredName("preferred_name") @@ -122,9 +122,9 @@ internal class ProGuardCompatibilityTest { .build() ) .addEmail( - Individual.UnionMember0.Email.builder() + Individual.IndividualResponseBody.Email.builder() .data("data") - .type(Individual.UnionMember0.Email.Type.WORK) + .type(Individual.IndividualResponseBody.Email.Type.WORK) .build() ) .encryptedSsn("encrypted_ssn") diff --git a/scripts/build b/scripts/build index f40634826..16a2b00db 100755 --- a/scripts/build +++ b/scripts/build @@ -5,4 +5,4 @@ set -e cd "$(dirname "$0")/.." echo "==> Building classes" -./gradlew build testClasses -x test +./gradlew build testClasses "$@" -x test diff --git a/scripts/mock b/scripts/mock index 0b28f6ea2..04d29019f 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.22.1 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 047bc1dbb..e81baf829 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi