diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a7202d..05479a1 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/**' @@ -16,14 +18,14 @@ jobs: lint: timeout-minutes: 15 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/blooio-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + 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@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Java - uses: actions/setup-java@v4 + 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 @@ -40,14 +42,17 @@ jobs: build: timeout-minutes: 15 name: build - runs-on: ${{ github.repository == 'stainless-sdks/blooio-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + permissions: + contents: read + id-token: write + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + 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@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin java-version: | @@ -56,21 +61,40 @@ 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 + - name: Get GitHub OIDC Token + if: |- + github.repository == 'stainless-sdks/blooio-java' && + !startsWith(github.ref, 'refs/heads/stl/') + id: github-oidc + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Build and upload Maven artifacts + if: |- + github.repository == 'stainless-sdks/blooio-java' && + !startsWith(github.ref, 'refs/heads/stl/') + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + PROJECT: blooio-java + run: ./scripts/upload-artifacts test: timeout-minutes: 15 name: test - runs-on: ${{ github.repository == 'stainless-sdks/blooio-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin java-version: | @@ -79,7 +103,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 11f4929..bb99be6 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@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Java - uses: actions/setup-java@v4 + 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 53c616f..06caa1a 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'Blooio/blooio-java-sdk' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check release environment run: | diff --git a/.gitignore b/.gitignore index b1346e6..90b85e9 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 2ed3b71..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.4" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 5906eac..fa485b0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio%2Fblooio-2d1e4d94ef5b48617299b653eb3509f32fe4e9d6315ee9f3bac50b83635d736b.yml -openapi_spec_hash: 27229154f39588fcc348a91e6b293664 -config_hash: 8cae0b4155e1a7413f8d8693d7253e37 +configured_endpoints: 54 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-4cfd8aadba2015ef9746f076452ef853f194ce40602854c29ce76842eff021a9.yml +openapi_spec_hash: 8137fd23f3a30da82f83a8ac74a433f8 +config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/CHANGELOG.md b/CHANGELOG.md index af9651d..0fb25e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,79 @@ # Changelog +## 0.1.0 (2026-07-25) + +Full Changelog: [v0.0.4...v0.1.0](https://github.com/Blooio/blooio-java-sdk/compare/v0.0.4...v0.1.0) + +### Features + +* **api:** api update ([ee72ac8](https://github.com/Blooio/blooio-java-sdk/commit/ee72ac8987ab0bcfd54340dbe0390f6d4f8fed63)) +* **api:** api update ([7f51cf6](https://github.com/Blooio/blooio-java-sdk/commit/7f51cf6db5c2f96e794de5e08e17d120440b42e8)) +* **api:** api update ([932d816](https://github.com/Blooio/blooio-java-sdk/commit/932d8162c2cd61c1dc99420ffc57b7ba3cf3ec2b)) +* **api:** api update ([ddc3458](https://github.com/Blooio/blooio-java-sdk/commit/ddc345895a8f3d40499f587ae3f5f0d34c51d1cb)) +* **api:** api update ([771db4a](https://github.com/Blooio/blooio-java-sdk/commit/771db4a13cd1736ea91b9e61beb8a987628bcacc)) +* **api:** api update ([62bd13a](https://github.com/Blooio/blooio-java-sdk/commit/62bd13a9dcfbdd3df4b764fb1bbe49b4481641ab)) +* **api:** api update ([242656c](https://github.com/Blooio/blooio-java-sdk/commit/242656c18bf53215a8064e1675aa1f8376d9c785)) +* **api:** api update ([b2ef3c6](https://github.com/Blooio/blooio-java-sdk/commit/b2ef3c62456ee3161c147df9d4d2189d61ff51b8)) +* **api:** api update ([e80c968](https://github.com/Blooio/blooio-java-sdk/commit/e80c968881cf120159070e90dfc1b6c3245dcfa9)) +* **api:** api update ([42ee71f](https://github.com/Blooio/blooio-java-sdk/commit/42ee71ffdc0961088155f55f433beceb12021350)) +* **api:** api update ([80f6a3d](https://github.com/Blooio/blooio-java-sdk/commit/80f6a3d0d669e0cfd2d399ba44193eb0e78e84f5)) +* **api:** api update ([1c697d5](https://github.com/Blooio/blooio-java-sdk/commit/1c697d5bd9f91a4a495d5d71667de5271e8cac0f)) +* **api:** manual updates ([1ae97f1](https://github.com/Blooio/blooio-java-sdk/commit/1ae97f141b08fc16fba03a7d99d6eace0e512001)) +* **api:** manual updates ([e112bbf](https://github.com/Blooio/blooio-java-sdk/commit/e112bbf06bcdf1bb105d9bf924a29f652ac28eb4)) +* **client:** add `HttpRequest#url()` method ([dd2bca3](https://github.com/Blooio/blooio-java-sdk/commit/dd2bca38596e4ef7626ef23d1a73ce7678a4375e)) +* **client:** allow configuring dispatcher executor service ([4f0db30](https://github.com/Blooio/blooio-java-sdk/commit/4f0db3097b9b0c1876887115146216c0b3a50c30)) +* **client:** improve logging ([b1579ce](https://github.com/Blooio/blooio-java-sdk/commit/b1579ce887a34b5a401642abe28d25d16d1c9194)) +* **client:** more robust error parsing ([8d94c9e](https://github.com/Blooio/blooio-java-sdk/commit/8d94c9ea92fb107cb3bbf301db7132cf0e70feb5)) +* **client:** send `X-Stainless-Kotlin-Version` header ([f6d45cd](https://github.com/Blooio/blooio-java-sdk/commit/f6d45cddc950b7443fb661d4804c4986ef89c187)) +* **client:** support proxy authentication ([8e5c00e](https://github.com/Blooio/blooio-java-sdk/commit/8e5c00ecbd7d4ba6e9c3f06d0001c2a953ac564e)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([17dc98d](https://github.com/Blooio/blooio-java-sdk/commit/17dc98df02fbf2f61b95baafaeab473e5d0aa1ba)) + + +### Bug Fixes + +* **client:** disallow coercion from float to int ([3ee28c5](https://github.com/Blooio/blooio-java-sdk/commit/3ee28c5e8bd033c3d96134fb13903941de2e8735)) +* **client:** fully respect max retries ([2ddb812](https://github.com/Blooio/blooio-java-sdk/commit/2ddb8126d441b260c80b9ef3b87a24f9ce20b5be)) +* **client:** preserve time zone in lenient date-time parsing ([573fcaf](https://github.com/Blooio/blooio-java-sdk/commit/573fcaf71930c387314fb18231334b542df8a47f)) +* **client:** send retry count header for max retries 0 ([2ddb812](https://github.com/Blooio/blooio-java-sdk/commit/2ddb8126d441b260c80b9ef3b87a24f9ce20b5be)) +* date time deserialization leniency ([b2ac60a](https://github.com/Blooio/blooio-java-sdk/commit/b2ac60a271022b4f389feee2e73467927b2c7a51)) + + +### Performance Improvements + +* **client:** create one json mapper ([6ef289e](https://github.com/Blooio/blooio-java-sdk/commit/6ef289e91429c1d6e5b7eea1746b0bb9ce24645b)) + + +### Chores + +* **ci:** upgrade `actions/github-script` ([a32a19d](https://github.com/Blooio/blooio-java-sdk/commit/a32a19dcfbeff710c7c6c0b4daaa8ed98107294a)) +* **ci:** upgrade `actions/setup-java` ([67eb04e](https://github.com/Blooio/blooio-java-sdk/commit/67eb04e66d48b47097af4e8d66f093688ff1f8ef)) +* **internal:** allow passing args to `./scripts/test` ([a8da670](https://github.com/Blooio/blooio-java-sdk/commit/a8da67050906f36094fdf4cf4fb278d6abe01f58)) +* **internal:** clean up maven repo artifact script and add html documentation to repo root ([d441da9](https://github.com/Blooio/blooio-java-sdk/commit/d441da9aa8607881a63e2ed14b9037fcccf1600d)) +* **internal:** codegen related update ([ccf0356](https://github.com/Blooio/blooio-java-sdk/commit/ccf03561744755a1bd800449143bc61b847b17ab)) +* **internal:** codegen related update ([58fcb73](https://github.com/Blooio/blooio-java-sdk/commit/58fcb730f98e0c1c1d8006b0aeb96f0aeb678a8b)) +* **internal:** codegen related update ([d939776](https://github.com/Blooio/blooio-java-sdk/commit/d9397764180aeb78502302531fbc19c928fa1ab5)) +* **internal:** codegen related update ([cdac773](https://github.com/Blooio/blooio-java-sdk/commit/cdac773c08fdbc1cfa3ddc812efaa40fb2da4a90)) +* **internal:** codegen related update ([d5926e0](https://github.com/Blooio/blooio-java-sdk/commit/d5926e0c1019ba3d777459623b0b847a286c7b97)) +* **internal:** correct cache invalidation for `SKIP_MOCK_TESTS` ([d3c3b9c](https://github.com/Blooio/blooio-java-sdk/commit/d3c3b9c5e26275edbc5897780f4338d88e65eb7d)) +* **internal:** depend on packages directly in example ([2ddb812](https://github.com/Blooio/blooio-java-sdk/commit/2ddb8126d441b260c80b9ef3b87a24f9ce20b5be)) +* **internal:** improve maven repo docs ([24c2d4b](https://github.com/Blooio/blooio-java-sdk/commit/24c2d4b5b8e3295a5218d2f999fd796fef269b88)) +* **internal:** remove mock server code ([2720979](https://github.com/Blooio/blooio-java-sdk/commit/2720979743e942537ebb7314e83b8f64bc8e3455)) +* **internal:** support uploading Maven repo artifacts to stainless package server ([d790509](https://github.com/Blooio/blooio-java-sdk/commit/d7905093b659e696e05478ee514fbbad370cc9d0)) +* **internal:** update `actions/checkout` version ([8cfab13](https://github.com/Blooio/blooio-java-sdk/commit/8cfab13629687564f829968a161b50843d8ddc73)) +* **internal:** update maven repo doc to include authentication ([0259720](https://github.com/Blooio/blooio-java-sdk/commit/025972007906b16f60bb8dc09ff3433c752f4be0)) +* **internal:** upgrade AssertJ ([0c278a7](https://github.com/Blooio/blooio-java-sdk/commit/0c278a7a62ba05270a09d4ad6d4e0c1eadebd280)) +* redact api-key headers in debug logs ([e7c56e6](https://github.com/Blooio/blooio-java-sdk/commit/e7c56e6299d79215ca5495603e92df1b358af702)) +* remove duplicated dokka setup ([c0c274e](https://github.com/Blooio/blooio-java-sdk/commit/c0c274e30b1bbee4384bd90848bb2ba78be70cae)) +* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/Blooio/blooio-java-sdk/issues/3240) in tests ([b2ac60a](https://github.com/Blooio/blooio-java-sdk/commit/b2ac60a271022b4f389feee2e73467927b2c7a51)) +* update mock server docs ([00e0cf5](https://github.com/Blooio/blooio-java-sdk/commit/00e0cf57e1fb18ddcc685aeea1cce58ac6decf0c)) +* update SDK settings ([c05f9fb](https://github.com/Blooio/blooio-java-sdk/commit/c05f9fb421415ac928f815b14c27c794cf0e771e)) + + +### Documentation + +* add comment for arbitrary value fields ([a636226](https://github.com/Blooio/blooio-java-sdk/commit/a636226690a94e032e7b7d6e8fcf9d2f373bcfc3)) +* clarify forwards compat behavior ([d05e295](https://github.com/Blooio/blooio-java-sdk/commit/d05e295ff73aeb1af17db057f1903375d8719bef)) + ## 0.0.4 (2025-12-03) Full Changelog: [v0.0.3...v0.0.4](https://github.com/Blooio/blooio-java-sdk/compare/v0.0.3...v0.0.4) diff --git a/LICENSE b/LICENSE index 789f1c0..ce4ae4d 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Blooio + Copyright 2026 Blooio Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 0ec93d0..17f6b42 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.blooio.api/blooio-java)](https://central.sonatype.com/artifact/com.blooio.api/blooio-java/0.0.4) -[![javadoc](https://javadoc.io/badge2/com.blooio.api/blooio-java/0.0.4/javadoc.svg)](https://javadoc.io/doc/com.blooio.api/blooio-java/0.0.4) +[![Maven Central](https://img.shields.io/maven-central/v/com.blooio.api/blooio-java)](https://central.sonatype.com/artifact/com.blooio.api/blooio-java/0.1.0) +[![javadoc](https://javadoc.io/badge2/com.blooio.api/blooio-java/0.1.0/javadoc.svg)](https://javadoc.io/doc/com.blooio.api/blooio-java/0.1.0) -The Blooio Java SDK provides convenient access to the Blooio REST API from applications written in Java. +The Blooio Java SDK provides convenient access to the [Blooio REST API](https://blooio.com) from applications written in Java. It is generated with [Stainless](https://www.stainless.com/). -Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.blooio.api/blooio-java/0.0.4). +The REST API documentation can be found on [blooio.com](https://blooio.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.blooio.api/blooio-java/0.1.0). @@ -24,7 +24,7 @@ Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.blooio.api/blo ### Gradle ```kotlin -implementation("com.blooio.api:blooio-java:0.0.4") +implementation("com.blooio.api:blooio-java:0.1.0") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.blooio.api:blooio-java:0.0.4") com.blooio.api blooio-java - 0.0.4 + 0.1.0 ``` @@ -98,10 +98,10 @@ BlooioClient client = BlooioOkHttpClient.builder() See this table for the available options: -| Setter | System property | Environment variable | Required | Default value | -| --------- | ---------------- | -------------------- | -------- | ------------------------------ | -| `apiKey` | `blooio.apiKey` | `BLOOIO_API_KEY` | true | - | -| `baseUrl` | `blooio.baseUrl` | `BLOOIO_BASE_URL` | true | `"https://backend.blooio.com"` | +| Setter | System property | Environment variable | Required | Default value | +| --------- | ---------------- | -------------------- | -------- | ------------------------------------- | +| `apiKey` | `blooio.apiKey` | `BLOOIO_API_KEY` | true | - | +| `baseUrl` | `blooio.baseUrl` | `BLOOIO_BASE_URL` | true | `"https://backend.blooio.com/v2/api"` | System properties take precedence over environment variables. @@ -174,6 +174,70 @@ CompletableFuture me = client.me().retrieve(); The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s. +## File uploads + +The SDK defines methods that accept files. + +To upload a file, pass a [`Path`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html): + +```java +import com.blooio.api.models.groups.icon.GroupIcon; +import com.blooio.api.models.groups.icon.IconSetParams; +import java.nio.file.Paths; + +IconSetParams params = IconSetParams.builder() + .groupId("grp_abc123def456") + .icon(Paths.get("/path/to/file")) + .build(); +GroupIcon groupIcon = client.groups().icon().set(params); +``` + +Or an arbitrary [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html): + +```java +import com.blooio.api.models.groups.icon.GroupIcon; +import com.blooio.api.models.groups.icon.IconSetParams; +import java.net.URL; + +IconSetParams params = IconSetParams.builder() + .groupId("grp_abc123def456") + .icon(new URL("https://example.com//path/to/file").openStream()) + .build(); +GroupIcon groupIcon = client.groups().icon().set(params); +``` + +Or a `byte[]` array: + +```java +import com.blooio.api.models.groups.icon.GroupIcon; +import com.blooio.api.models.groups.icon.IconSetParams; + +IconSetParams params = IconSetParams.builder() + .groupId("grp_abc123def456") + .icon("content".getBytes()) + .build(); +GroupIcon groupIcon = client.groups().icon().set(params); +``` + +Note that when passing a non-`Path` its filename is unknown so it will not be included in the request. To manually set a filename, pass a [`MultipartField`](blooio-java-core/src/main/kotlin/com/blooio/api/core/Values.kt): + +```java +import com.blooio.api.core.MultipartField; +import com.blooio.api.models.groups.icon.GroupIcon; +import com.blooio.api.models.groups.icon.IconSetParams; +import java.io.InputStream; +import java.net.URL; + +IconSetParams params = IconSetParams.builder() + .groupId("grp_abc123def456") + .icon(MultipartField.builder() + .value(new URL("https://example.com//path/to/file").openStream()) + .filename("/path/to/file") + .build()) + .build(); +GroupIcon groupIcon = client.groups().icon().set(params); +``` + ## Raw responses The SDK defines methods that deserialize responses into instances of Java classes. However, these methods don't provide access to the response headers, status code, or the raw response body. @@ -227,8 +291,6 @@ The SDK throws custom unchecked exception types: ## Logging -The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor). - Enable logging by setting the `BLOOIO_LOG` environment variable to `info`: ```sh @@ -241,6 +303,19 @@ Or to `debug` for more verbose logging: export BLOOIO_LOG=debug ``` +Or configure the client manually using the `logLevel` method: + +```java +import com.blooio.api.client.BlooioClient; +import com.blooio.api.client.okhttp.BlooioOkHttpClient; +import com.blooio.api.core.LogLevel; + +BlooioClient client = BlooioOkHttpClient.builder() + .fromEnv() + .logLevel(LogLevel.INFO) + .build(); +``` + ## ProGuard and R8 Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `blooio-java-core` is published with a [configuration file](blooio-java-core/src/main/resources/META-INF/proguard/blooio-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage). @@ -258,6 +333,8 @@ If the SDK threw an exception, but you're _certain_ the version is compatible, t > [!CAUTION] > We make no guarantee that the SDK works correctly when the Jackson version check is disabled. +Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead. + ## Network options ### Retries @@ -331,6 +408,40 @@ BlooioClient client = BlooioOkHttpClient.builder() .build(); ``` +If the proxy responds with `407 Proxy Authentication Required`, supply credentials by also configuring `proxyAuthenticator`: + +```java +import com.blooio.api.client.BlooioClient; +import com.blooio.api.client.okhttp.BlooioOkHttpClient; +import com.blooio.api.core.http.ProxyAuthenticator; + +BlooioClient client = BlooioOkHttpClient.builder() + .fromEnv() + .proxy(...) + // Or a custom implementation of `ProxyAuthenticator`. + .proxyAuthenticator(ProxyAuthenticator.basic("username", "password")) + .build(); +``` + +### Connection pooling + +To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods: + +```java +import com.blooio.api.client.BlooioClient; +import com.blooio.api.client.okhttp.BlooioOkHttpClient; +import java.time.Duration; + +BlooioClient client = BlooioOkHttpClient.builder() + .fromEnv() + // If `maxIdleConnections` is set, then `keepAliveDuration` must be set, and vice versa. + .maxIdleConnections(10) + .keepAliveDuration(Duration.ofMinutes(2)) + .build(); +``` + +If both options are unset, OkHttp's default connection pool settings are used. + ### HTTPS > [!NOTE] @@ -409,6 +520,21 @@ MeRetrieveParams params = MeRetrieveParams.builder() These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods. +To set undocumented parameters on _nested_ headers, query params, or body classes, call the `putAdditionalProperty` method on the nested class: + +```java +import com.blooio.api.core.JsonValue; +import com.blooio.api.models.me.numbers.contactcard.ContactCardUpdateParams; + +ContactCardUpdateParams params = ContactCardUpdateParams.builder() + .sharing(ContactCardUpdateParams.Sharing.builder() + .putAdditionalProperty("secretProperty", JsonValue.from("42")) + .build()) + .build(); +``` + +These properties can be accessed on the nested built object later using the `_additionalProperties()` method. + To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](blooio-java-core/src/main/kotlin/com/blooio/api/core/Values.kt) object to its setter: ```java @@ -462,11 +588,11 @@ To forcibly omit a required parameter or property, pass [`JsonMissing`](blooio-j ```java import com.blooio.api.core.JsonMissing; -import com.blooio.api.models.contacts.ContactCheckCapabilitiesParams; import com.blooio.api.models.me.MeRetrieveParams; +import com.blooio.api.models.me.numbers.contactcard.ContactCardRetrieveParams; -MeRetrieveParams params = ContactCheckCapabilitiesParams.builder() - .contact(JsonMissing.of()) +MeRetrieveParams params = ContactCardRetrieveParams.builder() + .number(JsonMissing.of()) .build(); ``` @@ -530,7 +656,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 [`BlooioInvalidDataException`](blooio-java-core/src/main/kotlin/com/blooio/api/errors/BlooioInvalidDataException.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()`: ```java import com.blooio.api.models.me.MeRetrieveResponse; diff --git a/SECURITY.md b/SECURITY.md index ea78ff0..4262c82 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,6 +18,10 @@ before making any information public. If you encounter security issues that are not directly related to SDKs but pertain to the services or products provided by Blooio, please follow the respective company's security reporting guidelines. +### Blooio Terms and Policies + +Please contact support@blooio.com for any questions or concerns regarding the security of our services. + --- Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/blooio-java-client-okhttp/build.gradle.kts b/blooio-java-client-okhttp/build.gradle.kts index 3449d62..a403de3 100644 --- a/blooio-java-client-okhttp/build.gradle.kts +++ b/blooio-java-client-okhttp/build.gradle.kts @@ -7,9 +7,8 @@ dependencies { api(project(":blooio-java-core")) implementation("com.squareup.okhttp3:okhttp:4.12.0") - implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") testImplementation(kotlin("test")) - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("org.assertj:assertj-core:3.27.7") testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2") } diff --git a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClient.kt b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClient.kt index 1d24917..ec4fcbc 100644 --- a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClient.kt +++ b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClient.kt @@ -5,10 +5,12 @@ package com.blooio.api.client.okhttp import com.blooio.api.client.BlooioClient import com.blooio.api.client.BlooioClientImpl import com.blooio.api.core.ClientOptions +import com.blooio.api.core.LogLevel import com.blooio.api.core.Sleeper import com.blooio.api.core.Timeout import com.blooio.api.core.http.Headers import com.blooio.api.core.http.HttpClient +import com.blooio.api.core.http.ProxyAuthenticator import com.blooio.api.core.http.QueryParams import com.blooio.api.core.jsonMapper import com.fasterxml.jackson.databind.json.JsonMapper @@ -16,6 +18,7 @@ import java.net.Proxy import java.time.Clock import java.time.Duration import java.util.Optional +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -44,16 +47,93 @@ class BlooioOkHttpClient private constructor() { class Builder internal 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 + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication + * Required`. + */ + fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply { + this.proxyAuthenticator = proxyAuthenticator + } + + /** + * Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`. + */ + fun proxyAuthenticator(proxyAuthenticator: Optional) = + proxyAuthenticator(proxyAuthenticator.getOrNull()) + + /** + * 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?) + + /** + * Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`. + */ + fun maxIdleConnections(maxIdleConnections: Optional) = + maxIdleConnections(maxIdleConnections.getOrNull()) + + /** + * 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 + } + + /** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */ + fun keepAliveDuration(keepAliveDuration: Optional) = + keepAliveDuration(keepAliveDuration.getOrNull()) + /** * The socket factory used to secure HTTPS connections. * @@ -144,7 +224,7 @@ class BlooioOkHttpClient private constructor() { /** * The base URL to use for every request. * - * Defaults to the production environment: `https://backend.blooio.com`. + * Defaults to the production environment: `https://backend.blooio.com/v2/api`. */ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) } @@ -154,6 +234,9 @@ class BlooioOkHttpClient 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. */ @@ -195,7 +278,16 @@ class BlooioOkHttpClient private constructor() { */ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } - /** API key must be provided in the Authorization header as `Bearer YOUR_API_KEY`. */ + /** + * 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) } + + /** API key authentication. Use your API key as the bearer token. */ fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } fun headers(headers: Headers) = apply { clientOptions.headers(headers) } @@ -297,6 +389,10 @@ class BlooioOkHttpClient private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .proxyAuthenticator(proxyAuthenticator) + .maxIdleConnections(maxIdleConnections) + .keepAliveDuration(keepAliveDuration) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) diff --git a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClientAsync.kt b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClientAsync.kt index d5e7916..e11a635 100644 --- a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClientAsync.kt +++ b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/BlooioOkHttpClientAsync.kt @@ -5,10 +5,12 @@ package com.blooio.api.client.okhttp import com.blooio.api.client.BlooioClientAsync import com.blooio.api.client.BlooioClientAsyncImpl import com.blooio.api.core.ClientOptions +import com.blooio.api.core.LogLevel import com.blooio.api.core.Sleeper import com.blooio.api.core.Timeout import com.blooio.api.core.http.Headers import com.blooio.api.core.http.HttpClient +import com.blooio.api.core.http.ProxyAuthenticator import com.blooio.api.core.http.QueryParams import com.blooio.api.core.jsonMapper import com.fasterxml.jackson.databind.json.JsonMapper @@ -16,6 +18,7 @@ import java.net.Proxy import java.time.Clock import java.time.Duration import java.util.Optional +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -44,16 +47,93 @@ class BlooioOkHttpClientAsync private constructor() { class Builder internal 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 + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication + * Required`. + */ + fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply { + this.proxyAuthenticator = proxyAuthenticator + } + + /** + * Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`. + */ + fun proxyAuthenticator(proxyAuthenticator: Optional) = + proxyAuthenticator(proxyAuthenticator.getOrNull()) + + /** + * 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?) + + /** + * Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`. + */ + fun maxIdleConnections(maxIdleConnections: Optional) = + maxIdleConnections(maxIdleConnections.getOrNull()) + + /** + * 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 + } + + /** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */ + fun keepAliveDuration(keepAliveDuration: Optional) = + keepAliveDuration(keepAliveDuration.getOrNull()) + /** * The socket factory used to secure HTTPS connections. * @@ -144,7 +224,7 @@ class BlooioOkHttpClientAsync private constructor() { /** * The base URL to use for every request. * - * Defaults to the production environment: `https://backend.blooio.com`. + * Defaults to the production environment: `https://backend.blooio.com/v2/api`. */ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) } @@ -154,6 +234,9 @@ class BlooioOkHttpClientAsync 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. */ @@ -195,7 +278,16 @@ class BlooioOkHttpClientAsync private constructor() { */ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } - /** API key must be provided in the Authorization header as `Bearer YOUR_API_KEY`. */ + /** + * 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) } + + /** API key authentication. Use your API key as the bearer token. */ fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } fun headers(headers: Headers) = apply { clientOptions.headers(headers) } @@ -297,6 +389,10 @@ class BlooioOkHttpClientAsync private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .proxyAuthenticator(proxyAuthenticator) + .maxIdleConnections(maxIdleConnections) + .keepAliveDuration(keepAliveDuration) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) diff --git a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/OkHttpClient.kt b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/OkHttpClient.kt index a5565a5..7403c9b 100644 --- a/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/OkHttpClient.kt +++ b/blooio-java-client-okhttp/src/main/kotlin/com/blooio/api/client/okhttp/OkHttpClient.kt @@ -8,18 +8,26 @@ import com.blooio.api.core.http.HttpMethod import com.blooio.api.core.http.HttpRequest import com.blooio.api.core.http.HttpRequestBody import com.blooio.api.core.http.HttpResponse +import com.blooio.api.core.http.ProxyAuthenticator import com.blooio.api.errors.BlooioIoException import java.io.IOException import java.io.InputStream +import java.io.OutputStream import java.net.Proxy import java.time.Duration import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture +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 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 @@ -27,17 +35,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(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient { +internal constructor(@JvmSynthetic internal 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 BlooioIoException("Request failed", e) } finally { @@ -55,7 +64,7 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien call.enqueue( object : Callback { override fun onResponse(call: Call, response: Response) { - future.complete(response.toResponse()) + future.complete(response.toHttpResponse()) } override fun onFailure(call: Call, e: IOException) { @@ -83,18 +92,6 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call { val clientBuilder = okHttpClient.newBuilder() - val logLevel = - when (System.getenv("BLOOIO_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()) @@ -107,89 +104,6 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien return client.newCall(request.toRequest(client)) } - 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 { @JvmStatic fun builder() = Builder() } @@ -198,6 +112,10 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien 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 private var hostnameVerifier: HostnameVerifier? = null @@ -208,6 +126,36 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien 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 + } + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { this.sslSocketFactory = sslSocketFactory } @@ -223,12 +171,45 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien fun build(): OkHttpClient = OkHttpClient( okhttp3.OkHttpClient.Builder() + // `RetryingHttpClient` handles retries if the user enabled them. + .retryOnConnectionFailure(false) .connectTimeout(timeout.connect()) .readTimeout(timeout.read()) .writeTimeout(timeout.write()) .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) { @@ -250,3 +231,126 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien ) } } + +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/blooio-java-core/build.gradle.kts b/blooio-java-core/build.gradle.kts index 813acf4..18b3914 100644 --- a/blooio-java-core/build.gradle.kts +++ b/blooio-java-core/build.gradle.kts @@ -5,14 +5,16 @@ plugins { configurations.all { resolutionStrategy { - // Compile and test against a lower Jackson version to ensure we're compatible with it. - // We publish with a higher version (see below) to ensure users depend on a secure version by default. - force("com.fasterxml.jackson.core:jackson-core:2.13.4") - force("com.fasterxml.jackson.core:jackson-databind:2.13.4") - force("com.fasterxml.jackson.core:jackson-annotations:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4") - force("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + // Compile and test against a lower Jackson version to ensure we're compatible with it. Note that + // we generally support 2.13.4, but test against 2.14.0 because 2.13.4 has some annoying (but + // niche) bugs (users should upgrade if they encounter them). We publish with a higher version + // (see below) to ensure users depend on a secure version by default. + force("com.fasterxml.jackson.core:jackson-core:2.14.0") + force("com.fasterxml.jackson.core:jackson-databind:2.14.0") + force("com.fasterxml.jackson.core:jackson-annotations:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0") + force("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } } @@ -25,13 +27,11 @@ 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") testImplementation(kotlin("test")) testImplementation(project(":blooio-java-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/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClient.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClient.kt index 77dd88d..97d034c 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClient.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClient.kt @@ -3,11 +3,14 @@ package com.blooio.api.client import com.blooio.api.core.ClientOptions -import com.blooio.api.services.blocking.BatchService -import com.blooio.api.services.blocking.ConfigService +import com.blooio.api.services.blocking.ChatService import com.blooio.api.services.blocking.ContactService +import com.blooio.api.services.blocking.FacetimeService +import com.blooio.api.services.blocking.GroupService +import com.blooio.api.services.blocking.LocationService import com.blooio.api.services.blocking.MeService -import com.blooio.api.services.blocking.MessageService +import com.blooio.api.services.blocking.PhoneNumberService +import com.blooio.api.services.blocking.WebhookService import java.util.function.Consumer /** @@ -46,15 +49,30 @@ interface BlooioClient { */ fun withOptions(modifier: Consumer): BlooioClient + /** Authentication and account information */ fun me(): MeService + /** Manage contacts (phone numbers and emails) */ fun contacts(): ContactService - fun messages(): MessageService + fun location(): LocationService - fun config(): ConfigService + /** Initiate FaceTime calls */ + fun facetime(): FacetimeService - fun batches(): BatchService + /** Manage contact groups */ + fun groups(): GroupService + + /** Manage webhook subscriptions */ + fun webhooks(): WebhookService + + fun chats(): ChatService + + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + fun phoneNumbers(): PhoneNumberService /** * Closes this client, relinquishing any underlying resources. @@ -79,14 +97,29 @@ interface BlooioClient { */ fun withOptions(modifier: Consumer): BlooioClient.WithRawResponse + /** Authentication and account information */ fun me(): MeService.WithRawResponse + /** Manage contacts (phone numbers and emails) */ fun contacts(): ContactService.WithRawResponse - fun messages(): MessageService.WithRawResponse + fun location(): LocationService.WithRawResponse - fun config(): ConfigService.WithRawResponse + /** Initiate FaceTime calls */ + fun facetime(): FacetimeService.WithRawResponse - fun batches(): BatchService.WithRawResponse + /** Manage contact groups */ + fun groups(): GroupService.WithRawResponse + + /** Manage webhook subscriptions */ + fun webhooks(): WebhookService.WithRawResponse + + fun chats(): ChatService.WithRawResponse + + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + fun phoneNumbers(): PhoneNumberService.WithRawResponse } } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsync.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsync.kt index 83256cc..0a3eafc 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsync.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsync.kt @@ -3,11 +3,14 @@ package com.blooio.api.client import com.blooio.api.core.ClientOptions -import com.blooio.api.services.async.BatchServiceAsync -import com.blooio.api.services.async.ConfigServiceAsync +import com.blooio.api.services.async.ChatServiceAsync import com.blooio.api.services.async.ContactServiceAsync +import com.blooio.api.services.async.FacetimeServiceAsync +import com.blooio.api.services.async.GroupServiceAsync +import com.blooio.api.services.async.LocationServiceAsync import com.blooio.api.services.async.MeServiceAsync -import com.blooio.api.services.async.MessageServiceAsync +import com.blooio.api.services.async.PhoneNumberServiceAsync +import com.blooio.api.services.async.WebhookServiceAsync import java.util.function.Consumer /** @@ -46,15 +49,30 @@ interface BlooioClientAsync { */ fun withOptions(modifier: Consumer): BlooioClientAsync + /** Authentication and account information */ fun me(): MeServiceAsync + /** Manage contacts (phone numbers and emails) */ fun contacts(): ContactServiceAsync - fun messages(): MessageServiceAsync + fun location(): LocationServiceAsync - fun config(): ConfigServiceAsync + /** Initiate FaceTime calls */ + fun facetime(): FacetimeServiceAsync - fun batches(): BatchServiceAsync + /** Manage contact groups */ + fun groups(): GroupServiceAsync + + /** Manage webhook subscriptions */ + fun webhooks(): WebhookServiceAsync + + fun chats(): ChatServiceAsync + + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + fun phoneNumbers(): PhoneNumberServiceAsync /** * Closes this client, relinquishing any underlying resources. @@ -81,14 +99,29 @@ interface BlooioClientAsync { modifier: Consumer ): BlooioClientAsync.WithRawResponse + /** Authentication and account information */ fun me(): MeServiceAsync.WithRawResponse + /** Manage contacts (phone numbers and emails) */ fun contacts(): ContactServiceAsync.WithRawResponse - fun messages(): MessageServiceAsync.WithRawResponse + fun location(): LocationServiceAsync.WithRawResponse - fun config(): ConfigServiceAsync.WithRawResponse + /** Initiate FaceTime calls */ + fun facetime(): FacetimeServiceAsync.WithRawResponse - fun batches(): BatchServiceAsync.WithRawResponse + /** Manage contact groups */ + fun groups(): GroupServiceAsync.WithRawResponse + + /** Manage webhook subscriptions */ + fun webhooks(): WebhookServiceAsync.WithRawResponse + + fun chats(): ChatServiceAsync.WithRawResponse + + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + fun phoneNumbers(): PhoneNumberServiceAsync.WithRawResponse } } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsyncImpl.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsyncImpl.kt index 764d2b2..fae4817 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsyncImpl.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientAsyncImpl.kt @@ -4,16 +4,22 @@ package com.blooio.api.client import com.blooio.api.core.ClientOptions import com.blooio.api.core.getPackageVersion -import com.blooio.api.services.async.BatchServiceAsync -import com.blooio.api.services.async.BatchServiceAsyncImpl -import com.blooio.api.services.async.ConfigServiceAsync -import com.blooio.api.services.async.ConfigServiceAsyncImpl +import com.blooio.api.services.async.ChatServiceAsync +import com.blooio.api.services.async.ChatServiceAsyncImpl import com.blooio.api.services.async.ContactServiceAsync import com.blooio.api.services.async.ContactServiceAsyncImpl +import com.blooio.api.services.async.FacetimeServiceAsync +import com.blooio.api.services.async.FacetimeServiceAsyncImpl +import com.blooio.api.services.async.GroupServiceAsync +import com.blooio.api.services.async.GroupServiceAsyncImpl +import com.blooio.api.services.async.LocationServiceAsync +import com.blooio.api.services.async.LocationServiceAsyncImpl import com.blooio.api.services.async.MeServiceAsync import com.blooio.api.services.async.MeServiceAsyncImpl -import com.blooio.api.services.async.MessageServiceAsync -import com.blooio.api.services.async.MessageServiceAsyncImpl +import com.blooio.api.services.async.PhoneNumberServiceAsync +import com.blooio.api.services.async.PhoneNumberServiceAsyncImpl +import com.blooio.api.services.async.WebhookServiceAsync +import com.blooio.api.services.async.WebhookServiceAsyncImpl import java.util.function.Consumer class BlooioClientAsyncImpl(private val clientOptions: ClientOptions) : BlooioClientAsync { @@ -39,16 +45,26 @@ class BlooioClientAsyncImpl(private val clientOptions: ClientOptions) : BlooioCl ContactServiceAsyncImpl(clientOptionsWithUserAgent) } - private val messages: MessageServiceAsync by lazy { - MessageServiceAsyncImpl(clientOptionsWithUserAgent) + private val location: LocationServiceAsync by lazy { + LocationServiceAsyncImpl(clientOptionsWithUserAgent) } - private val config: ConfigServiceAsync by lazy { - ConfigServiceAsyncImpl(clientOptionsWithUserAgent) + private val facetime: FacetimeServiceAsync by lazy { + FacetimeServiceAsyncImpl(clientOptionsWithUserAgent) } - private val batches: BatchServiceAsync by lazy { - BatchServiceAsyncImpl(clientOptionsWithUserAgent) + private val groups: GroupServiceAsync by lazy { + GroupServiceAsyncImpl(clientOptionsWithUserAgent) + } + + private val webhooks: WebhookServiceAsync by lazy { + WebhookServiceAsyncImpl(clientOptionsWithUserAgent) + } + + private val chats: ChatServiceAsync by lazy { ChatServiceAsyncImpl(clientOptionsWithUserAgent) } + + private val phoneNumbers: PhoneNumberServiceAsync by lazy { + PhoneNumberServiceAsyncImpl(clientOptionsWithUserAgent) } override fun sync(): BlooioClient = sync @@ -58,15 +74,30 @@ class BlooioClientAsyncImpl(private val clientOptions: ClientOptions) : BlooioCl override fun withOptions(modifier: Consumer): BlooioClientAsync = BlooioClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** Authentication and account information */ override fun me(): MeServiceAsync = me + /** Manage contacts (phone numbers and emails) */ override fun contacts(): ContactServiceAsync = contacts - override fun messages(): MessageServiceAsync = messages + override fun location(): LocationServiceAsync = location - override fun config(): ConfigServiceAsync = config + /** Initiate FaceTime calls */ + override fun facetime(): FacetimeServiceAsync = facetime - override fun batches(): BatchServiceAsync = batches + /** Manage contact groups */ + override fun groups(): GroupServiceAsync = groups + + /** Manage webhook subscriptions */ + override fun webhooks(): WebhookServiceAsync = webhooks + + override fun chats(): ChatServiceAsync = chats + + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + override fun phoneNumbers(): PhoneNumberServiceAsync = phoneNumbers override fun close() = clientOptions.close() @@ -81,16 +112,28 @@ class BlooioClientAsyncImpl(private val clientOptions: ClientOptions) : BlooioCl ContactServiceAsyncImpl.WithRawResponseImpl(clientOptions) } - private val messages: MessageServiceAsync.WithRawResponse by lazy { - MessageServiceAsyncImpl.WithRawResponseImpl(clientOptions) + private val location: LocationServiceAsync.WithRawResponse by lazy { + LocationServiceAsyncImpl.WithRawResponseImpl(clientOptions) } - private val config: ConfigServiceAsync.WithRawResponse by lazy { - ConfigServiceAsyncImpl.WithRawResponseImpl(clientOptions) + private val facetime: FacetimeServiceAsync.WithRawResponse by lazy { + FacetimeServiceAsyncImpl.WithRawResponseImpl(clientOptions) } - private val batches: BatchServiceAsync.WithRawResponse by lazy { - BatchServiceAsyncImpl.WithRawResponseImpl(clientOptions) + private val groups: GroupServiceAsync.WithRawResponse by lazy { + GroupServiceAsyncImpl.WithRawResponseImpl(clientOptions) + } + + private val webhooks: WebhookServiceAsync.WithRawResponse by lazy { + WebhookServiceAsyncImpl.WithRawResponseImpl(clientOptions) + } + + private val chats: ChatServiceAsync.WithRawResponse by lazy { + ChatServiceAsyncImpl.WithRawResponseImpl(clientOptions) + } + + private val phoneNumbers: PhoneNumberServiceAsync.WithRawResponse by lazy { + PhoneNumberServiceAsyncImpl.WithRawResponseImpl(clientOptions) } override fun withOptions( @@ -100,14 +143,29 @@ class BlooioClientAsyncImpl(private val clientOptions: ClientOptions) : BlooioCl clientOptions.toBuilder().apply(modifier::accept).build() ) + /** Authentication and account information */ override fun me(): MeServiceAsync.WithRawResponse = me + /** Manage contacts (phone numbers and emails) */ override fun contacts(): ContactServiceAsync.WithRawResponse = contacts - override fun messages(): MessageServiceAsync.WithRawResponse = messages + override fun location(): LocationServiceAsync.WithRawResponse = location + + /** Initiate FaceTime calls */ + override fun facetime(): FacetimeServiceAsync.WithRawResponse = facetime + + /** Manage contact groups */ + override fun groups(): GroupServiceAsync.WithRawResponse = groups + + /** Manage webhook subscriptions */ + override fun webhooks(): WebhookServiceAsync.WithRawResponse = webhooks - override fun config(): ConfigServiceAsync.WithRawResponse = config + override fun chats(): ChatServiceAsync.WithRawResponse = chats - override fun batches(): BatchServiceAsync.WithRawResponse = batches + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + override fun phoneNumbers(): PhoneNumberServiceAsync.WithRawResponse = phoneNumbers } } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientImpl.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientImpl.kt index 50431c9..f8e0b0a 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientImpl.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/client/BlooioClientImpl.kt @@ -4,16 +4,22 @@ package com.blooio.api.client import com.blooio.api.core.ClientOptions import com.blooio.api.core.getPackageVersion -import com.blooio.api.services.blocking.BatchService -import com.blooio.api.services.blocking.BatchServiceImpl -import com.blooio.api.services.blocking.ConfigService -import com.blooio.api.services.blocking.ConfigServiceImpl +import com.blooio.api.services.blocking.ChatService +import com.blooio.api.services.blocking.ChatServiceImpl import com.blooio.api.services.blocking.ContactService import com.blooio.api.services.blocking.ContactServiceImpl +import com.blooio.api.services.blocking.FacetimeService +import com.blooio.api.services.blocking.FacetimeServiceImpl +import com.blooio.api.services.blocking.GroupService +import com.blooio.api.services.blocking.GroupServiceImpl +import com.blooio.api.services.blocking.LocationService +import com.blooio.api.services.blocking.LocationServiceImpl import com.blooio.api.services.blocking.MeService import com.blooio.api.services.blocking.MeServiceImpl -import com.blooio.api.services.blocking.MessageService -import com.blooio.api.services.blocking.MessageServiceImpl +import com.blooio.api.services.blocking.PhoneNumberService +import com.blooio.api.services.blocking.PhoneNumberServiceImpl +import com.blooio.api.services.blocking.WebhookService +import com.blooio.api.services.blocking.WebhookServiceImpl import java.util.function.Consumer class BlooioClientImpl(private val clientOptions: ClientOptions) : BlooioClient { @@ -37,11 +43,23 @@ class BlooioClientImpl(private val clientOptions: ClientOptions) : BlooioClient private val contacts: ContactService by lazy { ContactServiceImpl(clientOptionsWithUserAgent) } - private val messages: MessageService by lazy { MessageServiceImpl(clientOptionsWithUserAgent) } + private val location: LocationService by lazy { + LocationServiceImpl(clientOptionsWithUserAgent) + } + + private val facetime: FacetimeService by lazy { + FacetimeServiceImpl(clientOptionsWithUserAgent) + } - private val config: ConfigService by lazy { ConfigServiceImpl(clientOptionsWithUserAgent) } + private val groups: GroupService by lazy { GroupServiceImpl(clientOptionsWithUserAgent) } - private val batches: BatchService by lazy { BatchServiceImpl(clientOptionsWithUserAgent) } + private val webhooks: WebhookService by lazy { WebhookServiceImpl(clientOptionsWithUserAgent) } + + private val chats: ChatService by lazy { ChatServiceImpl(clientOptionsWithUserAgent) } + + private val phoneNumbers: PhoneNumberService by lazy { + PhoneNumberServiceImpl(clientOptionsWithUserAgent) + } override fun async(): BlooioClientAsync = async @@ -50,15 +68,30 @@ class BlooioClientImpl(private val clientOptions: ClientOptions) : BlooioClient override fun withOptions(modifier: Consumer): BlooioClient = BlooioClientImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** Authentication and account information */ override fun me(): MeService = me + /** Manage contacts (phone numbers and emails) */ override fun contacts(): ContactService = contacts - override fun messages(): MessageService = messages + override fun location(): LocationService = location + + /** Initiate FaceTime calls */ + override fun facetime(): FacetimeService = facetime + + /** Manage contact groups */ + override fun groups(): GroupService = groups + + /** Manage webhook subscriptions */ + override fun webhooks(): WebhookService = webhooks - override fun config(): ConfigService = config + override fun chats(): ChatService = chats - override fun batches(): BatchService = batches + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + override fun phoneNumbers(): PhoneNumberService = phoneNumbers override fun close() = clientOptions.close() @@ -73,16 +106,28 @@ class BlooioClientImpl(private val clientOptions: ClientOptions) : BlooioClient ContactServiceImpl.WithRawResponseImpl(clientOptions) } - private val messages: MessageService.WithRawResponse by lazy { - MessageServiceImpl.WithRawResponseImpl(clientOptions) + private val location: LocationService.WithRawResponse by lazy { + LocationServiceImpl.WithRawResponseImpl(clientOptions) } - private val config: ConfigService.WithRawResponse by lazy { - ConfigServiceImpl.WithRawResponseImpl(clientOptions) + private val facetime: FacetimeService.WithRawResponse by lazy { + FacetimeServiceImpl.WithRawResponseImpl(clientOptions) } - private val batches: BatchService.WithRawResponse by lazy { - BatchServiceImpl.WithRawResponseImpl(clientOptions) + private val groups: GroupService.WithRawResponse by lazy { + GroupServiceImpl.WithRawResponseImpl(clientOptions) + } + + private val webhooks: WebhookService.WithRawResponse by lazy { + WebhookServiceImpl.WithRawResponseImpl(clientOptions) + } + + private val chats: ChatService.WithRawResponse by lazy { + ChatServiceImpl.WithRawResponseImpl(clientOptions) + } + + private val phoneNumbers: PhoneNumberService.WithRawResponse by lazy { + PhoneNumberServiceImpl.WithRawResponseImpl(clientOptions) } override fun withOptions( @@ -92,14 +137,29 @@ class BlooioClientImpl(private val clientOptions: ClientOptions) : BlooioClient clientOptions.toBuilder().apply(modifier::accept).build() ) + /** Authentication and account information */ override fun me(): MeService.WithRawResponse = me + /** Manage contacts (phone numbers and emails) */ override fun contacts(): ContactService.WithRawResponse = contacts - override fun messages(): MessageService.WithRawResponse = messages + override fun location(): LocationService.WithRawResponse = location + + /** Initiate FaceTime calls */ + override fun facetime(): FacetimeService.WithRawResponse = facetime + + /** Manage contact groups */ + override fun groups(): GroupService.WithRawResponse = groups + + /** Manage webhook subscriptions */ + override fun webhooks(): WebhookService.WithRawResponse = webhooks - override fun config(): ConfigService.WithRawResponse = config + override fun chats(): ChatService.WithRawResponse = chats - override fun batches(): BatchService.WithRawResponse = batches + /** + * Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan + * (Dedicated Enterprise). + */ + override fun phoneNumbers(): PhoneNumberService.WithRawResponse = phoneNumbers } } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/ClientOptions.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/ClientOptions.kt index 815c3e2..540df33 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/ClientOptions.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/ClientOptions.kt @@ -4,6 +4,7 @@ package com.blooio.api.core import com.blooio.api.core.http.Headers import com.blooio.api.core.http.HttpClient +import com.blooio.api.core.http.LoggingHttpClient import com.blooio.api.core.http.PhantomReachableClosingHttpClient import com.blooio.api.core.http.QueryParams import com.blooio.api.core.http.RetryingHttpClient @@ -66,6 +67,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. */ @@ -93,7 +97,15 @@ private constructor( * Defaults to 2. */ @get:JvmName("maxRetries") val maxRetries: Int, - /** API key must be provided in the Authorization header as `Bearer YOUR_API_KEY`. */ + /** + * 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]. + */ + @get:JvmName("logLevel") val logLevel: LogLevel, + /** API key authentication. Use your API key as the bearer token. */ @get:JvmName("apiKey") val apiKey: String, ) { @@ -106,7 +118,7 @@ private constructor( /** * The base URL to use for every request. * - * Defaults to the production environment: `https://backend.blooio.com`. + * Defaults to the production environment: `https://backend.blooio.com/v2/api`. */ fun baseUrl(): String = baseUrl ?: PRODUCTION_URL @@ -114,7 +126,7 @@ private constructor( companion object { - const val PRODUCTION_URL = "https://backend.blooio.com" + const val PRODUCTION_URL = "https://backend.blooio.com/v2/api" /** * Returns a mutable builder for constructing an instance of [ClientOptions]. @@ -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 apiKey: String? = null @JvmSynthetic @@ -164,6 +177,7 @@ private constructor( responseValidation = clientOptions.responseValidation timeout = clientOptions.timeout maxRetries = clientOptions.maxRetries + logLevel = clientOptions.logLevel apiKey = clientOptions.apiKey } @@ -220,7 +234,7 @@ private constructor( /** * The base URL to use for every request. * - * Defaults to the production environment: `https://backend.blooio.com`. + * Defaults to the production environment: `https://backend.blooio.com/v2/api`. */ fun baseUrl(baseUrl: String?) = apply { this.baseUrl = baseUrl } @@ -230,6 +244,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. */ @@ -271,7 +288,16 @@ private constructor( */ fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries } - /** API key must be provided in the Authorization header as `Bearer YOUR_API_KEY`. */ + /** + * 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 } + + /** API key authentication. Use your API key as the bearer token. */ fun apiKey(apiKey: String) = apply { this.apiKey = apiKey } fun headers(headers: Headers) = apply { @@ -361,20 +387,29 @@ private constructor( * * See this table for the available options: * - * |Setter |System property |Environment variable|Required|Default value | - * |---------|----------------|--------------------|--------|------------------------------| - * |`apiKey` |`blooio.apiKey` |`BLOOIO_API_KEY` |true |- | - * |`baseUrl`|`blooio.baseUrl`|`BLOOIO_BASE_URL` |true |`"https://backend.blooio.com"`| + * |Setter |System property |Environment variable|Required|Default value | + * |---------|----------------|--------------------|--------|-------------------------------------| + * |`apiKey` |`blooio.apiKey` |`BLOOIO_API_KEY` |true |- | + * |`baseUrl`|`blooio.baseUrl`|`BLOOIO_BASE_URL` |true |`"https://backend.blooio.com/v2/api"`| * * System properties take precedence over environment variables. */ fun fromEnv() = apply { + logLevel(LogLevel.fromEnv()) (System.getProperty("blooio.baseUrl") ?: System.getenv("BLOOIO_BASE_URL"))?.let { baseUrl(it) } (System.getProperty("blooio.apiKey") ?: System.getenv("BLOOIO_API_KEY"))?.let { apiKey(it) } + System.getenv("BLOOIO_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()) + } + } + } } /** @@ -404,18 +439,26 @@ private constructor( headers.put("X-Stainless-Package-Version", getPackageVersion()) headers.put("X-Stainless-Runtime", "JRE") headers.put("X-Stainless-Runtime-Version", getJavaVersion()) + headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString()) + // We replace after all the default headers to allow end-users to overwrite them. + headers.replaceAll(this.headers.build()) + queryParams.replaceAll(this.queryParams.build()) apiKey.let { if (!it.isEmpty()) { - headers.put("Authorization", "Bearer $it") + headers.replace("Authorization", "Bearer $it") } } - 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) @@ -430,6 +473,7 @@ private constructor( responseValidation, timeout, maxRetries, + logLevel, apiKey, ) } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/LogLevel.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/LogLevel.kt new file mode 100644 index 0000000..b03b996 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/LogLevel.kt @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.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 `BLOOIO_LOG` environment variable. */ + fun fromEnv() = + when (System.getenv("BLOOIO_LOG")?.lowercase()) { + "info" -> INFO + "error" -> ERROR + "debug" -> DEBUG + else -> OFF + } + } +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/ObjectMappers.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/ObjectMappers.kt index 555296c..5bf6b78 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/ObjectMappers.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/ObjectMappers.kt @@ -24,11 +24,14 @@ import java.io.InputStream import java.time.DateTimeException import java.time.LocalDate import java.time.LocalDateTime -import java.time.ZonedDateTime +import java.time.OffsetDateTime +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()) @@ -36,7 +39,7 @@ fun jsonMapper(): JsonMapper = .addModule( SimpleModule() .addSerializer(InputStreamSerializer) - .addDeserializer(LocalDateTime::class.java, LenientLocalDateTimeDeserializer()) + .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) ) .withCoercionConfig(LogicalType.Boolean) { it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -47,6 +50,7 @@ fun jsonMapper(): JsonMapper = } .withCoercionConfig(LogicalType.Integer) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) .setCoercion(CoercionInputShape.String, CoercionAction.Fail) .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) @@ -64,6 +68,12 @@ fun jsonMapper(): JsonMapper = .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) } + .withCoercionConfig(LogicalType.DateTime) { + it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) + } .withCoercionConfig(LogicalType.Array) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -124,10 +134,10 @@ private object InputStreamSerializer : BaseSerializer(InputStream:: } /** - * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes. + * A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes. */ -private class LenientLocalDateTimeDeserializer : - StdDeserializer(LocalDateTime::class.java) { +private class LenientOffsetDateTimeDeserializer : + StdDeserializer(OffsetDateTime::class.java) { companion object { @@ -141,7 +151,7 @@ private class LenientLocalDateTimeDeserializer : override fun logicalType(): LogicalType = LogicalType.DateTime - override fun deserialize(p: JsonParser, context: DeserializationContext?): LocalDateTime { + override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime { val exceptions = mutableListOf() for (formatter in DATE_TIME_FORMATTERS) { @@ -150,17 +160,20 @@ private class LenientLocalDateTimeDeserializer : return when { !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() + LocalDate.from(temporal) + .atStartOfDay() + .atZone(ZoneId.of("UTC")) + .toOffsetDateTime() !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() + LocalDateTime.from(temporal).atZone(ZoneId.of("UTC")).toOffsetDateTime() + else -> OffsetDateTime.from(temporal) } } catch (e: DateTimeException) { exceptions.add(e) } } - throw JsonParseException(p, "Cannot parse `LocalDateTime` from value: ${p.text}").apply { + throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply { exceptions.forEach { addSuppressed(it) } } } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/Properties.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/Properties.kt index 5aa1fbd..60ca187 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/Properties.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/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 = - BlooioClient::class.java.`package`.implementationVersion ?: "unknown" + BlooioClient::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/blooio-java-core/src/main/kotlin/com/blooio/api/core/RequestOptions.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/RequestOptions.kt index 33976b6..9547b58 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/RequestOptions.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/RequestOptions.kt @@ -33,6 +33,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/blooio-java-core/src/main/kotlin/com/blooio/api/core/Utils.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/Utils.kt index 2cc7fdd..dde0e91 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/Utils.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/Utils.kt @@ -5,6 +5,7 @@ package com.blooio.api.core import com.blooio.api.errors.BlooioInvalidDataException import java.util.Collections import java.util.SortedMap +import java.util.SortedSet import java.util.concurrent.CompletableFuture import java.util.concurrent.locks.Lock @@ -16,6 +17,11 @@ internal fun T?.getOrThrow(name: String): T = internal fun List.toImmutable(): List = if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList()) +@JvmSynthetic +internal fun > SortedSet.toImmutable(): SortedSet = + if (isEmpty()) Collections.emptySortedSet() + else Collections.unmodifiableSortedSet(toSortedSet(comparator() ?: Comparator.naturalOrder())) + @JvmSynthetic internal fun Map.toImmutable(): Map = if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap()) diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/handlers/EmptyHandler.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/handlers/EmptyHandler.kt deleted file mode 100644 index 1b2fcda..0000000 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/handlers/EmptyHandler.kt +++ /dev/null @@ -1,12 +0,0 @@ -@file:JvmName("EmptyHandler") - -package com.blooio.api.core.handlers - -import com.blooio.api.core.http.HttpResponse -import com.blooio.api.core.http.HttpResponse.Handler - -@JvmSynthetic internal fun emptyHandler(): Handler = EmptyHandlerInternal - -private object EmptyHandlerInternal : Handler { - override fun handle(response: HttpResponse): Void? = null -} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequest.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequest.kt index 7c10228..5bb901b 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequest.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequest.kt @@ -2,6 +2,7 @@ package com.blooio.api.core.http import com.blooio.api.core.checkRequired import com.blooio.api.core.toImmutable +import java.net.URLEncoder class HttpRequest private constructor( @@ -13,6 +14,35 @@ private constructor( @get:JvmName("body") val body: HttpRequestBody?, ) { + fun url(): String = buildString { + append(baseUrl) + + pathSegments.forEach { segment -> + if (!endsWith("/")) { + append("/") + } + append(URLEncoder.encode(segment, "UTF-8")) + } + + if (queryParams.isEmpty()) { + return@buildString + } + + append("?") + var isFirst = true + queryParams.keys().forEach { key -> + queryParams.values(key).forEach { value -> + if (!isFirst) { + append("&") + } + append(URLEncoder.encode(key, "UTF-8")) + append("=") + append(URLEncoder.encode(value, "UTF-8")) + isFirst = false + } + } + } + fun toBuilder(): Builder = Builder().from(this) override fun toString(): String = diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequestBodies.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequestBodies.kt index c2c707d..6febc5e 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequestBodies.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/HttpRequestBodies.kt @@ -5,16 +5,16 @@ package com.blooio.api.core.http import com.blooio.api.core.MultipartField +import com.blooio.api.core.toImmutable import com.blooio.api.errors.BlooioInvalidDataException import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.node.JsonNodeType +import java.io.ByteArrayInputStream import java.io.InputStream import java.io.OutputStream +import java.util.UUID import kotlin.jvm.optionals.getOrNull -import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder -import org.apache.hc.core5.http.ContentType -import org.apache.hc.core5.http.HttpEntity @JvmSynthetic internal inline fun json(jsonMapper: JsonMapper, value: T): HttpRequestBody = @@ -37,92 +37,231 @@ 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().getOrNull() - 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().getOrNull() + 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 + + override fun contentLength(): Long = -1L - parts.forEach { (name, bytes) -> - addBinaryBody( - name, - bytes, - ContentType.parseLenient(field.contentType), - field.filename().getOrNull(), - ) + override fun repeatable(): Boolean = false + + override fun close() = bytes.close() + } } - } + + addPart( + MultipartBody.Part.create( + name, + field.filename().getOrNull(), + 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().inputStream()) - JsonNodeType.BOOLEAN -> - sequenceOf(name to node.booleanValue().toString().inputStream()) - JsonNodeType.NUMBER -> - sequenceOf(name to node.numberValue().toString().inputStream()) - JsonNodeType.ARRAY -> - sequenceOf( - name to - node - .elements() - .asSequence() - .mapNotNull { element -> - when (element.nodeType) { - JsonNodeType.MISSING, - JsonNodeType.NULL -> null - JsonNodeType.STRING -> node.textValue() - JsonNodeType.BOOLEAN -> node.booleanValue().toString() - JsonNodeType.NUMBER -> node.numberValue().toString() - null, - JsonNodeType.BINARY, - JsonNodeType.ARRAY, - JsonNodeType.OBJECT, - JsonNodeType.POJO -> - throw BlooioInvalidDataException( - "Unexpected JsonNode type in array: ${node.nodeType}" - ) - } - } - .joinToString(",") - .inputStream() - ) - JsonNodeType.OBJECT -> - node.fields().asSequence().flatMap { (key, value) -> - serializePart("$name[$key]", value) - } - JsonNodeType.POJO, - null -> - throw BlooioInvalidDataException("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().byteInputStream()) + JsonNodeType.BOOLEAN -> sequenceOf(name to node.booleanValue().toString().byteInputStream()) + JsonNodeType.NUMBER -> sequenceOf(name to node.numberValue().toString().byteInputStream()) + JsonNodeType.ARRAY -> + sequenceOf( + name to + node + .elements() + .asSequence() + .mapNotNull { element -> + when (element.nodeType) { + JsonNodeType.MISSING, + JsonNodeType.NULL -> null + JsonNodeType.STRING -> element.textValue() + JsonNodeType.BOOLEAN -> element.booleanValue().toString() + JsonNodeType.NUMBER -> element.numberValue().toString() + null, + JsonNodeType.BINARY, + JsonNodeType.ARRAY, + JsonNodeType.OBJECT, + JsonNodeType.POJO -> + throw BlooioInvalidDataException( + "Unexpected JsonNode type in array: ${element.nodeType}" + ) + } + } + .joinToString(",") + .byteInputStream() + ) + JsonNodeType.OBJECT -> + node.fields().asSequence().flatMap { (key, value) -> + serializePart("$name[$key]", value) + } + JsonNodeType.POJO, + null -> throw BlooioInvalidDataException("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/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/LoggingHttpClient.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/LoggingHttpClient.kt new file mode 100644 index 0000000..727d765 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/LoggingHttpClient.kt @@ -0,0 +1,628 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.core.http + +import com.blooio.api.core.LogLevel +import com.blooio.api.core.RequestOptions +import com.blooio.api.core.checkRequired +import com.blooio.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 java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionException +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. */ + @get:JvmName("httpClient") val httpClient: HttpClient, + /** + * Sensitive headers to redact from logs. + * + * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie")`. + */ + @get:JvmName("redactedHeaders") 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]. + */ + @get:JvmName("clock") val clock: Clock, + /** + * The log level to use. + * + * Pass [LogLevel.fromEnv] to read from environment variables. + */ + @get:JvmName("level") 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 fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): CompletableFuture { + val loggingRequest = logRequest(request) + + val before = OffsetDateTime.now(clock) + val future = + try { + httpClient.executeAsync(loggingRequest, requestOptions) + } catch (e: Throwable) { + logFailure(e, Duration.between(before, OffsetDateTime.now(clock))) + throw e + } + return future.handle { response, error -> + val took = Duration.between(before, OffsetDateTime.now(clock)) + if (error != null) { + logFailure(unwrapCompletionException(error), took) + throw error + } + 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 unwrapCompletionException(error: Throwable): Throwable = + if (error is CompletionException && error.cause != null) error.cause!! else error + + 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: + * ```java + * .httpClient() + * .level() + * ``` + */ + @JvmStatic 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 + + @JvmSynthetic + 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: + * ```java + * .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/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/ProxyAuthenticator.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/ProxyAuthenticator.kt new file mode 100644 index 0000000..8fa881a --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/ProxyAuthenticator.kt @@ -0,0 +1,59 @@ +package com.blooio.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/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/RetryingHttpClient.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/RetryingHttpClient.kt index 6f34419..97644f6 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/RetryingHttpClient.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/core/http/RetryingHttpClient.kt @@ -1,3 +1,5 @@ +// File generated from our OpenAPI spec by Stainless. + package com.blooio.api.core.http import com.blooio.api.core.DefaultSleeper @@ -31,10 +33,6 @@ private constructor( ) : HttpClient { override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.execute(request, requestOptions) - } - var modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -48,6 +46,10 @@ private constructor( modifiedRequest = setRetryCountHeader(modifiedRequest, retries) } + if (!isRetryable(modifiedRequest)) { + return httpClient.execute(modifiedRequest, requestOptions) + } + val response = try { val response = httpClient.execute(modifiedRequest, requestOptions) @@ -75,10 +77,6 @@ private constructor( request: HttpRequest, requestOptions: RequestOptions, ): CompletableFuture { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.executeAsync(request, requestOptions) - } - val modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -94,8 +92,12 @@ private constructor( val requestWithRetryCount = if (shouldSendRetryCount) setRetryCountHeader(request, retries) else request - return httpClient - .executeAsync(requestWithRetryCount, requestOptions) + val responseFuture = httpClient.executeAsync(requestWithRetryCount, requestOptions) + if (!isRetryable(requestWithRetryCount)) { + return responseFuture + } + + return responseFuture .handleAsync( fun( response: HttpResponse?, @@ -199,7 +201,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, @@ -212,13 +214,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/blooio-java-core/src/main/kotlin/com/blooio/api/errors/BadRequestException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/BadRequestException.kt index 4474405..abc5aa2 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/BadRequestException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/BadRequestException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class BadRequestException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("400: $body", cause) { + BlooioServiceException( + "400: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 400 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/InternalServerException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/InternalServerException.kt index 8597337..dea69e8 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/InternalServerException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/InternalServerException.kt @@ -5,6 +5,7 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -14,7 +15,11 @@ private constructor( private val headers: Headers, private val body: JsonValue, cause: Throwable?, -) : BlooioServiceException("$statusCode: $body", cause) { +) : + BlooioServiceException( + "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = statusCode diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/NotFoundException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/NotFoundException.kt index 6fd595e..a3f41c3 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/NotFoundException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/NotFoundException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class NotFoundException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("404: $body", cause) { + BlooioServiceException( + "404: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 404 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/PermissionDeniedException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/PermissionDeniedException.kt index 664a509..3c343bc 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/PermissionDeniedException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/PermissionDeniedException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class PermissionDeniedException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("403: $body", cause) { + BlooioServiceException( + "403: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 403 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/RateLimitException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/RateLimitException.kt index 6f2e923..9227447 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/RateLimitException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/RateLimitException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class RateLimitException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("429: $body", cause) { + BlooioServiceException( + "429: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 429 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnauthorizedException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnauthorizedException.kt index ffa0075..bd54fc0 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnauthorizedException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnauthorizedException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class UnauthorizedException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("401: $body", cause) { + BlooioServiceException( + "401: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 401 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnexpectedStatusCodeException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnexpectedStatusCodeException.kt index 03bae54..ff324db 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnexpectedStatusCodeException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnexpectedStatusCodeException.kt @@ -5,6 +5,7 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -14,7 +15,11 @@ private constructor( private val headers: Headers, private val body: JsonValue, cause: Throwable?, -) : BlooioServiceException("$statusCode: $body", cause) { +) : + BlooioServiceException( + "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = statusCode diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnprocessableEntityException.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnprocessableEntityException.kt index ba5ee08..bdd02cc 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnprocessableEntityException.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/errors/UnprocessableEntityException.kt @@ -5,12 +5,16 @@ package com.blooio.api.errors import com.blooio.api.core.JsonValue import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers +import com.blooio.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class UnprocessableEntityException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - BlooioServiceException("422: $body", cause) { + BlooioServiceException( + "422: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 422 diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListParams.kt new file mode 100644 index 0000000..cba7bbc --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListParams.kt @@ -0,0 +1,406 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.Enum +import com.blooio.api.core.JsonField +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.errors.BlooioInvalidDataException +import com.fasterxml.jackson.annotation.JsonCreator +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** List all unique conversations for the organization, sorted by most recent message. */ +class ChatListParams +private constructor( + private val limit: Long?, + private val offset: Long?, + private val q: String?, + private val sort: Sort?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + /** + * Maximum number of items to return in a single response. Must be between 1 and 200; defaults + * to 50. Use together with `offset` to page through large result sets. + */ + fun limit(): Optional = Optional.ofNullable(limit) + + /** + * Number of items to skip before returning results. Combine with `limit` for page-based + * pagination (e.g. `offset=50&limit=50` returns the second page). Defaults to 0. + */ + fun offset(): Optional = Optional.ofNullable(offset) + + /** Search query (matches phone/email or contact name) */ + fun q(): Optional = Optional.ofNullable(q) + + /** Sort order */ + fun sort(): Optional = Optional.ofNullable(sort) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): ChatListParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [ChatListParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatListParams]. */ + class Builder internal constructor() { + + private var limit: Long? = null + private var offset: Long? = null + private var q: String? = null + private var sort: Sort? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(chatListParams: ChatListParams) = apply { + limit = chatListParams.limit + offset = chatListParams.offset + q = chatListParams.q + sort = chatListParams.sort + additionalHeaders = chatListParams.additionalHeaders.toBuilder() + additionalQueryParams = chatListParams.additionalQueryParams.toBuilder() + } + + /** + * Maximum number of items to return in a single response. Must be between 1 and 200; + * defaults to 50. Use together with `offset` to page through large result sets. + */ + fun limit(limit: Long?) = apply { this.limit = limit } + + /** + * Alias for [Builder.limit]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun limit(limit: Long) = limit(limit as Long?) + + /** Alias for calling [Builder.limit] with `limit.orElse(null)`. */ + fun limit(limit: Optional) = limit(limit.getOrNull()) + + /** + * Number of items to skip before returning results. Combine with `limit` for page-based + * pagination (e.g. `offset=50&limit=50` returns the second page). Defaults to 0. + */ + fun offset(offset: Long?) = apply { this.offset = offset } + + /** + * Alias for [Builder.offset]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun offset(offset: Long) = offset(offset as Long?) + + /** Alias for calling [Builder.offset] with `offset.orElse(null)`. */ + fun offset(offset: Optional) = offset(offset.getOrNull()) + + /** Search query (matches phone/email or contact name) */ + fun q(q: String?) = apply { this.q = q } + + /** Alias for calling [Builder.q] with `q.orElse(null)`. */ + fun q(q: Optional) = q(q.getOrNull()) + + /** Sort order */ + fun sort(sort: Sort?) = apply { this.sort = sort } + + /** Alias for calling [Builder.sort] with `sort.orElse(null)`. */ + fun sort(sort: Optional) = sort(sort.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [ChatListParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatListParams = + ChatListParams( + limit, + offset, + q, + sort, + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = + QueryParams.builder() + .apply { + limit?.let { put("limit", it.toString()) } + offset?.let { put("offset", it.toString()) } + q?.let { put("q", it) } + sort?.let { put("sort", it.toString()) } + putAll(additionalQueryParams) + } + .build() + + /** Sort order */ + class Sort @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val RECENT = of("recent") + + @JvmField val OLDEST = of("oldest") + + @JvmStatic fun of(value: String) = Sort(JsonField.of(value)) + } + + /** An enum containing [Sort]'s known values. */ + enum class Known { + RECENT, + OLDEST, + } + + /** + * An enum containing [Sort]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Sort] 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 { + RECENT, + OLDEST, + /** An enum member indicating that [Sort] 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) { + RECENT -> Value.RECENT + OLDEST -> Value.OLDEST + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + RECENT -> Known.RECENT + OLDEST -> Known.OLDEST + else -> throw BlooioInvalidDataException("Unknown Sort: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Sort = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Sort && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatListParams && + limit == other.limit && + offset == other.offset && + q == other.q && + sort == other.sort && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(limit, offset, q, sort, additionalHeaders, additionalQueryParams) + + override fun toString() = + "ChatListParams{limit=$limit, offset=$offset, q=$q, sort=$sort, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListResponse.kt new file mode 100644 index 0000000..f306121 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatListResponse.kt @@ -0,0 +1,1409 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.checkKnown +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +import com.blooio.api.models.contacts.Pagination +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class ChatListResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val chats: JsonField>, + private val pagination: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("chats") @ExcludeMissing chats: JsonField> = JsonMissing.of(), + @JsonProperty("pagination") + @ExcludeMissing + pagination: JsonField = JsonMissing.of(), + ) : this(chats, pagination, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chats(): Optional> = chats.getOptional("chats") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun pagination(): Optional = pagination.getOptional("pagination") + + /** + * Returns the raw JSON value of [chats]. + * + * Unlike [chats], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chats") @ExcludeMissing fun _chats(): JsonField> = chats + + /** + * Returns the raw JSON value of [pagination]. + * + * Unlike [pagination], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("pagination") + @ExcludeMissing + fun _pagination(): JsonField = pagination + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [ChatListResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatListResponse]. */ + class Builder internal constructor() { + + private var chats: JsonField>? = null + private var pagination: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatListResponse: ChatListResponse) = apply { + chats = chatListResponse.chats.map { it.toMutableList() } + pagination = chatListResponse.pagination + additionalProperties = chatListResponse.additionalProperties.toMutableMap() + } + + fun chats(chats: List) = chats(JsonField.of(chats)) + + /** + * Sets [Builder.chats] to an arbitrary JSON value. + * + * You should usually call [Builder.chats] with a well-typed `List` value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun chats(chats: JsonField>) = apply { + this.chats = chats.map { it.toMutableList() } + } + + /** + * Adds a single [Chat] to [chats]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addChat(chat: Chat) = apply { + chats = + (chats ?: JsonField.of(mutableListOf())).also { checkKnown("chats", it).add(chat) } + } + + fun pagination(pagination: Pagination) = pagination(JsonField.of(pagination)) + + /** + * Sets [Builder.pagination] to an arbitrary JSON value. + * + * You should usually call [Builder.pagination] with a well-typed [Pagination] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun pagination(pagination: JsonField) = apply { this.pagination = pagination } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ChatListResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatListResponse = + ChatListResponse( + (chats ?: JsonMissing.of()).map { it.toImmutable() }, + pagination, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ChatListResponse = apply { + if (validated) { + return@apply + } + + chats().ifPresent { it.forEach { it.validate() } } + pagination().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (chats.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (pagination.asKnown().getOrNull()?.validity() ?: 0) + + class Chat + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val id: JsonField, + private val backgroundId: JsonField, + private val backgroundUrl: JsonField, + private val contact: JsonField, + private val groupId: JsonField, + private val groupName: JsonField, + private val inboundCount: JsonField, + private val isGroup: JsonField, + private val lastInboundTime: JsonField, + private val lastMessage: JsonField, + private val lastMessageTime: JsonField, + private val lastOutboundTime: JsonField, + private val memberCount: JsonField, + private val messageCount: JsonField, + private val outboundCount: JsonField, + private val type: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("id") @ExcludeMissing id: JsonField = JsonMissing.of(), + @JsonProperty("background_id") + @ExcludeMissing + backgroundId: JsonField = JsonMissing.of(), + @JsonProperty("background_url") + @ExcludeMissing + backgroundUrl: JsonField = JsonMissing.of(), + @JsonProperty("contact") @ExcludeMissing contact: JsonField = JsonMissing.of(), + @JsonProperty("group_id") @ExcludeMissing groupId: JsonField = JsonMissing.of(), + @JsonProperty("group_name") + @ExcludeMissing + groupName: JsonField = JsonMissing.of(), + @JsonProperty("inbound_count") + @ExcludeMissing + inboundCount: JsonField = JsonMissing.of(), + @JsonProperty("is_group") + @ExcludeMissing + isGroup: JsonField = JsonMissing.of(), + @JsonProperty("last_inbound_time") + @ExcludeMissing + lastInboundTime: JsonField = JsonMissing.of(), + @JsonProperty("last_message") + @ExcludeMissing + lastMessage: JsonField = JsonMissing.of(), + @JsonProperty("last_message_time") + @ExcludeMissing + lastMessageTime: JsonField = JsonMissing.of(), + @JsonProperty("last_outbound_time") + @ExcludeMissing + lastOutboundTime: JsonField = JsonMissing.of(), + @JsonProperty("member_count") + @ExcludeMissing + memberCount: JsonField = JsonMissing.of(), + @JsonProperty("message_count") + @ExcludeMissing + messageCount: JsonField = JsonMissing.of(), + @JsonProperty("outbound_count") + @ExcludeMissing + outboundCount: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonField = JsonMissing.of(), + ) : this( + id, + backgroundId, + backgroundUrl, + contact, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + mutableMapOf(), + ) + + /** + * Chat identifier (phone number, email, or group ID) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun id(): Optional = id.getOptional("id") + + /** + * Identifier for the active chat background + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundId(): Optional = backgroundId.getOptional("background_id") + + /** + * Public URL of the chat background image (if one has been set via the API) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundUrl(): Optional = backgroundUrl.getOptional("background_url") + + /** + * Contact info (only for non-group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun contact(): Optional = contact.getOptional("contact") + + /** + * Group ID (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupId(): Optional = groupId.getOptional("group_id") + + /** + * Group name (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupName(): Optional = groupName.getOptional("group_name") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun inboundCount(): Optional = inboundCount.getOptional("inbound_count") + + /** + * Whether this is a group chat + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun isGroup(): Optional = isGroup.getOptional("is_group") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastInboundTime(): Optional = lastInboundTime.getOptional("last_inbound_time") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastMessage(): Optional = lastMessage.getOptional("last_message") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastMessageTime(): Optional = lastMessageTime.getOptional("last_message_time") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastOutboundTime(): Optional = lastOutboundTime.getOptional("last_outbound_time") + + /** + * Number of members (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun memberCount(): Optional = memberCount.getOptional("member_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageCount(): Optional = messageCount.getOptional("message_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun outboundCount(): Optional = outboundCount.getOptional("outbound_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun type(): Optional = type.getOptional("type") + + /** + * Returns the raw JSON value of [id]. + * + * Unlike [id], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("id") @ExcludeMissing fun _id(): JsonField = id + + /** + * Returns the raw JSON value of [backgroundId]. + * + * Unlike [backgroundId], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("background_id") + @ExcludeMissing + fun _backgroundId(): JsonField = backgroundId + + /** + * Returns the raw JSON value of [backgroundUrl]. + * + * Unlike [backgroundUrl], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("background_url") + @ExcludeMissing + fun _backgroundUrl(): JsonField = backgroundUrl + + /** + * Returns the raw JSON value of [contact]. + * + * Unlike [contact], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("contact") @ExcludeMissing fun _contact(): JsonField = contact + + /** + * Returns the raw JSON value of [groupId]. + * + * Unlike [groupId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_id") @ExcludeMissing fun _groupId(): JsonField = groupId + + /** + * Returns the raw JSON value of [groupName]. + * + * Unlike [groupName], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_name") @ExcludeMissing fun _groupName(): JsonField = groupName + + /** + * Returns the raw JSON value of [inboundCount]. + * + * Unlike [inboundCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("inbound_count") + @ExcludeMissing + fun _inboundCount(): JsonField = inboundCount + + /** + * Returns the raw JSON value of [isGroup]. + * + * Unlike [isGroup], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("is_group") @ExcludeMissing fun _isGroup(): JsonField = isGroup + + /** + * Returns the raw JSON value of [lastInboundTime]. + * + * Unlike [lastInboundTime], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_inbound_time") + @ExcludeMissing + fun _lastInboundTime(): JsonField = lastInboundTime + + /** + * Returns the raw JSON value of [lastMessage]. + * + * Unlike [lastMessage], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_message") + @ExcludeMissing + fun _lastMessage(): JsonField = lastMessage + + /** + * Returns the raw JSON value of [lastMessageTime]. + * + * Unlike [lastMessageTime], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_message_time") + @ExcludeMissing + fun _lastMessageTime(): JsonField = lastMessageTime + + /** + * Returns the raw JSON value of [lastOutboundTime]. + * + * Unlike [lastOutboundTime], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_outbound_time") + @ExcludeMissing + fun _lastOutboundTime(): JsonField = lastOutboundTime + + /** + * Returns the raw JSON value of [memberCount]. + * + * Unlike [memberCount], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("member_count") + @ExcludeMissing + fun _memberCount(): JsonField = memberCount + + /** + * Returns the raw JSON value of [messageCount]. + * + * Unlike [messageCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("message_count") + @ExcludeMissing + fun _messageCount(): JsonField = messageCount + + /** + * Returns the raw JSON value of [outboundCount]. + * + * Unlike [outboundCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("outbound_count") + @ExcludeMissing + fun _outboundCount(): JsonField = outboundCount + + /** + * Returns the raw JSON value of [type]. + * + * Unlike [type], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonField = type + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Chat]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Chat]. */ + class Builder internal constructor() { + + private var id: JsonField = JsonMissing.of() + private var backgroundId: JsonField = JsonMissing.of() + private var backgroundUrl: JsonField = JsonMissing.of() + private var contact: JsonField = JsonMissing.of() + private var groupId: JsonField = JsonMissing.of() + private var groupName: JsonField = JsonMissing.of() + private var inboundCount: JsonField = JsonMissing.of() + private var isGroup: JsonField = JsonMissing.of() + private var lastInboundTime: JsonField = JsonMissing.of() + private var lastMessage: JsonField = JsonMissing.of() + private var lastMessageTime: JsonField = JsonMissing.of() + private var lastOutboundTime: JsonField = JsonMissing.of() + private var memberCount: JsonField = JsonMissing.of() + private var messageCount: JsonField = JsonMissing.of() + private var outboundCount: JsonField = JsonMissing.of() + private var type: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chat: Chat) = apply { + id = chat.id + backgroundId = chat.backgroundId + backgroundUrl = chat.backgroundUrl + contact = chat.contact + groupId = chat.groupId + groupName = chat.groupName + inboundCount = chat.inboundCount + isGroup = chat.isGroup + lastInboundTime = chat.lastInboundTime + lastMessage = chat.lastMessage + lastMessageTime = chat.lastMessageTime + lastOutboundTime = chat.lastOutboundTime + memberCount = chat.memberCount + messageCount = chat.messageCount + outboundCount = chat.outboundCount + type = chat.type + additionalProperties = chat.additionalProperties.toMutableMap() + } + + /** Chat identifier (phone number, email, or group ID) */ + fun id(id: String) = id(JsonField.of(id)) + + /** + * Sets [Builder.id] to an arbitrary JSON value. + * + * You should usually call [Builder.id] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun id(id: JsonField) = apply { this.id = id } + + /** Identifier for the active chat background */ + fun backgroundId(backgroundId: String?) = + backgroundId(JsonField.ofNullable(backgroundId)) + + /** Alias for calling [Builder.backgroundId] with `backgroundId.orElse(null)`. */ + fun backgroundId(backgroundId: Optional) = + backgroundId(backgroundId.getOrNull()) + + /** + * Sets [Builder.backgroundId] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun backgroundId(backgroundId: JsonField) = apply { + this.backgroundId = backgroundId + } + + /** Public URL of the chat background image (if one has been set via the API) */ + fun backgroundUrl(backgroundUrl: String?) = + backgroundUrl(JsonField.ofNullable(backgroundUrl)) + + /** Alias for calling [Builder.backgroundUrl] with `backgroundUrl.orElse(null)`. */ + fun backgroundUrl(backgroundUrl: Optional) = + backgroundUrl(backgroundUrl.getOrNull()) + + /** + * Sets [Builder.backgroundUrl] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundUrl] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun backgroundUrl(backgroundUrl: JsonField) = apply { + this.backgroundUrl = backgroundUrl + } + + /** Contact info (only for non-group chats) */ + fun contact(contact: Contact?) = contact(JsonField.ofNullable(contact)) + + /** Alias for calling [Builder.contact] with `contact.orElse(null)`. */ + fun contact(contact: Optional) = contact(contact.getOrNull()) + + /** + * Sets [Builder.contact] to an arbitrary JSON value. + * + * You should usually call [Builder.contact] with a well-typed [Contact] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun contact(contact: JsonField) = apply { this.contact = contact } + + /** Group ID (only for group chats) */ + fun groupId(groupId: String?) = groupId(JsonField.ofNullable(groupId)) + + /** Alias for calling [Builder.groupId] with `groupId.orElse(null)`. */ + fun groupId(groupId: Optional) = groupId(groupId.getOrNull()) + + /** + * Sets [Builder.groupId] to an arbitrary JSON value. + * + * You should usually call [Builder.groupId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun groupId(groupId: JsonField) = apply { this.groupId = groupId } + + /** Group name (only for group chats) */ + fun groupName(groupName: String?) = groupName(JsonField.ofNullable(groupName)) + + /** Alias for calling [Builder.groupName] with `groupName.orElse(null)`. */ + fun groupName(groupName: Optional) = groupName(groupName.getOrNull()) + + /** + * Sets [Builder.groupName] to an arbitrary JSON value. + * + * You should usually call [Builder.groupName] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun groupName(groupName: JsonField) = apply { this.groupName = groupName } + + fun inboundCount(inboundCount: Long) = inboundCount(JsonField.of(inboundCount)) + + /** + * Sets [Builder.inboundCount] to an arbitrary JSON value. + * + * You should usually call [Builder.inboundCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun inboundCount(inboundCount: JsonField) = apply { + this.inboundCount = inboundCount + } + + /** Whether this is a group chat */ + fun isGroup(isGroup: Boolean) = isGroup(JsonField.of(isGroup)) + + /** + * Sets [Builder.isGroup] to an arbitrary JSON value. + * + * You should usually call [Builder.isGroup] with a well-typed [Boolean] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun isGroup(isGroup: JsonField) = apply { this.isGroup = isGroup } + + fun lastInboundTime(lastInboundTime: Long?) = + lastInboundTime(JsonField.ofNullable(lastInboundTime)) + + /** + * Alias for [Builder.lastInboundTime]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun lastInboundTime(lastInboundTime: Long) = lastInboundTime(lastInboundTime as Long?) + + /** Alias for calling [Builder.lastInboundTime] with `lastInboundTime.orElse(null)`. */ + fun lastInboundTime(lastInboundTime: Optional) = + lastInboundTime(lastInboundTime.getOrNull()) + + /** + * Sets [Builder.lastInboundTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastInboundTime] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastInboundTime(lastInboundTime: JsonField) = apply { + this.lastInboundTime = lastInboundTime + } + + fun lastMessage(lastMessage: LastMessage) = lastMessage(JsonField.of(lastMessage)) + + /** + * Sets [Builder.lastMessage] to an arbitrary JSON value. + * + * You should usually call [Builder.lastMessage] with a well-typed [LastMessage] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastMessage(lastMessage: JsonField) = apply { + this.lastMessage = lastMessage + } + + fun lastMessageTime(lastMessageTime: Long) = + lastMessageTime(JsonField.of(lastMessageTime)) + + /** + * Sets [Builder.lastMessageTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastMessageTime] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastMessageTime(lastMessageTime: JsonField) = apply { + this.lastMessageTime = lastMessageTime + } + + fun lastOutboundTime(lastOutboundTime: Long?) = + lastOutboundTime(JsonField.ofNullable(lastOutboundTime)) + + /** + * Alias for [Builder.lastOutboundTime]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun lastOutboundTime(lastOutboundTime: Long) = + lastOutboundTime(lastOutboundTime as Long?) + + /** + * Alias for calling [Builder.lastOutboundTime] with `lastOutboundTime.orElse(null)`. + */ + fun lastOutboundTime(lastOutboundTime: Optional) = + lastOutboundTime(lastOutboundTime.getOrNull()) + + /** + * Sets [Builder.lastOutboundTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastOutboundTime] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastOutboundTime(lastOutboundTime: JsonField) = apply { + this.lastOutboundTime = lastOutboundTime + } + + /** Number of members (only for group chats) */ + fun memberCount(memberCount: Long) = memberCount(JsonField.of(memberCount)) + + /** + * Sets [Builder.memberCount] to an arbitrary JSON value. + * + * You should usually call [Builder.memberCount] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun memberCount(memberCount: JsonField) = apply { this.memberCount = memberCount } + + fun messageCount(messageCount: Long) = messageCount(JsonField.of(messageCount)) + + /** + * Sets [Builder.messageCount] to an arbitrary JSON value. + * + * You should usually call [Builder.messageCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messageCount(messageCount: JsonField) = apply { + this.messageCount = messageCount + } + + fun outboundCount(outboundCount: Long) = outboundCount(JsonField.of(outboundCount)) + + /** + * Sets [Builder.outboundCount] to an arbitrary JSON value. + * + * You should usually call [Builder.outboundCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun outboundCount(outboundCount: JsonField) = apply { + this.outboundCount = outboundCount + } + + fun type(type: Type) = type(JsonField.of(type)) + + /** + * Sets [Builder.type] to an arbitrary JSON value. + * + * You should usually call [Builder.type] with a well-typed [Type] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun type(type: JsonField) = apply { this.type = type } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Chat]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Chat = + Chat( + id, + backgroundId, + backgroundUrl, + contact, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Chat = apply { + if (validated) { + return@apply + } + + id() + backgroundId() + backgroundUrl() + contact().ifPresent { it.validate() } + groupId() + groupName() + inboundCount() + isGroup() + lastInboundTime() + lastMessage().ifPresent { it.validate() } + lastMessageTime() + lastOutboundTime() + memberCount() + messageCount() + outboundCount() + type().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (id.asKnown().isPresent) 1 else 0) + + (if (backgroundId.asKnown().isPresent) 1 else 0) + + (if (backgroundUrl.asKnown().isPresent) 1 else 0) + + (contact.asKnown().getOrNull()?.validity() ?: 0) + + (if (groupId.asKnown().isPresent) 1 else 0) + + (if (groupName.asKnown().isPresent) 1 else 0) + + (if (inboundCount.asKnown().isPresent) 1 else 0) + + (if (isGroup.asKnown().isPresent) 1 else 0) + + (if (lastInboundTime.asKnown().isPresent) 1 else 0) + + (lastMessage.asKnown().getOrNull()?.validity() ?: 0) + + (if (lastMessageTime.asKnown().isPresent) 1 else 0) + + (if (lastOutboundTime.asKnown().isPresent) 1 else 0) + + (if (memberCount.asKnown().isPresent) 1 else 0) + + (if (messageCount.asKnown().isPresent) 1 else 0) + + (if (outboundCount.asKnown().isPresent) 1 else 0) + + (type.asKnown().getOrNull()?.validity() ?: 0) + + /** Contact info (only for non-group chats) */ + class Contact + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val contactId: JsonField, + private val identifier: JsonField, + private val name: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("contact_id") + @ExcludeMissing + contactId: JsonField = JsonMissing.of(), + @JsonProperty("identifier") + @ExcludeMissing + identifier: JsonField = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + ) : this(contactId, identifier, name, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun contactId(): Optional = contactId.getOptional("contact_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun identifier(): Optional = identifier.getOptional("identifier") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun name(): Optional = name.getOptional("name") + + /** + * Returns the raw JSON value of [contactId]. + * + * Unlike [contactId], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("contact_id") + @ExcludeMissing + fun _contactId(): JsonField = contactId + + /** + * Returns the raw JSON value of [identifier]. + * + * Unlike [identifier], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("identifier") + @ExcludeMissing + fun _identifier(): JsonField = identifier + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Contact]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Contact]. */ + class Builder internal constructor() { + + private var contactId: JsonField = JsonMissing.of() + private var identifier: JsonField = JsonMissing.of() + private var name: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(contact: Contact) = apply { + contactId = contact.contactId + identifier = contact.identifier + name = contact.name + additionalProperties = contact.additionalProperties.toMutableMap() + } + + fun contactId(contactId: String) = contactId(JsonField.of(contactId)) + + /** + * Sets [Builder.contactId] to an arbitrary JSON value. + * + * You should usually call [Builder.contactId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun contactId(contactId: JsonField) = apply { this.contactId = contactId } + + fun identifier(identifier: String) = identifier(JsonField.of(identifier)) + + /** + * Sets [Builder.identifier] to an arbitrary JSON value. + * + * You should usually call [Builder.identifier] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun identifier(identifier: JsonField) = apply { + this.identifier = identifier + } + + fun name(name: String?) = name(JsonField.ofNullable(name)) + + /** Alias for calling [Builder.name] with `name.orElse(null)`. */ + fun name(name: Optional) = name(name.getOrNull()) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun name(name: JsonField) = apply { this.name = name } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = + apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { + additionalProperties.remove(key) + } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Contact]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Contact = + Contact(contactId, identifier, name, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Contact = apply { + if (validated) { + return@apply + } + + contactId() + identifier() + name() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (contactId.asKnown().isPresent) 1 else 0) + + (if (identifier.asKnown().isPresent) 1 else 0) + + (if (name.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Contact && + contactId == other.contactId && + identifier == other.identifier && + name == other.name && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(contactId, identifier, name, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Contact{contactId=$contactId, identifier=$identifier, name=$name, additionalProperties=$additionalProperties}" + } + + class Type @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is + * on an older version than the API, then the API may respond with new members that the + * SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PHONE = of("phone") + + @JvmField val EMAIL = of("email") + + @JvmField val GROUP = of("group") + + @JvmStatic fun of(value: String) = Type(JsonField.of(value)) + } + + /** An enum containing [Type]'s known values. */ + enum class Known { + PHONE, + EMAIL, + GROUP, + } + + /** + * An enum containing [Type]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Type] 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 { + PHONE, + EMAIL, + GROUP, + /** An enum member indicating that [Type] 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) { + PHONE -> Value.PHONE + EMAIL -> Value.EMAIL + GROUP -> Value.GROUP + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PHONE -> Known.PHONE + EMAIL -> Known.EMAIL + GROUP -> Known.GROUP + else -> throw BlooioInvalidDataException("Unknown Type: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Type = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Type && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Chat && + id == other.id && + backgroundId == other.backgroundId && + backgroundUrl == other.backgroundUrl && + contact == other.contact && + groupId == other.groupId && + groupName == other.groupName && + inboundCount == other.inboundCount && + isGroup == other.isGroup && + lastInboundTime == other.lastInboundTime && + lastMessage == other.lastMessage && + lastMessageTime == other.lastMessageTime && + lastOutboundTime == other.lastOutboundTime && + memberCount == other.memberCount && + messageCount == other.messageCount && + outboundCount == other.outboundCount && + type == other.type && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + id, + backgroundId, + backgroundUrl, + contact, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Chat{id=$id, backgroundId=$backgroundId, backgroundUrl=$backgroundUrl, contact=$contact, groupId=$groupId, groupName=$groupName, inboundCount=$inboundCount, isGroup=$isGroup, lastInboundTime=$lastInboundTime, lastMessage=$lastMessage, lastMessageTime=$lastMessageTime, lastOutboundTime=$lastOutboundTime, memberCount=$memberCount, messageCount=$messageCount, outboundCount=$outboundCount, type=$type, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatListResponse && + chats == other.chats && + pagination == other.pagination && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(chats, pagination, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ChatListResponse{chats=$chats, pagination=$pagination, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadParams.kt new file mode 100644 index 0000000..67be4e1 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadParams.kt @@ -0,0 +1,229 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.JsonValue +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.core.toImmutable +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** Mark all messages in a chat as read. This sends a read receipt to the sender. */ +class ChatMarkAsReadParams +private constructor( + private val chatId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** Additional body properties to send with the request. */ + fun _additionalBodyProperties(): Map = additionalBodyProperties + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): ChatMarkAsReadParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [ChatMarkAsReadParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatMarkAsReadParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatMarkAsReadParams: ChatMarkAsReadParams) = apply { + chatId = chatMarkAsReadParams.chatId + additionalHeaders = chatMarkAsReadParams.additionalHeaders.toBuilder() + additionalQueryParams = chatMarkAsReadParams.additionalQueryParams.toBuilder() + additionalBodyProperties = chatMarkAsReadParams.additionalBodyProperties.toMutableMap() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + putAllAdditionalBodyProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { + additionalBodyProperties.remove(key) + } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalBodyProperty) + } + + /** + * Returns an immutable instance of [ChatMarkAsReadParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatMarkAsReadParams = + ChatMarkAsReadParams( + chatId, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatMarkAsReadParams && + chatId == other.chatId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash(chatId, additionalHeaders, additionalQueryParams, additionalBodyProperties) + + override fun toString() = + "ChatMarkAsReadParams{chatId=$chatId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadResponse.kt similarity index 65% rename from blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusResponse.kt rename to blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadResponse.kt index 4a7029d..8d7d65a 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusResponse.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatMarkAsReadResponse.kt @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. -package com.blooio.api.models.messages +package com.blooio.api.models.chats import com.blooio.api.core.Enum import com.blooio.api.core.ExcludeMissing @@ -17,38 +17,59 @@ import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull -class MessageGetStatusResponse +class ChatMarkAsReadResponse @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( - private val messageId: JsonField, + private val chatId: JsonField, + private val markedAt: JsonField, private val status: JsonField, private val additionalProperties: MutableMap, ) { @JsonCreator private constructor( - @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("marked_at") @ExcludeMissing markedAt: JsonField = JsonMissing.of(), @JsonProperty("status") @ExcludeMissing status: JsonField = JsonMissing.of(), - ) : this(messageId, status, mutableMapOf()) + ) : this(chatId, markedAt, status, mutableMapOf()) /** + * Chat identifier + * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun messageId(): Optional = messageId.getOptional("message_id") + fun chatId(): Optional = chatId.getOptional("chat_id") /** + * Timestamp when marked as read + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun markedAt(): Optional = markedAt.getOptional("marked_at") + + /** + * Read status + * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ fun status(): Optional = status.getOptional("status") /** - * Returns the raw JSON value of [messageId]. + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [markedAt]. * - * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + * Unlike [markedAt], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + @JsonProperty("marked_at") @ExcludeMissing fun _markedAt(): JsonField = markedAt /** * Returns the raw JSON value of [status]. @@ -71,35 +92,49 @@ private constructor( companion object { - /** Returns a mutable builder for constructing an instance of [MessageGetStatusResponse]. */ + /** Returns a mutable builder for constructing an instance of [ChatMarkAsReadResponse]. */ @JvmStatic fun builder() = Builder() } - /** A builder for [MessageGetStatusResponse]. */ + /** A builder for [ChatMarkAsReadResponse]. */ class Builder internal constructor() { - private var messageId: JsonField = JsonMissing.of() + private var chatId: JsonField = JsonMissing.of() + private var markedAt: JsonField = JsonMissing.of() private var status: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic - internal fun from(messageGetStatusResponse: MessageGetStatusResponse) = apply { - messageId = messageGetStatusResponse.messageId - status = messageGetStatusResponse.status - additionalProperties = messageGetStatusResponse.additionalProperties.toMutableMap() + internal fun from(chatMarkAsReadResponse: ChatMarkAsReadResponse) = apply { + chatId = chatMarkAsReadResponse.chatId + markedAt = chatMarkAsReadResponse.markedAt + status = chatMarkAsReadResponse.status + additionalProperties = chatMarkAsReadResponse.additionalProperties.toMutableMap() } - fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + /** Chat identifier */ + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) /** - * Sets [Builder.messageId] to an arbitrary JSON value. + * Sets [Builder.chatId] to an arbitrary JSON value. * - * You should usually call [Builder.messageId] with a well-typed [String] value instead. - * This method is primarily for setting the field to an undocumented or not yet supported - * value. + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. */ - fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + /** Timestamp when marked as read */ + fun markedAt(markedAt: Long) = markedAt(JsonField.of(markedAt)) + /** + * Sets [Builder.markedAt] to an arbitrary JSON value. + * + * You should usually call [Builder.markedAt] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun markedAt(markedAt: JsonField) = apply { this.markedAt = markedAt } + + /** Read status */ fun status(status: Status) = status(JsonField.of(status)) /** @@ -130,22 +165,31 @@ private constructor( } /** - * Returns an immutable instance of [MessageGetStatusResponse]. + * Returns an immutable instance of [ChatMarkAsReadResponse]. * * Further updates to this [Builder] will not mutate the returned instance. */ - fun build(): MessageGetStatusResponse = - MessageGetStatusResponse(messageId, status, additionalProperties.toMutableMap()) + fun build(): ChatMarkAsReadResponse = + ChatMarkAsReadResponse(chatId, markedAt, status, additionalProperties.toMutableMap()) } private var validated: Boolean = false - fun validate(): MessageGetStatusResponse = 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ChatMarkAsReadResponse = apply { if (validated) { return@apply } - messageId() + chatId() + markedAt() status().ifPresent { it.validate() } validated = true } @@ -165,9 +209,11 @@ private constructor( */ @JvmSynthetic internal fun validity(): Int = - (if (messageId.asKnown().isPresent) 1 else 0) + + (if (chatId.asKnown().isPresent) 1 else 0) + + (if (markedAt.asKnown().isPresent) 1 else 0) + (status.asKnown().getOrNull()?.validity() ?: 0) + /** Read status */ class Status @JsonCreator private constructor(private val value: JsonField) : Enum { /** @@ -182,32 +228,14 @@ private constructor( companion object { - @JvmField val PENDING = of("pending") - - @JvmField val QUEUED = of("queued") - - @JvmField val SENT = of("sent") - - @JvmField val DELIVERED = of("delivered") - - @JvmField val FAILED = of("failed") - - @JvmField val CANCELLED = of("cancelled") - - @JvmField val CANCELLATION_REQUESTED = of("cancellation_requested") + @JvmField val READ = of("read") @JvmStatic fun of(value: String) = Status(JsonField.of(value)) } /** An enum containing [Status]'s known values. */ enum class Known { - PENDING, - QUEUED, - SENT, - DELIVERED, - FAILED, - CANCELLED, - CANCELLATION_REQUESTED, + READ } /** @@ -220,13 +248,7 @@ private constructor( * - It was constructed with an arbitrary value using the [of] method. */ enum class Value { - PENDING, - QUEUED, - SENT, - DELIVERED, - FAILED, - CANCELLED, - CANCELLATION_REQUESTED, + READ, /** An enum member indicating that [Status] was instantiated with an unknown value. */ _UNKNOWN, } @@ -240,13 +262,7 @@ private constructor( */ fun value(): Value = when (this) { - PENDING -> Value.PENDING - QUEUED -> Value.QUEUED - SENT -> Value.SENT - DELIVERED -> Value.DELIVERED - FAILED -> Value.FAILED - CANCELLED -> Value.CANCELLED - CANCELLATION_REQUESTED -> Value.CANCELLATION_REQUESTED + READ -> Value.READ else -> Value._UNKNOWN } @@ -261,13 +277,7 @@ private constructor( */ fun known(): Known = when (this) { - PENDING -> Known.PENDING - QUEUED -> Known.QUEUED - SENT -> Known.SENT - DELIVERED -> Known.DELIVERED - FAILED -> Known.FAILED - CANCELLED -> Known.CANCELLED - CANCELLATION_REQUESTED -> Known.CANCELLATION_REQUESTED + READ -> Known.READ else -> throw BlooioInvalidDataException("Unknown Status: $value") } @@ -285,6 +295,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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Status = apply { if (validated) { return@apply @@ -328,16 +347,19 @@ private constructor( return true } - return other is MessageGetStatusResponse && - messageId == other.messageId && + return other is ChatMarkAsReadResponse && + chatId == other.chatId && + markedAt == other.markedAt && status == other.status && additionalProperties == other.additionalProperties } - private val hashCode: Int by lazy { Objects.hash(messageId, status, additionalProperties) } + private val hashCode: Int by lazy { + Objects.hash(chatId, markedAt, status, additionalProperties) + } override fun hashCode(): Int = hashCode override fun toString() = - "MessageGetStatusResponse{messageId=$messageId, status=$status, additionalProperties=$additionalProperties}" + "ChatMarkAsReadResponse{chatId=$chatId, markedAt=$markedAt, status=$status, additionalProperties=$additionalProperties}" } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveParams.kt new file mode 100644 index 0000000..6a1740a --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveParams.kt @@ -0,0 +1,189 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** Get details for a specific conversation. */ +class ChatRetrieveParams +private constructor( + private val chatId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): ChatRetrieveParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [ChatRetrieveParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatRetrieveParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(chatRetrieveParams: ChatRetrieveParams) = apply { + chatId = chatRetrieveParams.chatId + additionalHeaders = chatRetrieveParams.additionalHeaders.toBuilder() + additionalQueryParams = chatRetrieveParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [ChatRetrieveParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatRetrieveParams = + ChatRetrieveParams(chatId, additionalHeaders.build(), additionalQueryParams.build()) + } + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatRetrieveParams && + chatId == other.chatId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = Objects.hash(chatId, additionalHeaders, additionalQueryParams) + + override fun toString() = + "ChatRetrieveParams{chatId=$chatId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveResponse.kt new file mode 100644 index 0000000..6310bf3 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatRetrieveResponse.kt @@ -0,0 +1,1206 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class ChatRetrieveResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val id: JsonField, + private val backgroundId: JsonField, + private val backgroundUrl: JsonField, + private val contact: JsonField, + private val firstMessageTime: JsonField, + private val groupId: JsonField, + private val groupName: JsonField, + private val inboundCount: JsonField, + private val isGroup: JsonField, + private val lastInboundTime: JsonField, + private val lastMessage: JsonField, + private val lastMessageTime: JsonField, + private val lastOutboundTime: JsonField, + private val memberCount: JsonField, + private val messageCount: JsonField, + private val outboundCount: JsonField, + private val type: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("id") @ExcludeMissing id: JsonField = JsonMissing.of(), + @JsonProperty("background_id") + @ExcludeMissing + backgroundId: JsonField = JsonMissing.of(), + @JsonProperty("background_url") + @ExcludeMissing + backgroundUrl: JsonField = JsonMissing.of(), + @JsonProperty("contact") @ExcludeMissing contact: JsonField = JsonMissing.of(), + @JsonProperty("first_message_time") + @ExcludeMissing + firstMessageTime: JsonField = JsonMissing.of(), + @JsonProperty("group_id") @ExcludeMissing groupId: JsonField = JsonMissing.of(), + @JsonProperty("group_name") @ExcludeMissing groupName: JsonField = JsonMissing.of(), + @JsonProperty("inbound_count") + @ExcludeMissing + inboundCount: JsonField = JsonMissing.of(), + @JsonProperty("is_group") @ExcludeMissing isGroup: JsonField = JsonMissing.of(), + @JsonProperty("last_inbound_time") + @ExcludeMissing + lastInboundTime: JsonField = JsonMissing.of(), + @JsonProperty("last_message") + @ExcludeMissing + lastMessage: JsonField = JsonMissing.of(), + @JsonProperty("last_message_time") + @ExcludeMissing + lastMessageTime: JsonField = JsonMissing.of(), + @JsonProperty("last_outbound_time") + @ExcludeMissing + lastOutboundTime: JsonField = JsonMissing.of(), + @JsonProperty("member_count") + @ExcludeMissing + memberCount: JsonField = JsonMissing.of(), + @JsonProperty("message_count") + @ExcludeMissing + messageCount: JsonField = JsonMissing.of(), + @JsonProperty("outbound_count") + @ExcludeMissing + outboundCount: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonField = JsonMissing.of(), + ) : this( + id, + backgroundId, + backgroundUrl, + contact, + firstMessageTime, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + mutableMapOf(), + ) + + /** + * Chat identifier (phone number, email, or group ID) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun id(): Optional = id.getOptional("id") + + /** + * Identifier for the active chat background + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundId(): Optional = backgroundId.getOptional("background_id") + + /** + * Public URL of the chat background image (if one has been set via the API) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundUrl(): Optional = backgroundUrl.getOptional("background_url") + + /** + * Contact info (only for non-group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun contact(): Optional = contact.getOptional("contact") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun firstMessageTime(): Optional = firstMessageTime.getOptional("first_message_time") + + /** + * Group ID (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupId(): Optional = groupId.getOptional("group_id") + + /** + * Group name (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupName(): Optional = groupName.getOptional("group_name") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun inboundCount(): Optional = inboundCount.getOptional("inbound_count") + + /** + * Whether this is a group chat + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun isGroup(): Optional = isGroup.getOptional("is_group") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastInboundTime(): Optional = lastInboundTime.getOptional("last_inbound_time") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastMessage(): Optional = lastMessage.getOptional("last_message") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastMessageTime(): Optional = lastMessageTime.getOptional("last_message_time") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastOutboundTime(): Optional = lastOutboundTime.getOptional("last_outbound_time") + + /** + * Number of members (only for group chats) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun memberCount(): Optional = memberCount.getOptional("member_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageCount(): Optional = messageCount.getOptional("message_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun outboundCount(): Optional = outboundCount.getOptional("outbound_count") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun type(): Optional = type.getOptional("type") + + /** + * Returns the raw JSON value of [id]. + * + * Unlike [id], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("id") @ExcludeMissing fun _id(): JsonField = id + + /** + * Returns the raw JSON value of [backgroundId]. + * + * Unlike [backgroundId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("background_id") + @ExcludeMissing + fun _backgroundId(): JsonField = backgroundId + + /** + * Returns the raw JSON value of [backgroundUrl]. + * + * Unlike [backgroundUrl], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("background_url") + @ExcludeMissing + fun _backgroundUrl(): JsonField = backgroundUrl + + /** + * Returns the raw JSON value of [contact]. + * + * Unlike [contact], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("contact") @ExcludeMissing fun _contact(): JsonField = contact + + /** + * Returns the raw JSON value of [firstMessageTime]. + * + * Unlike [firstMessageTime], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("first_message_time") + @ExcludeMissing + fun _firstMessageTime(): JsonField = firstMessageTime + + /** + * Returns the raw JSON value of [groupId]. + * + * Unlike [groupId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_id") @ExcludeMissing fun _groupId(): JsonField = groupId + + /** + * Returns the raw JSON value of [groupName]. + * + * Unlike [groupName], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_name") @ExcludeMissing fun _groupName(): JsonField = groupName + + /** + * Returns the raw JSON value of [inboundCount]. + * + * Unlike [inboundCount], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("inbound_count") + @ExcludeMissing + fun _inboundCount(): JsonField = inboundCount + + /** + * Returns the raw JSON value of [isGroup]. + * + * Unlike [isGroup], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("is_group") @ExcludeMissing fun _isGroup(): JsonField = isGroup + + /** + * Returns the raw JSON value of [lastInboundTime]. + * + * Unlike [lastInboundTime], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_inbound_time") + @ExcludeMissing + fun _lastInboundTime(): JsonField = lastInboundTime + + /** + * Returns the raw JSON value of [lastMessage]. + * + * Unlike [lastMessage], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_message") + @ExcludeMissing + fun _lastMessage(): JsonField = lastMessage + + /** + * Returns the raw JSON value of [lastMessageTime]. + * + * Unlike [lastMessageTime], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_message_time") + @ExcludeMissing + fun _lastMessageTime(): JsonField = lastMessageTime + + /** + * Returns the raw JSON value of [lastOutboundTime]. + * + * Unlike [lastOutboundTime], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_outbound_time") + @ExcludeMissing + fun _lastOutboundTime(): JsonField = lastOutboundTime + + /** + * Returns the raw JSON value of [memberCount]. + * + * Unlike [memberCount], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("member_count") @ExcludeMissing fun _memberCount(): JsonField = memberCount + + /** + * Returns the raw JSON value of [messageCount]. + * + * Unlike [messageCount], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_count") + @ExcludeMissing + fun _messageCount(): JsonField = messageCount + + /** + * Returns the raw JSON value of [outboundCount]. + * + * Unlike [outboundCount], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("outbound_count") + @ExcludeMissing + fun _outboundCount(): JsonField = outboundCount + + /** + * Returns the raw JSON value of [type]. + * + * Unlike [type], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonField = type + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [ChatRetrieveResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatRetrieveResponse]. */ + class Builder internal constructor() { + + private var id: JsonField = JsonMissing.of() + private var backgroundId: JsonField = JsonMissing.of() + private var backgroundUrl: JsonField = JsonMissing.of() + private var contact: JsonField = JsonMissing.of() + private var firstMessageTime: JsonField = JsonMissing.of() + private var groupId: JsonField = JsonMissing.of() + private var groupName: JsonField = JsonMissing.of() + private var inboundCount: JsonField = JsonMissing.of() + private var isGroup: JsonField = JsonMissing.of() + private var lastInboundTime: JsonField = JsonMissing.of() + private var lastMessage: JsonField = JsonMissing.of() + private var lastMessageTime: JsonField = JsonMissing.of() + private var lastOutboundTime: JsonField = JsonMissing.of() + private var memberCount: JsonField = JsonMissing.of() + private var messageCount: JsonField = JsonMissing.of() + private var outboundCount: JsonField = JsonMissing.of() + private var type: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatRetrieveResponse: ChatRetrieveResponse) = apply { + id = chatRetrieveResponse.id + backgroundId = chatRetrieveResponse.backgroundId + backgroundUrl = chatRetrieveResponse.backgroundUrl + contact = chatRetrieveResponse.contact + firstMessageTime = chatRetrieveResponse.firstMessageTime + groupId = chatRetrieveResponse.groupId + groupName = chatRetrieveResponse.groupName + inboundCount = chatRetrieveResponse.inboundCount + isGroup = chatRetrieveResponse.isGroup + lastInboundTime = chatRetrieveResponse.lastInboundTime + lastMessage = chatRetrieveResponse.lastMessage + lastMessageTime = chatRetrieveResponse.lastMessageTime + lastOutboundTime = chatRetrieveResponse.lastOutboundTime + memberCount = chatRetrieveResponse.memberCount + messageCount = chatRetrieveResponse.messageCount + outboundCount = chatRetrieveResponse.outboundCount + type = chatRetrieveResponse.type + additionalProperties = chatRetrieveResponse.additionalProperties.toMutableMap() + } + + /** Chat identifier (phone number, email, or group ID) */ + fun id(id: String) = id(JsonField.of(id)) + + /** + * Sets [Builder.id] to an arbitrary JSON value. + * + * You should usually call [Builder.id] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun id(id: JsonField) = apply { this.id = id } + + /** Identifier for the active chat background */ + fun backgroundId(backgroundId: String?) = backgroundId(JsonField.ofNullable(backgroundId)) + + /** Alias for calling [Builder.backgroundId] with `backgroundId.orElse(null)`. */ + fun backgroundId(backgroundId: Optional) = backgroundId(backgroundId.getOrNull()) + + /** + * Sets [Builder.backgroundId] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun backgroundId(backgroundId: JsonField) = apply { + this.backgroundId = backgroundId + } + + /** Public URL of the chat background image (if one has been set via the API) */ + fun backgroundUrl(backgroundUrl: String?) = + backgroundUrl(JsonField.ofNullable(backgroundUrl)) + + /** Alias for calling [Builder.backgroundUrl] with `backgroundUrl.orElse(null)`. */ + fun backgroundUrl(backgroundUrl: Optional) = + backgroundUrl(backgroundUrl.getOrNull()) + + /** + * Sets [Builder.backgroundUrl] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundUrl] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun backgroundUrl(backgroundUrl: JsonField) = apply { + this.backgroundUrl = backgroundUrl + } + + /** Contact info (only for non-group chats) */ + fun contact(contact: Contact?) = contact(JsonField.ofNullable(contact)) + + /** Alias for calling [Builder.contact] with `contact.orElse(null)`. */ + fun contact(contact: Optional) = contact(contact.getOrNull()) + + /** + * Sets [Builder.contact] to an arbitrary JSON value. + * + * You should usually call [Builder.contact] with a well-typed [Contact] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun contact(contact: JsonField) = apply { this.contact = contact } + + fun firstMessageTime(firstMessageTime: Long) = + firstMessageTime(JsonField.of(firstMessageTime)) + + /** + * Sets [Builder.firstMessageTime] to an arbitrary JSON value. + * + * You should usually call [Builder.firstMessageTime] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun firstMessageTime(firstMessageTime: JsonField) = apply { + this.firstMessageTime = firstMessageTime + } + + /** Group ID (only for group chats) */ + fun groupId(groupId: String?) = groupId(JsonField.ofNullable(groupId)) + + /** Alias for calling [Builder.groupId] with `groupId.orElse(null)`. */ + fun groupId(groupId: Optional) = groupId(groupId.getOrNull()) + + /** + * Sets [Builder.groupId] to an arbitrary JSON value. + * + * You should usually call [Builder.groupId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun groupId(groupId: JsonField) = apply { this.groupId = groupId } + + /** Group name (only for group chats) */ + fun groupName(groupName: String?) = groupName(JsonField.ofNullable(groupName)) + + /** Alias for calling [Builder.groupName] with `groupName.orElse(null)`. */ + fun groupName(groupName: Optional) = groupName(groupName.getOrNull()) + + /** + * Sets [Builder.groupName] to an arbitrary JSON value. + * + * You should usually call [Builder.groupName] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun groupName(groupName: JsonField) = apply { this.groupName = groupName } + + fun inboundCount(inboundCount: Long) = inboundCount(JsonField.of(inboundCount)) + + /** + * Sets [Builder.inboundCount] to an arbitrary JSON value. + * + * You should usually call [Builder.inboundCount] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun inboundCount(inboundCount: JsonField) = apply { this.inboundCount = inboundCount } + + /** Whether this is a group chat */ + fun isGroup(isGroup: Boolean) = isGroup(JsonField.of(isGroup)) + + /** + * Sets [Builder.isGroup] to an arbitrary JSON value. + * + * You should usually call [Builder.isGroup] with a well-typed [Boolean] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun isGroup(isGroup: JsonField) = apply { this.isGroup = isGroup } + + fun lastInboundTime(lastInboundTime: Long?) = + lastInboundTime(JsonField.ofNullable(lastInboundTime)) + + /** + * Alias for [Builder.lastInboundTime]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun lastInboundTime(lastInboundTime: Long) = lastInboundTime(lastInboundTime as Long?) + + /** Alias for calling [Builder.lastInboundTime] with `lastInboundTime.orElse(null)`. */ + fun lastInboundTime(lastInboundTime: Optional) = + lastInboundTime(lastInboundTime.getOrNull()) + + /** + * Sets [Builder.lastInboundTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastInboundTime] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun lastInboundTime(lastInboundTime: JsonField) = apply { + this.lastInboundTime = lastInboundTime + } + + fun lastMessage(lastMessage: LastMessage) = lastMessage(JsonField.of(lastMessage)) + + /** + * Sets [Builder.lastMessage] to an arbitrary JSON value. + * + * You should usually call [Builder.lastMessage] with a well-typed [LastMessage] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastMessage(lastMessage: JsonField) = apply { + this.lastMessage = lastMessage + } + + fun lastMessageTime(lastMessageTime: Long) = lastMessageTime(JsonField.of(lastMessageTime)) + + /** + * Sets [Builder.lastMessageTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastMessageTime] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun lastMessageTime(lastMessageTime: JsonField) = apply { + this.lastMessageTime = lastMessageTime + } + + fun lastOutboundTime(lastOutboundTime: Long?) = + lastOutboundTime(JsonField.ofNullable(lastOutboundTime)) + + /** + * Alias for [Builder.lastOutboundTime]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun lastOutboundTime(lastOutboundTime: Long) = lastOutboundTime(lastOutboundTime as Long?) + + /** Alias for calling [Builder.lastOutboundTime] with `lastOutboundTime.orElse(null)`. */ + fun lastOutboundTime(lastOutboundTime: Optional) = + lastOutboundTime(lastOutboundTime.getOrNull()) + + /** + * Sets [Builder.lastOutboundTime] to an arbitrary JSON value. + * + * You should usually call [Builder.lastOutboundTime] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastOutboundTime(lastOutboundTime: JsonField) = apply { + this.lastOutboundTime = lastOutboundTime + } + + /** Number of members (only for group chats) */ + fun memberCount(memberCount: Long) = memberCount(JsonField.of(memberCount)) + + /** + * Sets [Builder.memberCount] to an arbitrary JSON value. + * + * You should usually call [Builder.memberCount] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun memberCount(memberCount: JsonField) = apply { this.memberCount = memberCount } + + fun messageCount(messageCount: Long) = messageCount(JsonField.of(messageCount)) + + /** + * Sets [Builder.messageCount] to an arbitrary JSON value. + * + * You should usually call [Builder.messageCount] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageCount(messageCount: JsonField) = apply { this.messageCount = messageCount } + + fun outboundCount(outboundCount: Long) = outboundCount(JsonField.of(outboundCount)) + + /** + * Sets [Builder.outboundCount] to an arbitrary JSON value. + * + * You should usually call [Builder.outboundCount] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun outboundCount(outboundCount: JsonField) = apply { + this.outboundCount = outboundCount + } + + fun type(type: Type) = type(JsonField.of(type)) + + /** + * Sets [Builder.type] to an arbitrary JSON value. + * + * You should usually call [Builder.type] with a well-typed [Type] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun type(type: JsonField) = apply { this.type = type } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ChatRetrieveResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatRetrieveResponse = + ChatRetrieveResponse( + id, + backgroundId, + backgroundUrl, + contact, + firstMessageTime, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ChatRetrieveResponse = apply { + if (validated) { + return@apply + } + + id() + backgroundId() + backgroundUrl() + contact().ifPresent { it.validate() } + firstMessageTime() + groupId() + groupName() + inboundCount() + isGroup() + lastInboundTime() + lastMessage().ifPresent { it.validate() } + lastMessageTime() + lastOutboundTime() + memberCount() + messageCount() + outboundCount() + type().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (id.asKnown().isPresent) 1 else 0) + + (if (backgroundId.asKnown().isPresent) 1 else 0) + + (if (backgroundUrl.asKnown().isPresent) 1 else 0) + + (contact.asKnown().getOrNull()?.validity() ?: 0) + + (if (firstMessageTime.asKnown().isPresent) 1 else 0) + + (if (groupId.asKnown().isPresent) 1 else 0) + + (if (groupName.asKnown().isPresent) 1 else 0) + + (if (inboundCount.asKnown().isPresent) 1 else 0) + + (if (isGroup.asKnown().isPresent) 1 else 0) + + (if (lastInboundTime.asKnown().isPresent) 1 else 0) + + (lastMessage.asKnown().getOrNull()?.validity() ?: 0) + + (if (lastMessageTime.asKnown().isPresent) 1 else 0) + + (if (lastOutboundTime.asKnown().isPresent) 1 else 0) + + (if (memberCount.asKnown().isPresent) 1 else 0) + + (if (messageCount.asKnown().isPresent) 1 else 0) + + (if (outboundCount.asKnown().isPresent) 1 else 0) + + (type.asKnown().getOrNull()?.validity() ?: 0) + + /** Contact info (only for non-group chats) */ + class Contact + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val contactId: JsonField, + private val identifier: JsonField, + private val name: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("contact_id") + @ExcludeMissing + contactId: JsonField = JsonMissing.of(), + @JsonProperty("identifier") + @ExcludeMissing + identifier: JsonField = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + ) : this(contactId, identifier, name, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun contactId(): Optional = contactId.getOptional("contact_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun identifier(): Optional = identifier.getOptional("identifier") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun name(): Optional = name.getOptional("name") + + /** + * Returns the raw JSON value of [contactId]. + * + * Unlike [contactId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("contact_id") @ExcludeMissing fun _contactId(): JsonField = contactId + + /** + * Returns the raw JSON value of [identifier]. + * + * Unlike [identifier], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("identifier") + @ExcludeMissing + fun _identifier(): JsonField = identifier + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Contact]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Contact]. */ + class Builder internal constructor() { + + private var contactId: JsonField = JsonMissing.of() + private var identifier: JsonField = JsonMissing.of() + private var name: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(contact: Contact) = apply { + contactId = contact.contactId + identifier = contact.identifier + name = contact.name + additionalProperties = contact.additionalProperties.toMutableMap() + } + + fun contactId(contactId: String) = contactId(JsonField.of(contactId)) + + /** + * Sets [Builder.contactId] to an arbitrary JSON value. + * + * You should usually call [Builder.contactId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun contactId(contactId: JsonField) = apply { this.contactId = contactId } + + fun identifier(identifier: String) = identifier(JsonField.of(identifier)) + + /** + * Sets [Builder.identifier] to an arbitrary JSON value. + * + * You should usually call [Builder.identifier] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun identifier(identifier: JsonField) = apply { this.identifier = identifier } + + fun name(name: String?) = name(JsonField.ofNullable(name)) + + /** Alias for calling [Builder.name] with `name.orElse(null)`. */ + fun name(name: Optional) = name(name.getOrNull()) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun name(name: JsonField) = apply { this.name = name } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Contact]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Contact = + Contact(contactId, identifier, name, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Contact = apply { + if (validated) { + return@apply + } + + contactId() + identifier() + name() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (contactId.asKnown().isPresent) 1 else 0) + + (if (identifier.asKnown().isPresent) 1 else 0) + + (if (name.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Contact && + contactId == other.contactId && + identifier == other.identifier && + name == other.name && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(contactId, identifier, name, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Contact{contactId=$contactId, identifier=$identifier, name=$name, additionalProperties=$additionalProperties}" + } + + class Type @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PHONE = of("phone") + + @JvmField val EMAIL = of("email") + + @JvmField val GROUP = of("group") + + @JvmStatic fun of(value: String) = Type(JsonField.of(value)) + } + + /** An enum containing [Type]'s known values. */ + enum class Known { + PHONE, + EMAIL, + GROUP, + } + + /** + * An enum containing [Type]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Type] 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 { + PHONE, + EMAIL, + GROUP, + /** An enum member indicating that [Type] 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) { + PHONE -> Value.PHONE + EMAIL -> Value.EMAIL + GROUP -> Value.GROUP + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PHONE -> Known.PHONE + EMAIL -> Known.EMAIL + GROUP -> Known.GROUP + else -> throw BlooioInvalidDataException("Unknown Type: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Type = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Type && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatRetrieveResponse && + id == other.id && + backgroundId == other.backgroundId && + backgroundUrl == other.backgroundUrl && + contact == other.contact && + firstMessageTime == other.firstMessageTime && + groupId == other.groupId && + groupName == other.groupName && + inboundCount == other.inboundCount && + isGroup == other.isGroup && + lastInboundTime == other.lastInboundTime && + lastMessage == other.lastMessage && + lastMessageTime == other.lastMessageTime && + lastOutboundTime == other.lastOutboundTime && + memberCount == other.memberCount && + messageCount == other.messageCount && + outboundCount == other.outboundCount && + type == other.type && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + id, + backgroundId, + backgroundUrl, + contact, + firstMessageTime, + groupId, + groupName, + inboundCount, + isGroup, + lastInboundTime, + lastMessage, + lastMessageTime, + lastOutboundTime, + memberCount, + messageCount, + outboundCount, + type, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ChatRetrieveResponse{id=$id, backgroundId=$backgroundId, backgroundUrl=$backgroundUrl, contact=$contact, firstMessageTime=$firstMessageTime, groupId=$groupId, groupName=$groupName, inboundCount=$inboundCount, isGroup=$isGroup, lastInboundTime=$lastInboundTime, lastMessage=$lastMessage, lastMessageTime=$lastMessageTime, lastOutboundTime=$lastOutboundTime, memberCount=$memberCount, messageCount=$messageCount, outboundCount=$outboundCount, type=$type, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardParams.kt new file mode 100644 index 0000000..fb78c13 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardParams.kt @@ -0,0 +1,239 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.JsonValue +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.core.toImmutable +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Stage the contact card (Name & Photo) for sharing in a chat. The contact card will be piggybacked + * onto the next outgoing message (text or attachment) sent to this chat. This is idempotent — + * calling it multiple times is harmless. + * + * ⚠️ **Plan requirement:** Contact card sharing is only available on **Dedicated Commercial** and + * **Dedicated Enterprise** plans. Numbers on other plans receive a `403`. + */ +class ChatShareContactCardParams +private constructor( + private val chatId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** Additional body properties to send with the request. */ + fun _additionalBodyProperties(): Map = additionalBodyProperties + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): ChatShareContactCardParams = builder().build() + + /** + * Returns a mutable builder for constructing an instance of [ChatShareContactCardParams]. + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatShareContactCardParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatShareContactCardParams: ChatShareContactCardParams) = apply { + chatId = chatShareContactCardParams.chatId + additionalHeaders = chatShareContactCardParams.additionalHeaders.toBuilder() + additionalQueryParams = chatShareContactCardParams.additionalQueryParams.toBuilder() + additionalBodyProperties = + chatShareContactCardParams.additionalBodyProperties.toMutableMap() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + putAllAdditionalBodyProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { + additionalBodyProperties.remove(key) + } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalBodyProperty) + } + + /** + * Returns an immutable instance of [ChatShareContactCardParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatShareContactCardParams = + ChatShareContactCardParams( + chatId, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatShareContactCardParams && + chatId == other.chatId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash(chatId, additionalHeaders, additionalQueryParams, additionalBodyProperties) + + override fun toString() = + "ChatShareContactCardParams{chatId=$chatId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardResponse.kt new file mode 100644 index 0000000..794f529 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/ChatShareContactCardResponse.kt @@ -0,0 +1,235 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional + +class ChatShareContactCardResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val chatId: JsonField, + private val message: JsonField, + private val success: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("message") @ExcludeMissing message: JsonField = JsonMissing.of(), + @JsonProperty("success") @ExcludeMissing success: JsonField = JsonMissing.of(), + ) : this(chatId, message, success, mutableMapOf()) + + /** + * Normalized chat identifier + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chatId(): Optional = chatId.getOptional("chat_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun message(): Optional = message.getOptional("message") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun success(): Optional = success.getOptional("success") + + /** + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [message]. + * + * Unlike [message], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message") @ExcludeMissing fun _message(): JsonField = message + + /** + * Returns the raw JSON value of [success]. + * + * Unlike [success], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("success") @ExcludeMissing fun _success(): JsonField = success + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [ChatShareContactCardResponse]. + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatShareContactCardResponse]. */ + class Builder internal constructor() { + + private var chatId: JsonField = JsonMissing.of() + private var message: JsonField = JsonMissing.of() + private var success: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatShareContactCardResponse: ChatShareContactCardResponse) = apply { + chatId = chatShareContactCardResponse.chatId + message = chatShareContactCardResponse.message + success = chatShareContactCardResponse.success + additionalProperties = chatShareContactCardResponse.additionalProperties.toMutableMap() + } + + /** Normalized chat identifier */ + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) + + /** + * Sets [Builder.chatId] to an arbitrary JSON value. + * + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + fun message(message: String) = message(JsonField.of(message)) + + /** + * Sets [Builder.message] to an arbitrary JSON value. + * + * You should usually call [Builder.message] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun message(message: JsonField) = apply { this.message = message } + + fun success(success: Boolean) = success(JsonField.of(success)) + + /** + * Sets [Builder.success] to an arbitrary JSON value. + * + * You should usually call [Builder.success] with a well-typed [Boolean] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun success(success: JsonField) = apply { this.success = success } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ChatShareContactCardResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatShareContactCardResponse = + ChatShareContactCardResponse( + chatId, + message, + success, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ChatShareContactCardResponse = apply { + if (validated) { + return@apply + } + + chatId() + message() + success() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (chatId.asKnown().isPresent) 1 else 0) + + (if (message.asKnown().isPresent) 1 else 0) + + (if (success.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatShareContactCardResponse && + chatId == other.chatId && + message == other.message && + success == other.success && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(chatId, message, success, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ChatShareContactCardResponse{chatId=$chatId, message=$message, success=$success, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/LastMessage.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/LastMessage.kt new file mode 100644 index 0000000..e0f63a5 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/LastMessage.kt @@ -0,0 +1,400 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class LastMessage +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val direction: JsonField, + private val messageId: JsonField, + private val text: JsonField, + private val timeSent: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("direction") + @ExcludeMissing + direction: JsonField = JsonMissing.of(), + @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), + @JsonProperty("text") @ExcludeMissing text: JsonField = JsonMissing.of(), + @JsonProperty("time_sent") @ExcludeMissing timeSent: JsonField = JsonMissing.of(), + ) : this(direction, messageId, text, timeSent, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun direction(): Optional = direction.getOptional("direction") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = text.getOptional("text") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeSent(): Optional = timeSent.getOptional("time_sent") + + /** + * Returns the raw JSON value of [direction]. + * + * Unlike [direction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("direction") @ExcludeMissing fun _direction(): JsonField = direction + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("text") @ExcludeMissing fun _text(): JsonField = text + + /** + * Returns the raw JSON value of [timeSent]. + * + * Unlike [timeSent], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("time_sent") @ExcludeMissing fun _timeSent(): JsonField = timeSent + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [LastMessage]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [LastMessage]. */ + class Builder internal constructor() { + + private var direction: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var text: JsonField = JsonMissing.of() + private var timeSent: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(lastMessage: LastMessage) = apply { + direction = lastMessage.direction + messageId = lastMessage.messageId + text = lastMessage.text + timeSent = lastMessage.timeSent + additionalProperties = lastMessage.additionalProperties.toMutableMap() + } + + fun direction(direction: Direction) = direction(JsonField.of(direction)) + + /** + * Sets [Builder.direction] to an arbitrary JSON value. + * + * You should usually call [Builder.direction] with a well-typed [Direction] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun direction(direction: JsonField) = apply { this.direction = direction } + + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + fun text(text: String?) = text(JsonField.ofNullable(text)) + + /** Alias for calling [Builder.text] with `text.orElse(null)`. */ + fun text(text: Optional) = text(text.getOrNull()) + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun text(text: JsonField) = apply { this.text = text } + + fun timeSent(timeSent: Long) = timeSent(JsonField.of(timeSent)) + + /** + * Sets [Builder.timeSent] to an arbitrary JSON value. + * + * You should usually call [Builder.timeSent] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun timeSent(timeSent: JsonField) = apply { this.timeSent = timeSent } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [LastMessage]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): LastMessage = + LastMessage(direction, messageId, text, timeSent, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): LastMessage = apply { + if (validated) { + return@apply + } + + direction().ifPresent { it.validate() } + messageId() + text() + timeSent() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (direction.asKnown().getOrNull()?.validity() ?: 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (if (text.asKnown().isPresent) 1 else 0) + + (if (timeSent.asKnown().isPresent) 1 else 0) + + class Direction @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val INBOUND = of("inbound") + + @JvmField val OUTBOUND = of("outbound") + + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) + } + + /** An enum containing [Direction]'s known values. */ + enum class Known { + INBOUND, + OUTBOUND, + } + + /** + * An enum containing [Direction]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Direction] 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 { + INBOUND, + OUTBOUND, + /** + * An enum member indicating that [Direction] 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) { + INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND + else -> throw BlooioInvalidDataException("Unknown Direction: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Direction = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Direction && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is LastMessage && + direction == other.direction && + messageId == other.messageId && + text == other.text && + timeSent == other.timeSent && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(direction, messageId, text, timeSent, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "LastMessage{direction=$direction, messageId=$messageId, text=$text, timeSent=$timeSent, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRemoveParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRemoveParams.kt new file mode 100644 index 0000000..eceee5b --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRemoveParams.kt @@ -0,0 +1,230 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.background + +import com.blooio.api.core.JsonValue +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.core.toImmutable +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** Remove the background image from a conversation, reverting to the default appearance. */ +class BackgroundRemoveParams +private constructor( + private val chatId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** Additional body properties to send with the request. */ + fun _additionalBodyProperties(): Map = additionalBodyProperties + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): BackgroundRemoveParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [BackgroundRemoveParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BackgroundRemoveParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(backgroundRemoveParams: BackgroundRemoveParams) = apply { + chatId = backgroundRemoveParams.chatId + additionalHeaders = backgroundRemoveParams.additionalHeaders.toBuilder() + additionalQueryParams = backgroundRemoveParams.additionalQueryParams.toBuilder() + additionalBodyProperties = + backgroundRemoveParams.additionalBodyProperties.toMutableMap() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + putAllAdditionalBodyProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { + additionalBodyProperties.remove(key) + } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalBodyProperty) + } + + /** + * Returns an immutable instance of [BackgroundRemoveParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): BackgroundRemoveParams = + BackgroundRemoveParams( + chatId, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is BackgroundRemoveParams && + chatId == other.chatId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash(chatId, additionalHeaders, additionalQueryParams, additionalBodyProperties) + + override fun toString() = + "BackgroundRemoveParams{chatId=$chatId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/batches/BatchListMessagesParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRetrieveParams.kt similarity index 77% rename from blooio-java-core/src/main/kotlin/com/blooio/api/models/batches/BatchListMessagesParams.kt rename to blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRetrieveParams.kt index 2ff3330..6604efb 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/models/batches/BatchListMessagesParams.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundRetrieveParams.kt @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. -package com.blooio.api.models.batches +package com.blooio.api.models.chats.background import com.blooio.api.core.Params import com.blooio.api.core.http.Headers @@ -9,15 +9,18 @@ import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull -/** List messages in a batch (stub) */ -class BatchListMessagesParams +/** + * Get the current background image metadata for a conversation. Works for both 1-on-1 and group + * chats. + */ +class BackgroundRetrieveParams private constructor( - private val batchId: String?, + private val chatId: String?, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { - fun batchId(): Optional = Optional.ofNullable(batchId) + fun chatId(): Optional = Optional.ofNullable(chatId) /** Additional headers to send with the request. */ fun _additionalHeaders(): Headers = additionalHeaders @@ -29,30 +32,30 @@ private constructor( companion object { - @JvmStatic fun none(): BatchListMessagesParams = builder().build() + @JvmStatic fun none(): BackgroundRetrieveParams = builder().build() - /** Returns a mutable builder for constructing an instance of [BatchListMessagesParams]. */ + /** Returns a mutable builder for constructing an instance of [BackgroundRetrieveParams]. */ @JvmStatic fun builder() = Builder() } - /** A builder for [BatchListMessagesParams]. */ + /** A builder for [BackgroundRetrieveParams]. */ class Builder internal constructor() { - private var batchId: String? = null + private var chatId: String? = null private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() @JvmSynthetic - internal fun from(batchListMessagesParams: BatchListMessagesParams) = apply { - batchId = batchListMessagesParams.batchId - additionalHeaders = batchListMessagesParams.additionalHeaders.toBuilder() - additionalQueryParams = batchListMessagesParams.additionalQueryParams.toBuilder() + internal fun from(backgroundRetrieveParams: BackgroundRetrieveParams) = apply { + chatId = backgroundRetrieveParams.chatId + additionalHeaders = backgroundRetrieveParams.additionalHeaders.toBuilder() + additionalQueryParams = backgroundRetrieveParams.additionalQueryParams.toBuilder() } - fun batchId(batchId: String?) = apply { this.batchId = batchId } + fun chatId(chatId: String?) = apply { this.chatId = chatId } - /** Alias for calling [Builder.batchId] with `batchId.orElse(null)`. */ - fun batchId(batchId: Optional) = batchId(batchId.getOrNull()) + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) fun additionalHeaders(additionalHeaders: Headers) = apply { this.additionalHeaders.clear() @@ -153,13 +156,13 @@ private constructor( } /** - * Returns an immutable instance of [BatchListMessagesParams]. + * Returns an immutable instance of [BackgroundRetrieveParams]. * * Further updates to this [Builder] will not mutate the returned instance. */ - fun build(): BatchListMessagesParams = - BatchListMessagesParams( - batchId, + fun build(): BackgroundRetrieveParams = + BackgroundRetrieveParams( + chatId, additionalHeaders.build(), additionalQueryParams.build(), ) @@ -167,7 +170,7 @@ private constructor( fun _pathParam(index: Int): String = when (index) { - 0 -> batchId ?: "" + 0 -> chatId ?: "" else -> "" } @@ -180,14 +183,14 @@ private constructor( return true } - return other is BatchListMessagesParams && - batchId == other.batchId && + return other is BackgroundRetrieveParams && + chatId == other.chatId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams } - override fun hashCode(): Int = Objects.hash(batchId, additionalHeaders, additionalQueryParams) + override fun hashCode(): Int = Objects.hash(chatId, additionalHeaders, additionalQueryParams) override fun toString() = - "BatchListMessagesParams{batchId=$batchId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "BackgroundRetrieveParams{chatId=$chatId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundSetParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundSetParams.kt new file mode 100644 index 0000000..5f3fafe --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/BackgroundSetParams.kt @@ -0,0 +1,509 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.background + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.MultipartField +import com.blooio.api.core.Params +import com.blooio.api.core.checkRequired +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonProperty +import java.io.InputStream +import java.nio.file.Path +import java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.io.path.inputStream +import kotlin.io.path.name +import kotlin.jvm.optionals.getOrNull + +/** + * Set or update the background image for a conversation. Works for both 1-on-1 and group chats. + * + * The request body must be `multipart/form-data` with a single `background` field containing the + * **raw image file bytes** (not a URL or base64 string). Supported formats: JPEG, PNG, GIF, WebP, + * HEIC/HEIF. Maximum file size: 10 MB. + * + * **Example with curl** — note the `@` prefix that tells curl to read the file from disk: + * ```bash + * curl -X PUT "https://api.blooio.com/v2/api/chats/%2B15551234567/background" \ + * -H "Authorization: Bearer YOUR_API_KEY" \ + * -F "background=@/path/to/image.jpg;type=image/jpeg" + * ``` + * + * When the chat id is a phone number, percent-encode the leading `+` as `%2B` in the URL path. + */ +class BackgroundSetParams +private constructor( + private val chatId: String?, + private val body: Body, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file field + * in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with curl, or a + * `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or base64 string. + * + * @throws BlooioInvalidDataException 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 background(): InputStream = body.background() + + /** + * Returns the raw multipart value of [background]. + * + * Unlike [background], this method doesn't throw if the multipart field has an unexpected type. + */ + fun _background(): MultipartField = body._background() + + fun _additionalBodyProperties(): Map = body._additionalProperties() + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [BackgroundSetParams]. + * + * The following fields are required: + * ```java + * .background() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BackgroundSetParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var body: Body.Builder = Body.builder() + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(backgroundSetParams: BackgroundSetParams) = apply { + chatId = backgroundSetParams.chatId + body = backgroundSetParams.body.toBuilder() + additionalHeaders = backgroundSetParams.additionalHeaders.toBuilder() + additionalQueryParams = backgroundSetParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + /** + * Sets the entire request body. + * + * This is generally only useful if you are already constructing the body separately. + * Otherwise, it's more convenient to use the top-level setters instead: + * - [background] + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with curl, or + * a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or base64 string. + */ + fun background(background: InputStream) = apply { body.background(background) } + + /** + * Sets [Builder.background] to an arbitrary multipart value. + * + * You should usually call [Builder.background] with a well-typed [InputStream] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun background(background: MultipartField) = apply { + body.background(background) + } + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with curl, or + * a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or base64 string. + */ + fun background(background: ByteArray) = apply { body.background(background) } + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with curl, or + * a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or base64 string. + */ + fun background(path: Path) = apply { body.background(path) } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + body.additionalProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + body.putAdditionalProperty(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + body.putAllAdditionalProperties(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { body.removeAdditionalProperty(key) } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + body.removeAllAdditionalProperties(keys) + } + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [BackgroundSetParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .background() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): BackgroundSetParams = + BackgroundSetParams( + chatId, + body.build(), + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _body(): Map> = + (mapOf("background" to _background()) + + _additionalBodyProperties().mapValues { (_, value) -> MultipartField.of(value) }) + .toImmutable() + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + class Body + private constructor( + private val background: MultipartField, + private val additionalProperties: MutableMap, + ) { + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with curl, or + * a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or base64 string. + * + * @throws BlooioInvalidDataException 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 background(): InputStream = background.value.getRequired("background") + + /** + * Returns the raw multipart value of [background]. + * + * Unlike [background], this method doesn't throw if the multipart field has an unexpected + * type. + */ + @JsonProperty("background") + @ExcludeMissing + fun _background(): MultipartField = background + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [Body]. + * + * The following fields are required: + * ```java + * .background() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var background: MultipartField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + background = body.background + additionalProperties = body.additionalProperties.toMutableMap() + } + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with + * curl, or a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or + * base64 string. + */ + fun background(background: InputStream) = background(MultipartField.of(background)) + + /** + * Sets [Builder.background] to an arbitrary multipart value. + * + * You should usually call [Builder.background] with a well-typed [InputStream] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun background(background: MultipartField) = apply { + this.background = background + } + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with + * curl, or a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or + * base64 string. + */ + fun background(background: ByteArray) = background(background.inputStream()) + + /** + * Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a file + * field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` with + * curl, or a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send a URL or + * base64 string. + */ + fun background(path: Path) = + background( + MultipartField.builder() + .value(path.inputStream()) + .filename(path.name) + .build() + ) + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Body]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .background() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): Body = + Body(checkRequired("background", background), 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Body = apply { + if (validated) { + return@apply + } + + background() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Body && + background == other.background && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(background, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{background=$background, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is BackgroundSetParams && + chatId == other.chatId && + body == other.body && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(chatId, body, additionalHeaders, additionalQueryParams) + + override fun toString() = + "BackgroundSetParams{chatId=$chatId, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/ChatBackgroundResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/ChatBackgroundResponse.kt new file mode 100644 index 0000000..2afde6d --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/background/ChatBackgroundResponse.kt @@ -0,0 +1,415 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.background + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** Response for chat background operations */ +class ChatBackgroundResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val backgroundId: JsonField, + private val backgroundUrl: JsonField, + private val backgroundVersion: JsonField, + private val changed: JsonField, + private val chatId: JsonField, + private val hasBackground: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("background_id") + @ExcludeMissing + backgroundId: JsonField = JsonMissing.of(), + @JsonProperty("background_url") + @ExcludeMissing + backgroundUrl: JsonField = JsonMissing.of(), + @JsonProperty("background_version") + @ExcludeMissing + backgroundVersion: JsonField = JsonMissing.of(), + @JsonProperty("changed") @ExcludeMissing changed: JsonField = JsonMissing.of(), + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("has_background") + @ExcludeMissing + hasBackground: JsonField = JsonMissing.of(), + ) : this( + backgroundId, + backgroundUrl, + backgroundVersion, + changed, + chatId, + hasBackground, + mutableMapOf(), + ) + + /** + * Unique identifier for the current background, or null if none + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundId(): Optional = backgroundId.getOptional("background_id") + + /** + * Public URL of the persisted background image stored in R2. Returned after a successful PUT + * and on GET when a background has been set through the API. May be null if persistence failed + * or the background was set outside of the API. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundUrl(): Optional = backgroundUrl.getOptional("background_url") + + /** + * Version number of the background (for cache invalidation) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun backgroundVersion(): Optional = backgroundVersion.getOptional("background_version") + + /** + * Whether the background was changed by this operation (only present on PUT) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun changed(): Optional = changed.getOptional("changed") + + /** + * Normalized chat identifier (phone number, email, or group ID) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chatId(): Optional = chatId.getOptional("chat_id") + + /** + * Whether the chat currently has a background set + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun hasBackground(): Optional = hasBackground.getOptional("has_background") + + /** + * Returns the raw JSON value of [backgroundId]. + * + * Unlike [backgroundId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("background_id") + @ExcludeMissing + fun _backgroundId(): JsonField = backgroundId + + /** + * Returns the raw JSON value of [backgroundUrl]. + * + * Unlike [backgroundUrl], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("background_url") + @ExcludeMissing + fun _backgroundUrl(): JsonField = backgroundUrl + + /** + * Returns the raw JSON value of [backgroundVersion]. + * + * Unlike [backgroundVersion], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("background_version") + @ExcludeMissing + fun _backgroundVersion(): JsonField = backgroundVersion + + /** + * Returns the raw JSON value of [changed]. + * + * Unlike [changed], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("changed") @ExcludeMissing fun _changed(): JsonField = changed + + /** + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [hasBackground]. + * + * Unlike [hasBackground], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("has_background") + @ExcludeMissing + fun _hasBackground(): JsonField = hasBackground + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [ChatBackgroundResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ChatBackgroundResponse]. */ + class Builder internal constructor() { + + private var backgroundId: JsonField = JsonMissing.of() + private var backgroundUrl: JsonField = JsonMissing.of() + private var backgroundVersion: JsonField = JsonMissing.of() + private var changed: JsonField = JsonMissing.of() + private var chatId: JsonField = JsonMissing.of() + private var hasBackground: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(chatBackgroundResponse: ChatBackgroundResponse) = apply { + backgroundId = chatBackgroundResponse.backgroundId + backgroundUrl = chatBackgroundResponse.backgroundUrl + backgroundVersion = chatBackgroundResponse.backgroundVersion + changed = chatBackgroundResponse.changed + chatId = chatBackgroundResponse.chatId + hasBackground = chatBackgroundResponse.hasBackground + additionalProperties = chatBackgroundResponse.additionalProperties.toMutableMap() + } + + /** Unique identifier for the current background, or null if none */ + fun backgroundId(backgroundId: String?) = backgroundId(JsonField.ofNullable(backgroundId)) + + /** Alias for calling [Builder.backgroundId] with `backgroundId.orElse(null)`. */ + fun backgroundId(backgroundId: Optional) = backgroundId(backgroundId.getOrNull()) + + /** + * Sets [Builder.backgroundId] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun backgroundId(backgroundId: JsonField) = apply { + this.backgroundId = backgroundId + } + + /** + * Public URL of the persisted background image stored in R2. Returned after a successful + * PUT and on GET when a background has been set through the API. May be null if persistence + * failed or the background was set outside of the API. + */ + fun backgroundUrl(backgroundUrl: String?) = + backgroundUrl(JsonField.ofNullable(backgroundUrl)) + + /** Alias for calling [Builder.backgroundUrl] with `backgroundUrl.orElse(null)`. */ + fun backgroundUrl(backgroundUrl: Optional) = + backgroundUrl(backgroundUrl.getOrNull()) + + /** + * Sets [Builder.backgroundUrl] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundUrl] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun backgroundUrl(backgroundUrl: JsonField) = apply { + this.backgroundUrl = backgroundUrl + } + + /** Version number of the background (for cache invalidation) */ + fun backgroundVersion(backgroundVersion: Long?) = + backgroundVersion(JsonField.ofNullable(backgroundVersion)) + + /** + * Alias for [Builder.backgroundVersion]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun backgroundVersion(backgroundVersion: Long) = + backgroundVersion(backgroundVersion as Long?) + + /** Alias for calling [Builder.backgroundVersion] with `backgroundVersion.orElse(null)`. */ + fun backgroundVersion(backgroundVersion: Optional) = + backgroundVersion(backgroundVersion.getOrNull()) + + /** + * Sets [Builder.backgroundVersion] to an arbitrary JSON value. + * + * You should usually call [Builder.backgroundVersion] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun backgroundVersion(backgroundVersion: JsonField) = apply { + this.backgroundVersion = backgroundVersion + } + + /** Whether the background was changed by this operation (only present on PUT) */ + fun changed(changed: Boolean) = changed(JsonField.of(changed)) + + /** + * Sets [Builder.changed] to an arbitrary JSON value. + * + * You should usually call [Builder.changed] with a well-typed [Boolean] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun changed(changed: JsonField) = apply { this.changed = changed } + + /** Normalized chat identifier (phone number, email, or group ID) */ + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) + + /** + * Sets [Builder.chatId] to an arbitrary JSON value. + * + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + /** Whether the chat currently has a background set */ + fun hasBackground(hasBackground: Boolean) = hasBackground(JsonField.of(hasBackground)) + + /** + * Sets [Builder.hasBackground] to an arbitrary JSON value. + * + * You should usually call [Builder.hasBackground] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun hasBackground(hasBackground: JsonField) = apply { + this.hasBackground = hasBackground + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ChatBackgroundResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ChatBackgroundResponse = + ChatBackgroundResponse( + backgroundId, + backgroundUrl, + backgroundVersion, + changed, + chatId, + hasBackground, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ChatBackgroundResponse = apply { + if (validated) { + return@apply + } + + backgroundId() + backgroundUrl() + backgroundVersion() + changed() + chatId() + hasBackground() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (backgroundId.asKnown().isPresent) 1 else 0) + + (if (backgroundUrl.asKnown().isPresent) 1 else 0) + + (if (backgroundVersion.asKnown().isPresent) 1 else 0) + + (if (changed.asKnown().isPresent) 1 else 0) + + (if (chatId.asKnown().isPresent) 1 else 0) + + (if (hasBackground.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChatBackgroundResponse && + backgroundId == other.backgroundId && + backgroundUrl == other.backgroundUrl && + backgroundVersion == other.backgroundVersion && + changed == other.changed && + chatId == other.chatId && + hasBackground == other.hasBackground && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + backgroundId, + backgroundUrl, + backgroundVersion, + changed, + chatId, + hasBackground, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ChatBackgroundResponse{backgroundId=$backgroundId, backgroundUrl=$backgroundUrl, backgroundVersion=$backgroundVersion, changed=$changed, chatId=$chatId, hasBackground=$hasBackground, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/LinkPreview.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/LinkPreview.kt new file mode 100644 index 0000000..98a3e2c --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/LinkPreview.kt @@ -0,0 +1,214 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional + +/** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are optional. + * Only applies when the message text (or the concatenated part text) is exactly a single http(s) + * URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open Graph metadata to + * generate a preview. If the image download fails, the send still succeeds — Blooio silently falls + * back to the auto-generated preview. + */ +class LinkPreview +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val imageUrl: JsonField, + private val title: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("image_url") @ExcludeMissing imageUrl: JsonField = JsonMissing.of(), + @JsonProperty("title") @ExcludeMissing title: JsonField = JsonMissing.of(), + ) : this(imageUrl, title, mutableMapOf()) + + /** + * HTTPS URL to an image (png, jpg, webp, gif). Blooio downloads the image server-side and + * attaches it as the rich-link hero. Max 16 MB. If the download fails or returns a non-image + * MIME, the send falls back to auto-fetched OG metadata. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun imageUrl(): Optional = imageUrl.getOptional("image_url") + + /** + * Bold title line rendered in the iMessage bubble. Overrides the page's ``. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun title(): Optional = title.getOptional("title") + + /** + * Returns the raw JSON value of [imageUrl]. + * + * Unlike [imageUrl], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("image_url") @ExcludeMissing fun _imageUrl(): JsonField = imageUrl + + /** + * Returns the raw JSON value of [title]. + * + * Unlike [title], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("title") @ExcludeMissing fun _title(): JsonField = title + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [LinkPreview]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [LinkPreview]. */ + class Builder internal constructor() { + + private var imageUrl: JsonField = JsonMissing.of() + private var title: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(linkPreview: LinkPreview) = apply { + imageUrl = linkPreview.imageUrl + title = linkPreview.title + additionalProperties = linkPreview.additionalProperties.toMutableMap() + } + + /** + * HTTPS URL to an image (png, jpg, webp, gif). Blooio downloads the image server-side and + * attaches it as the rich-link hero. Max 16 MB. If the download fails or returns a + * non-image MIME, the send falls back to auto-fetched OG metadata. + */ + fun imageUrl(imageUrl: String) = imageUrl(JsonField.of(imageUrl)) + + /** + * Sets [Builder.imageUrl] to an arbitrary JSON value. + * + * You should usually call [Builder.imageUrl] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun imageUrl(imageUrl: JsonField) = apply { this.imageUrl = imageUrl } + + /** + * Bold title line rendered in the iMessage bubble. Overrides the page's ``. + */ + fun title(title: String) = title(JsonField.of(title)) + + /** + * Sets [Builder.title] to an arbitrary JSON value. + * + * You should usually call [Builder.title] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun title(title: JsonField) = apply { this.title = title } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [LinkPreview]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): LinkPreview = LinkPreview(imageUrl, title, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): LinkPreview = apply { + if (validated) { + return@apply + } + + imageUrl() + title() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (imageUrl.asKnown().isPresent) 1 else 0) + (if (title.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is LinkPreview && + imageUrl == other.imageUrl && + title == other.title && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(imageUrl, title, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "LinkPreview{imageUrl=$imageUrl, title=$title, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusParams.kt similarity index 84% rename from blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusParams.kt rename to blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusParams.kt index bf14e64..0e18b4c 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageGetStatusParams.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusParams.kt @@ -1,22 +1,26 @@ // File generated from our OpenAPI spec by Stainless. -package com.blooio.api.models.messages +package com.blooio.api.models.chats.messages import com.blooio.api.core.Params +import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers import com.blooio.api.core.http.QueryParams import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull -/** Get current delivery status only */ +/** Get delivery status for a specific message. */ class MessageGetStatusParams private constructor( + private val chatId: String, private val messageId: String?, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { + fun chatId(): String = chatId + fun messageId(): Optional = Optional.ofNullable(messageId) /** Additional headers to send with the request. */ @@ -29,26 +33,35 @@ private constructor( companion object { - @JvmStatic fun none(): MessageGetStatusParams = builder().build() - - /** Returns a mutable builder for constructing an instance of [MessageGetStatusParams]. */ + /** + * Returns a mutable builder for constructing an instance of [MessageGetStatusParams]. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + */ @JvmStatic fun builder() = Builder() } /** A builder for [MessageGetStatusParams]. */ class Builder internal constructor() { + private var chatId: String? = null private var messageId: String? = null private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() @JvmSynthetic internal fun from(messageGetStatusParams: MessageGetStatusParams) = apply { + chatId = messageGetStatusParams.chatId messageId = messageGetStatusParams.messageId additionalHeaders = messageGetStatusParams.additionalHeaders.toBuilder() additionalQueryParams = messageGetStatusParams.additionalQueryParams.toBuilder() } + fun chatId(chatId: String) = apply { this.chatId = chatId } + fun messageId(messageId: String?) = apply { this.messageId = messageId } /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ @@ -156,9 +169,17 @@ private constructor( * Returns an immutable instance of [MessageGetStatusParams]. * * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + * + * @throws IllegalStateException if any required field is unset. */ fun build(): MessageGetStatusParams = MessageGetStatusParams( + checkRequired("chatId", chatId), messageId, additionalHeaders.build(), additionalQueryParams.build(), @@ -167,7 +188,8 @@ private constructor( fun _pathParam(index: Int): String = when (index) { - 0 -> messageId ?: "" + 0 -> chatId + 1 -> messageId ?: "" else -> "" } @@ -181,13 +203,15 @@ private constructor( } return other is MessageGetStatusParams && + chatId == other.chatId && messageId == other.messageId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams } - override fun hashCode(): Int = Objects.hash(messageId, additionalHeaders, additionalQueryParams) + override fun hashCode(): Int = + Objects.hash(chatId, messageId, additionalHeaders, additionalQueryParams) override fun toString() = - "MessageGetStatusParams{messageId=$messageId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "MessageGetStatusParams{chatId=$chatId, messageId=$messageId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusResponse.kt similarity index 57% rename from blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveResponse.kt rename to blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusResponse.kt index 38a65f1..9e1d646 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveResponse.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageGetStatusResponse.kt @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. -package com.blooio.api.models.messages +package com.blooio.api.models.chats.messages import com.blooio.api.core.Enum import com.blooio.api.core.ExcludeMissing @@ -17,50 +17,42 @@ import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull -class MessageRetrieveResponse +class MessageGetStatusResponse @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( - private val apiKey: JsonField, - private val attachmentsCount: JsonField, + private val chatId: JsonField, private val direction: JsonField, - private val externalId: JsonField, + private val error: JsonField, private val messageId: JsonField, - private val metadata: JsonValue, - private val protocol: JsonField, + private val protocol: JsonField, private val status: JsonField, - private val textLength: JsonField, + private val timeDelivered: JsonField, private val timeSent: JsonField, private val additionalProperties: MutableMap, ) { @JsonCreator private constructor( - @JsonProperty("api_key") @ExcludeMissing apiKey: JsonField = JsonMissing.of(), - @JsonProperty("attachments_count") - @ExcludeMissing - attachmentsCount: JsonField = JsonMissing.of(), + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), @JsonProperty("direction") @ExcludeMissing direction: JsonField = JsonMissing.of(), - @JsonProperty("external_id") - @ExcludeMissing - externalId: JsonField = JsonMissing.of(), + @JsonProperty("error") @ExcludeMissing error: JsonField = JsonMissing.of(), @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), - @JsonProperty("metadata") @ExcludeMissing metadata: JsonValue = JsonMissing.of(), - @JsonProperty("protocol") @ExcludeMissing protocol: JsonField = JsonMissing.of(), + @JsonProperty("protocol") @ExcludeMissing protocol: JsonField = JsonMissing.of(), @JsonProperty("status") @ExcludeMissing status: JsonField = JsonMissing.of(), - @JsonProperty("text_length") @ExcludeMissing textLength: JsonField = JsonMissing.of(), + @JsonProperty("time_delivered") + @ExcludeMissing + timeDelivered: JsonField = JsonMissing.of(), @JsonProperty("time_sent") @ExcludeMissing timeSent: JsonField = JsonMissing.of(), ) : this( - apiKey, - attachmentsCount, + chatId, direction, - externalId, + error, messageId, - metadata, protocol, status, - textLength, + timeDelivered, timeSent, mutableMapOf(), ) @@ -69,13 +61,7 @@ private constructor( * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun apiKey(): Optional = apiKey.getOptional("api_key") - - /** - * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the - * server responded with an unexpected value). - */ - fun attachmentsCount(): Optional = attachmentsCount.getOptional("attachments_count") + fun chatId(): Optional = chatId.getOptional("chat_id") /** * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -84,12 +70,10 @@ private constructor( fun direction(): Optional = direction.getOptional("direction") /** - * Recipient phone number. - * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun externalId(): Optional = externalId.getOptional("external_id") + fun error(): Optional = error.getOptional("error") /** * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -97,19 +81,26 @@ private constructor( */ fun messageId(): Optional = messageId.getOptional("message_id") - /** Original metadata provided plus system generated fields. */ - @JsonProperty("metadata") @ExcludeMissing fun _metadata(): JsonValue = metadata - /** - * The protocol used to send the message (e.g., imessage, rcs, sms). + * Transport used to carry the message; never null. `pending` = accepted and dispatched, wire + * service not resolved yet (settles within seconds of send); `imessage` = delivered over + * iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green + * bubble); `unknown` = accepted by the carrier but the wire service could not be resolved + * before the tracking window closed (see `error`). * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun protocol(): Optional = protocol.getOptional("protocol") + fun protocol(): Optional = protocol.getOptional("protocol") /** - * Current delivery status. + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` = + * accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + * carrier (protocol resolution happens around here); `delivered` = a delivery receipt was + * received; `failed` = could not be delivered (see `error`); `cancellation_requested` = a + * cancel was requested for a still-queued message (best-effort); `cancelled` = cancelled before + * dispatch. Inbound messages are surfaced via webhooks with `received`; read receipts arrive as + * a `read` event. * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -120,32 +111,20 @@ private constructor( * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun textLength(): Optional = textLength.getOptional("text_length") + fun timeDelivered(): Optional = timeDelivered.getOptional("time_delivered") /** - * Unix timestamp (ms) when the message was queued/sent. - * * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ fun timeSent(): Optional = timeSent.getOptional("time_sent") /** - * Returns the raw JSON value of [apiKey]. - * - * Unlike [apiKey], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("api_key") @ExcludeMissing fun _apiKey(): JsonField = apiKey - - /** - * Returns the raw JSON value of [attachmentsCount]. + * Returns the raw JSON value of [chatId]. * - * Unlike [attachmentsCount], this method doesn't throw if the JSON field has an unexpected - * type. + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("attachments_count") - @ExcludeMissing - fun _attachmentsCount(): JsonField = attachmentsCount + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId /** * Returns the raw JSON value of [direction]. @@ -155,11 +134,11 @@ private constructor( @JsonProperty("direction") @ExcludeMissing fun _direction(): JsonField = direction /** - * Returns the raw JSON value of [externalId]. + * Returns the raw JSON value of [error]. * - * Unlike [externalId], this method doesn't throw if the JSON field has an unexpected type. + * Unlike [error], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("external_id") @ExcludeMissing fun _externalId(): JsonField = externalId + @JsonProperty("error") @ExcludeMissing fun _error(): JsonField = error /** * Returns the raw JSON value of [messageId]. @@ -173,7 +152,7 @@ private constructor( * * Unlike [protocol], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("protocol") @ExcludeMissing fun _protocol(): JsonField = protocol + @JsonProperty("protocol") @ExcludeMissing fun _protocol(): JsonField = protocol /** * Returns the raw JSON value of [status]. @@ -183,11 +162,13 @@ private constructor( @JsonProperty("status") @ExcludeMissing fun _status(): JsonField = status /** - * Returns the raw JSON value of [textLength]. + * Returns the raw JSON value of [timeDelivered]. * - * Unlike [textLength], this method doesn't throw if the JSON field has an unexpected type. + * Unlike [timeDelivered], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("text_length") @ExcludeMissing fun _textLength(): JsonField = textLength + @JsonProperty("time_delivered") + @ExcludeMissing + fun _timeDelivered(): JsonField = timeDelivered /** * Returns the raw JSON value of [timeSent]. @@ -210,63 +191,45 @@ private constructor( companion object { - /** Returns a mutable builder for constructing an instance of [MessageRetrieveResponse]. */ + /** Returns a mutable builder for constructing an instance of [MessageGetStatusResponse]. */ @JvmStatic fun builder() = Builder() } - /** A builder for [MessageRetrieveResponse]. */ + /** A builder for [MessageGetStatusResponse]. */ class Builder internal constructor() { - private var apiKey: JsonField = JsonMissing.of() - private var attachmentsCount: JsonField = JsonMissing.of() + private var chatId: JsonField = JsonMissing.of() private var direction: JsonField = JsonMissing.of() - private var externalId: JsonField = JsonMissing.of() + private var error: JsonField = JsonMissing.of() private var messageId: JsonField = JsonMissing.of() - private var metadata: JsonValue = JsonMissing.of() - private var protocol: JsonField = JsonMissing.of() + private var protocol: JsonField = JsonMissing.of() private var status: JsonField = JsonMissing.of() - private var textLength: JsonField = JsonMissing.of() + private var timeDelivered: JsonField = JsonMissing.of() private var timeSent: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic - internal fun from(messageRetrieveResponse: MessageRetrieveResponse) = apply { - apiKey = messageRetrieveResponse.apiKey - attachmentsCount = messageRetrieveResponse.attachmentsCount - direction = messageRetrieveResponse.direction - externalId = messageRetrieveResponse.externalId - messageId = messageRetrieveResponse.messageId - metadata = messageRetrieveResponse.metadata - protocol = messageRetrieveResponse.protocol - status = messageRetrieveResponse.status - textLength = messageRetrieveResponse.textLength - timeSent = messageRetrieveResponse.timeSent - additionalProperties = messageRetrieveResponse.additionalProperties.toMutableMap() + internal fun from(messageGetStatusResponse: MessageGetStatusResponse) = apply { + chatId = messageGetStatusResponse.chatId + direction = messageGetStatusResponse.direction + error = messageGetStatusResponse.error + messageId = messageGetStatusResponse.messageId + protocol = messageGetStatusResponse.protocol + status = messageGetStatusResponse.status + timeDelivered = messageGetStatusResponse.timeDelivered + timeSent = messageGetStatusResponse.timeSent + additionalProperties = messageGetStatusResponse.additionalProperties.toMutableMap() } - fun apiKey(apiKey: String) = apiKey(JsonField.of(apiKey)) + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) /** - * Sets [Builder.apiKey] to an arbitrary JSON value. + * Sets [Builder.chatId] to an arbitrary JSON value. * - * You should usually call [Builder.apiKey] with a well-typed [String] value instead. This + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This * method is primarily for setting the field to an undocumented or not yet supported value. */ - fun apiKey(apiKey: JsonField) = apply { this.apiKey = apiKey } - - fun attachmentsCount(attachmentsCount: Long) = - attachmentsCount(JsonField.of(attachmentsCount)) - - /** - * Sets [Builder.attachmentsCount] to an arbitrary JSON value. - * - * You should usually call [Builder.attachmentsCount] with a well-typed [Long] value - * instead. This method is primarily for setting the field to an undocumented or not yet - * supported value. - */ - fun attachmentsCount(attachmentsCount: JsonField) = apply { - this.attachmentsCount = attachmentsCount - } + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } fun direction(direction: Direction) = direction(JsonField.of(direction)) @@ -279,17 +242,18 @@ private constructor( */ fun direction(direction: JsonField) = apply { this.direction = direction } - /** Recipient phone number. */ - fun externalId(externalId: String) = externalId(JsonField.of(externalId)) + fun error(error: String?) = error(JsonField.ofNullable(error)) + + /** Alias for calling [Builder.error] with `error.orElse(null)`. */ + fun error(error: Optional) = error(error.getOrNull()) /** - * Sets [Builder.externalId] to an arbitrary JSON value. + * Sets [Builder.error] to an arbitrary JSON value. * - * You should usually call [Builder.externalId] with a well-typed [String] value instead. - * This method is primarily for setting the field to an undocumented or not yet supported - * value. + * You should usually call [Builder.error] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. */ - fun externalId(externalId: JsonField) = apply { this.externalId = externalId } + fun error(error: JsonField) = apply { this.error = error } fun messageId(messageId: String) = messageId(JsonField.of(messageId)) @@ -302,22 +266,37 @@ private constructor( */ fun messageId(messageId: JsonField) = apply { this.messageId = messageId } - /** Original metadata provided plus system generated fields. */ - fun metadata(metadata: JsonValue) = apply { this.metadata = metadata } - - /** The protocol used to send the message (e.g., imessage, rcs, sms). */ - fun protocol(protocol: String) = protocol(JsonField.of(protocol)) + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, + * wire service not resolved yet (settles within seconds of send); `imessage` = delivered + * over iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS + * (green bubble); `unknown` = accepted by the carrier but the wire service could not be + * resolved before the tracking window closed (see `error`). + */ + fun protocol(protocol: Protocol) = protocol(JsonField.of(protocol)) /** * Sets [Builder.protocol] to an arbitrary JSON value. * - * You should usually call [Builder.protocol] with a well-typed [String] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. + * You should usually call [Builder.protocol] with a well-typed [Protocol] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. */ - fun protocol(protocol: JsonField) = apply { this.protocol = protocol } + fun protocol(protocol: JsonField) = apply { this.protocol = protocol } - /** Current delivery status. */ - fun status(status: Status) = status(JsonField.of(status)) + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` + * = accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to + * Apple/the carrier (protocol resolution happens around here); `delivered` = a delivery + * receipt was received; `failed` = could not be delivered (see `error`); + * `cancellation_requested` = a cancel was requested for a still-queued message + * (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are surfaced via + * webhooks with `received`; read receipts arrive as a `read` event. + */ + fun status(status: Status?) = status(JsonField.ofNullable(status)) + + /** Alias for calling [Builder.status] with `status.orElse(null)`. */ + fun status(status: Optional) = status(status.getOrNull()) /** * Sets [Builder.status] to an arbitrary JSON value. @@ -327,17 +306,29 @@ private constructor( */ fun status(status: JsonField) = apply { this.status = status } - fun textLength(textLength: Long) = textLength(JsonField.of(textLength)) + fun timeDelivered(timeDelivered: Long?) = timeDelivered(JsonField.ofNullable(timeDelivered)) /** - * Sets [Builder.textLength] to an arbitrary JSON value. + * Alias for [Builder.timeDelivered]. * - * You should usually call [Builder.textLength] with a well-typed [Long] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. + * This unboxed primitive overload exists for backwards compatibility. + */ + fun timeDelivered(timeDelivered: Long) = timeDelivered(timeDelivered as Long?) + + /** Alias for calling [Builder.timeDelivered] with `timeDelivered.orElse(null)`. */ + fun timeDelivered(timeDelivered: Optional) = timeDelivered(timeDelivered.getOrNull()) + + /** + * Sets [Builder.timeDelivered] to an arbitrary JSON value. + * + * You should usually call [Builder.timeDelivered] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. */ - fun textLength(textLength: JsonField) = apply { this.textLength = textLength } + fun timeDelivered(timeDelivered: JsonField) = apply { + this.timeDelivered = timeDelivered + } - /** Unix timestamp (ms) when the message was queued/sent. */ fun timeSent(timeSent: Long) = timeSent(JsonField.of(timeSent)) /** @@ -368,21 +359,19 @@ private constructor( } /** - * Returns an immutable instance of [MessageRetrieveResponse]. + * Returns an immutable instance of [MessageGetStatusResponse]. * * Further updates to this [Builder] will not mutate the returned instance. */ - fun build(): MessageRetrieveResponse = - MessageRetrieveResponse( - apiKey, - attachmentsCount, + fun build(): MessageGetStatusResponse = + MessageGetStatusResponse( + chatId, direction, - externalId, + error, messageId, - metadata, protocol, status, - textLength, + timeDelivered, timeSent, additionalProperties.toMutableMap(), ) @@ -390,19 +379,26 @@ private constructor( private var validated: Boolean = false - fun validate(): MessageRetrieveResponse = 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): MessageGetStatusResponse = apply { if (validated) { return@apply } - apiKey() - attachmentsCount() + chatId() direction().ifPresent { it.validate() } - externalId() + error() messageId() - protocol() + protocol().ifPresent { it.validate() } status().ifPresent { it.validate() } - textLength() + timeDelivered() timeSent() validated = true } @@ -422,14 +418,13 @@ private constructor( */ @JvmSynthetic internal fun validity(): Int = - (if (apiKey.asKnown().isPresent) 1 else 0) + - (if (attachmentsCount.asKnown().isPresent) 1 else 0) + + (if (chatId.asKnown().isPresent) 1 else 0) + (direction.asKnown().getOrNull()?.validity() ?: 0) + - (if (externalId.asKnown().isPresent) 1 else 0) + + (if (error.asKnown().isPresent) 1 else 0) + (if (messageId.asKnown().isPresent) 1 else 0) + - (if (protocol.asKnown().isPresent) 1 else 0) + + (protocol.asKnown().getOrNull()?.validity() ?: 0) + (status.asKnown().getOrNull()?.validity() ?: 0) + - (if (textLength.asKnown().isPresent) 1 else 0) + + (if (timeDelivered.asKnown().isPresent) 1 else 0) + (if (timeSent.asKnown().isPresent) 1 else 0) class Direction @JsonCreator private constructor(private val value: JsonField) : Enum { @@ -446,17 +441,17 @@ private constructor( companion object { - @JvmField val OUTBOUND = of("outbound") - @JvmField val INBOUND = of("inbound") + @JvmField val OUTBOUND = of("outbound") + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) } /** An enum containing [Direction]'s known values. */ enum class Known { - OUTBOUND, INBOUND, + OUTBOUND, } /** @@ -469,8 +464,8 @@ private constructor( * - It was constructed with an arbitrary value using the [of] method. */ enum class Value { - OUTBOUND, INBOUND, + OUTBOUND, /** * An enum member indicating that [Direction] was instantiated with an unknown value. */ @@ -486,8 +481,8 @@ private constructor( */ fun value(): Value = when (this) { - OUTBOUND -> Value.OUTBOUND INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND else -> Value._UNKNOWN } @@ -502,8 +497,8 @@ private constructor( */ fun known(): Known = when (this) { - OUTBOUND -> Known.OUTBOUND INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND else -> throw BlooioInvalidDataException("Unknown Direction: $value") } @@ -521,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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Direction = apply { if (validated) { return@apply @@ -559,7 +563,174 @@ private constructor( override fun toString() = value.toString() } - /** Current delivery status. */ + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, wire + * service not resolved yet (settles within seconds of send); `imessage` = delivered over + * iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green + * bubble); `unknown` = accepted by the carrier but the wire service could not be resolved + * before the tracking window closed (see `error`). + */ + class Protocol @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PENDING = of("pending") + + @JvmField val UNKNOWN = of("unknown") + + @JvmField val IMESSAGE = of("imessage") + + @JvmField val SMS = of("sms") + + @JvmField val RCS = of("rcs") + + @JvmStatic fun of(value: String) = Protocol(JsonField.of(value)) + } + + /** An enum containing [Protocol]'s known values. */ + enum class Known { + PENDING, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + } + + /** + * An enum containing [Protocol]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Protocol] 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, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + /** An enum member indicating that [Protocol] 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 + UNKNOWN -> Value.UNKNOWN + IMESSAGE -> Value.IMESSAGE + SMS -> Value.SMS + RCS -> Value.RCS + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PENDING -> Known.PENDING + UNKNOWN -> Known.UNKNOWN + IMESSAGE -> Known.IMESSAGE + SMS -> Known.SMS + RCS -> Known.RCS + else -> throw BlooioInvalidDataException("Unknown Protocol: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Protocol = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Protocol && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` = + * accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + * carrier (protocol resolution happens around here); `delivered` = a delivery receipt was + * received; `failed` = could not be delivered (see `error`); `cancellation_requested` = a + * cancel was requested for a still-queued message (best-effort); `cancelled` = cancelled before + * dispatch. Inbound messages are surfaced via webhooks with `received`; read receipts arrive as + * a `read` event. + */ class Status @JsonCreator private constructor(private val value: JsonField) : Enum { /** @@ -584,10 +755,10 @@ private constructor( @JvmField val FAILED = of("failed") - @JvmField val CANCELLED = of("cancelled") - @JvmField val CANCELLATION_REQUESTED = of("cancellation_requested") + @JvmField val CANCELLED = of("cancelled") + @JvmStatic fun of(value: String) = Status(JsonField.of(value)) } @@ -598,8 +769,8 @@ private constructor( SENT, DELIVERED, FAILED, - CANCELLED, CANCELLATION_REQUESTED, + CANCELLED, } /** @@ -617,8 +788,8 @@ private constructor( SENT, DELIVERED, FAILED, - CANCELLED, CANCELLATION_REQUESTED, + CANCELLED, /** An enum member indicating that [Status] was instantiated with an unknown value. */ _UNKNOWN, } @@ -637,8 +808,8 @@ private constructor( SENT -> Value.SENT DELIVERED -> Value.DELIVERED FAILED -> Value.FAILED - CANCELLED -> Value.CANCELLED CANCELLATION_REQUESTED -> Value.CANCELLATION_REQUESTED + CANCELLED -> Value.CANCELLED else -> Value._UNKNOWN } @@ -658,8 +829,8 @@ private constructor( SENT -> Known.SENT DELIVERED -> Known.DELIVERED FAILED -> Known.FAILED - CANCELLED -> Known.CANCELLED CANCELLATION_REQUESTED -> Known.CANCELLATION_REQUESTED + CANCELLED -> Known.CANCELLED else -> throw BlooioInvalidDataException("Unknown Status: $value") } @@ -677,6 +848,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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Status = apply { if (validated) { return@apply @@ -720,31 +900,27 @@ private constructor( return true } - return other is MessageRetrieveResponse && - apiKey == other.apiKey && - attachmentsCount == other.attachmentsCount && + return other is MessageGetStatusResponse && + chatId == other.chatId && direction == other.direction && - externalId == other.externalId && + error == other.error && messageId == other.messageId && - metadata == other.metadata && protocol == other.protocol && status == other.status && - textLength == other.textLength && + timeDelivered == other.timeDelivered && timeSent == other.timeSent && additionalProperties == other.additionalProperties } private val hashCode: Int by lazy { Objects.hash( - apiKey, - attachmentsCount, + chatId, direction, - externalId, + error, messageId, - metadata, protocol, status, - textLength, + timeDelivered, timeSent, additionalProperties, ) @@ -753,5 +929,5 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "MessageRetrieveResponse{apiKey=$apiKey, attachmentsCount=$attachmentsCount, direction=$direction, externalId=$externalId, messageId=$messageId, metadata=$metadata, protocol=$protocol, status=$status, textLength=$textLength, timeSent=$timeSent, additionalProperties=$additionalProperties}" + "MessageGetStatusResponse{chatId=$chatId, direction=$direction, error=$error, messageId=$messageId, protocol=$protocol, status=$status, timeDelivered=$timeDelivered, timeSent=$timeSent, additionalProperties=$additionalProperties}" } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListParams.kt new file mode 100644 index 0000000..7e2af9c --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListParams.kt @@ -0,0 +1,615 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.JsonField +import com.blooio.api.core.Params +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.errors.BlooioInvalidDataException +import com.fasterxml.jackson.annotation.JsonCreator +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** List all messages in a conversation with optional filtering. */ +class MessageListParams +private constructor( + private val chatId: String?, + private val direction: Direction?, + private val limit: Long?, + private val offset: Long?, + private val since: Long?, + private val sort: Sort?, + private val until: Long?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + /** Filter by message direction */ + fun direction(): Optional = Optional.ofNullable(direction) + + /** + * Maximum number of items to return in a single response. Must be between 1 and 200; defaults + * to 50. Use together with `offset` to page through large result sets. + */ + fun limit(): Optional = Optional.ofNullable(limit) + + /** + * Number of items to skip before returning results. Combine with `limit` for page-based + * pagination (e.g. `offset=50&limit=50` returns the second page). Defaults to 0. + */ + fun offset(): Optional = Optional.ofNullable(offset) + + /** Only messages sent after this timestamp (ms) */ + fun since(): Optional = Optional.ofNullable(since) + + /** Sort order by time */ + fun sort(): Optional = Optional.ofNullable(sort) + + /** Only messages sent before this timestamp (ms) */ + fun until(): Optional = Optional.ofNullable(until) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): MessageListParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [MessageListParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageListParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var direction: Direction? = null + private var limit: Long? = null + private var offset: Long? = null + private var since: Long? = null + private var sort: Sort? = null + private var until: Long? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(messageListParams: MessageListParams) = apply { + chatId = messageListParams.chatId + direction = messageListParams.direction + limit = messageListParams.limit + offset = messageListParams.offset + since = messageListParams.since + sort = messageListParams.sort + until = messageListParams.until + additionalHeaders = messageListParams.additionalHeaders.toBuilder() + additionalQueryParams = messageListParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + /** Filter by message direction */ + fun direction(direction: Direction?) = apply { this.direction = direction } + + /** Alias for calling [Builder.direction] with `direction.orElse(null)`. */ + fun direction(direction: Optional) = direction(direction.getOrNull()) + + /** + * Maximum number of items to return in a single response. Must be between 1 and 200; + * defaults to 50. Use together with `offset` to page through large result sets. + */ + fun limit(limit: Long?) = apply { this.limit = limit } + + /** + * Alias for [Builder.limit]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun limit(limit: Long) = limit(limit as Long?) + + /** Alias for calling [Builder.limit] with `limit.orElse(null)`. */ + fun limit(limit: Optional) = limit(limit.getOrNull()) + + /** + * Number of items to skip before returning results. Combine with `limit` for page-based + * pagination (e.g. `offset=50&limit=50` returns the second page). Defaults to 0. + */ + fun offset(offset: Long?) = apply { this.offset = offset } + + /** + * Alias for [Builder.offset]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun offset(offset: Long) = offset(offset as Long?) + + /** Alias for calling [Builder.offset] with `offset.orElse(null)`. */ + fun offset(offset: Optional) = offset(offset.getOrNull()) + + /** Only messages sent after this timestamp (ms) */ + fun since(since: Long?) = apply { this.since = since } + + /** + * Alias for [Builder.since]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun since(since: Long) = since(since as Long?) + + /** Alias for calling [Builder.since] with `since.orElse(null)`. */ + fun since(since: Optional) = since(since.getOrNull()) + + /** Sort order by time */ + fun sort(sort: Sort?) = apply { this.sort = sort } + + /** Alias for calling [Builder.sort] with `sort.orElse(null)`. */ + fun sort(sort: Optional) = sort(sort.getOrNull()) + + /** Only messages sent before this timestamp (ms) */ + fun until(until: Long?) = apply { this.until = until } + + /** + * Alias for [Builder.until]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun until(until: Long) = until(until as Long?) + + /** Alias for calling [Builder.until] with `until.orElse(null)`. */ + fun until(until: Optional) = until(until.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [MessageListParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageListParams = + MessageListParams( + chatId, + direction, + limit, + offset, + since, + sort, + until, + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = + QueryParams.builder() + .apply { + direction?.let { put("direction", it.toString()) } + limit?.let { put("limit", it.toString()) } + offset?.let { put("offset", it.toString()) } + since?.let { put("since", it.toString()) } + sort?.let { put("sort", it.toString()) } + until?.let { put("until", it.toString()) } + putAll(additionalQueryParams) + } + .build() + + /** Filter by message direction */ + class Direction @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val INBOUND = of("inbound") + + @JvmField val OUTBOUND = of("outbound") + + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) + } + + /** An enum containing [Direction]'s known values. */ + enum class Known { + INBOUND, + OUTBOUND, + } + + /** + * An enum containing [Direction]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Direction] 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 { + INBOUND, + OUTBOUND, + /** + * An enum member indicating that [Direction] 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) { + INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND + else -> throw BlooioInvalidDataException("Unknown Direction: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Direction = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Direction && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** Sort order by time */ + class Sort @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val ASC = of("asc") + + @JvmField val DESC = of("desc") + + @JvmStatic fun of(value: String) = Sort(JsonField.of(value)) + } + + /** An enum containing [Sort]'s known values. */ + enum class Known { + ASC, + DESC, + } + + /** + * An enum containing [Sort]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Sort] 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 { + ASC, + DESC, + /** An enum member indicating that [Sort] 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) { + ASC -> Value.ASC + DESC -> Value.DESC + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + ASC -> Known.ASC + DESC -> Known.DESC + else -> throw BlooioInvalidDataException("Unknown Sort: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Sort = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Sort && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageListParams && + chatId == other.chatId && + direction == other.direction && + limit == other.limit && + offset == other.offset && + since == other.since && + sort == other.sort && + until == other.until && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash( + chatId, + direction, + limit, + offset, + since, + sort, + until, + additionalHeaders, + additionalQueryParams, + ) + + override fun toString() = + "MessageListParams{chatId=$chatId, direction=$direction, limit=$limit, offset=$offset, since=$since, sort=$sort, until=$until, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListResponse.kt new file mode 100644 index 0000000..fccbab0 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageListResponse.kt @@ -0,0 +1,1770 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.checkKnown +import com.blooio.api.core.checkRequired +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +import com.blooio.api.models.contacts.Pagination +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class MessageListResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val chatId: JsonField, + private val messages: JsonField>, + private val pagination: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("messages") + @ExcludeMissing + messages: JsonField> = JsonMissing.of(), + @JsonProperty("pagination") + @ExcludeMissing + pagination: JsonField = JsonMissing.of(), + ) : this(chatId, messages, pagination, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chatId(): Optional = chatId.getOptional("chat_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messages(): Optional> = messages.getOptional("messages") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun pagination(): Optional = pagination.getOptional("pagination") + + /** + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [messages]. + * + * Unlike [messages], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("messages") @ExcludeMissing fun _messages(): JsonField> = messages + + /** + * Returns the raw JSON value of [pagination]. + * + * Unlike [pagination], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("pagination") + @ExcludeMissing + fun _pagination(): JsonField = pagination + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [MessageListResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageListResponse]. */ + class Builder internal constructor() { + + private var chatId: JsonField = JsonMissing.of() + private var messages: JsonField>? = null + private var pagination: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(messageListResponse: MessageListResponse) = apply { + chatId = messageListResponse.chatId + messages = messageListResponse.messages.map { it.toMutableList() } + pagination = messageListResponse.pagination + additionalProperties = messageListResponse.additionalProperties.toMutableMap() + } + + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) + + /** + * Sets [Builder.chatId] to an arbitrary JSON value. + * + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + fun messages(messages: List) = messages(JsonField.of(messages)) + + /** + * Sets [Builder.messages] to an arbitrary JSON value. + * + * You should usually call [Builder.messages] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messages(messages: JsonField>) = apply { + this.messages = messages.map { it.toMutableList() } + } + + /** + * Adds a single [Message] to [messages]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addMessage(message: Message) = apply { + messages = + (messages ?: JsonField.of(mutableListOf())).also { + checkKnown("messages", it).add(message) + } + } + + fun pagination(pagination: Pagination) = pagination(JsonField.of(pagination)) + + /** + * Sets [Builder.pagination] to an arbitrary JSON value. + * + * You should usually call [Builder.pagination] with a well-typed [Pagination] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun pagination(pagination: JsonField) = apply { this.pagination = pagination } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [MessageListResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageListResponse = + MessageListResponse( + chatId, + (messages ?: JsonMissing.of()).map { it.toImmutable() }, + pagination, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): MessageListResponse = apply { + if (validated) { + return@apply + } + + chatId() + messages().ifPresent { it.forEach { it.validate() } } + pagination().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (chatId.asKnown().isPresent) 1 else 0) + + (messages.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (pagination.asKnown().getOrNull()?.validity() ?: 0) + + class Message + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val attachments: JsonField>, + private val direction: JsonField, + private val error: JsonField, + private val externalId: JsonField, + private val internalId: JsonField, + private val messageId: JsonField, + private val protocol: JsonField, + private val reactions: JsonField>, + private val replyTo: JsonField, + private val sender: JsonField, + private val status: JsonField, + private val text: JsonField, + private val timeDelivered: JsonField, + private val timeSent: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("attachments") + @ExcludeMissing + attachments: JsonField> = JsonMissing.of(), + @JsonProperty("direction") + @ExcludeMissing + direction: JsonField = JsonMissing.of(), + @JsonProperty("error") @ExcludeMissing error: JsonField = JsonMissing.of(), + @JsonProperty("external_id") + @ExcludeMissing + externalId: JsonField = JsonMissing.of(), + @JsonProperty("internal_id") + @ExcludeMissing + internalId: JsonField = JsonMissing.of(), + @JsonProperty("message_id") + @ExcludeMissing + messageId: JsonField = JsonMissing.of(), + @JsonProperty("protocol") + @ExcludeMissing + protocol: JsonField = JsonMissing.of(), + @JsonProperty("reactions") + @ExcludeMissing + reactions: JsonField> = JsonMissing.of(), + @JsonProperty("reply_to") + @ExcludeMissing + replyTo: JsonField = JsonMissing.of(), + @JsonProperty("sender") @ExcludeMissing sender: JsonField = JsonMissing.of(), + @JsonProperty("status") @ExcludeMissing status: JsonField = JsonMissing.of(), + @JsonProperty("text") @ExcludeMissing text: JsonField = JsonMissing.of(), + @JsonProperty("time_delivered") + @ExcludeMissing + timeDelivered: JsonField = JsonMissing.of(), + @JsonProperty("time_sent") @ExcludeMissing timeSent: JsonField = JsonMissing.of(), + ) : this( + attachments, + direction, + error, + externalId, + internalId, + messageId, + protocol, + reactions, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + mutableMapOf(), + ) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun attachments(): Optional> = attachments.getOptional("attachments") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun direction(): Optional = direction.getOptional("direction") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun error(): Optional = error.getOptional("error") + + /** + * Phone number or email of the contact, or group ID for group messages + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun externalId(): Optional = externalId.getOptional("external_id") + + /** + * Organization phone number (from-number) used for this message + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun internalId(): Optional = internalId.getOptional("internal_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, + * wire service not resolved yet (settles within seconds of send); `imessage` = delivered + * over iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS + * (green bubble); `unknown` = accepted by the carrier but the wire service could not be + * resolved before the tracking window closed (see `error`). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun protocol(): Optional = protocol.getOptional("protocol") + + /** + * Reactions on this message (tapbacks and emoji reactions) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun reactions(): Optional> = reactions.getOptional("reactions") + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on + * every GET endpoint that returns a single message or a list of messages. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun replyTo(): Optional = replyTo.getOptional("reply_to") + + /** + * Sender's phone number or email for inbound group messages. Null for outbound messages and + * 1-1 chats. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun sender(): Optional = sender.getOptional("sender") + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` + * = accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to + * Apple/the carrier (protocol resolution happens around here); `delivered` = a delivery + * receipt was received; `failed` = could not be delivered (see `error`); + * `cancellation_requested` = a cancel was requested for a still-queued message + * (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are surfaced via + * webhooks with `received`; read receipts arrive as a `read` event. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun status(): Optional = status.getOptional("status") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = text.getOptional("text") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeDelivered(): Optional = timeDelivered.getOptional("time_delivered") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeSent(): Optional = timeSent.getOptional("time_sent") + + /** + * Returns the raw JSON value of [attachments]. + * + * Unlike [attachments], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("attachments") + @ExcludeMissing + fun _attachments(): JsonField> = attachments + + /** + * Returns the raw JSON value of [direction]. + * + * Unlike [direction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("direction") + @ExcludeMissing + fun _direction(): JsonField = direction + + /** + * Returns the raw JSON value of [error]. + * + * Unlike [error], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("error") @ExcludeMissing fun _error(): JsonField = error + + /** + * Returns the raw JSON value of [externalId]. + * + * Unlike [externalId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("external_id") + @ExcludeMissing + fun _externalId(): JsonField = externalId + + /** + * Returns the raw JSON value of [internalId]. + * + * Unlike [internalId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("internal_id") + @ExcludeMissing + fun _internalId(): JsonField = internalId + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [protocol]. + * + * Unlike [protocol], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("protocol") @ExcludeMissing fun _protocol(): JsonField = protocol + + /** + * Returns the raw JSON value of [reactions]. + * + * Unlike [reactions], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reactions") + @ExcludeMissing + fun _reactions(): JsonField> = reactions + + /** + * Returns the raw JSON value of [replyTo]. + * + * Unlike [replyTo], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reply_to") @ExcludeMissing fun _replyTo(): JsonField = replyTo + + /** + * Returns the raw JSON value of [sender]. + * + * Unlike [sender], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("sender") @ExcludeMissing fun _sender(): JsonField = sender + + /** + * 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 + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("text") @ExcludeMissing fun _text(): JsonField = text + + /** + * Returns the raw JSON value of [timeDelivered]. + * + * Unlike [timeDelivered], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("time_delivered") + @ExcludeMissing + fun _timeDelivered(): JsonField = timeDelivered + + /** + * Returns the raw JSON value of [timeSent]. + * + * Unlike [timeSent], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("time_sent") @ExcludeMissing fun _timeSent(): JsonField = timeSent + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Message]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Message]. */ + class Builder internal constructor() { + + private var attachments: JsonField>? = null + private var direction: JsonField = JsonMissing.of() + private var error: JsonField = JsonMissing.of() + private var externalId: JsonField = JsonMissing.of() + private var internalId: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var protocol: JsonField = JsonMissing.of() + private var reactions: JsonField>? = null + private var replyTo: JsonField = JsonMissing.of() + private var sender: JsonField = JsonMissing.of() + private var status: JsonField = JsonMissing.of() + private var text: JsonField = JsonMissing.of() + private var timeDelivered: JsonField = JsonMissing.of() + private var timeSent: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(message: Message) = apply { + attachments = message.attachments.map { it.toMutableList() } + direction = message.direction + error = message.error + externalId = message.externalId + internalId = message.internalId + messageId = message.messageId + protocol = message.protocol + reactions = message.reactions.map { it.toMutableList() } + replyTo = message.replyTo + sender = message.sender + status = message.status + text = message.text + timeDelivered = message.timeDelivered + timeSent = message.timeSent + additionalProperties = message.additionalProperties.toMutableMap() + } + + fun attachments(attachments: List) = attachments(JsonField.of(attachments)) + + /** + * Sets [Builder.attachments] to an arbitrary JSON value. + * + * You should usually call [Builder.attachments] with a well-typed `List` + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun attachments(attachments: JsonField>) = apply { + this.attachments = attachments.map { it.toMutableList() } + } + + /** + * Adds a single [JsonValue] to [attachments]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addAttachment(attachment: JsonValue) = apply { + attachments = + (attachments ?: JsonField.of(mutableListOf())).also { + checkKnown("attachments", it).add(attachment) + } + } + + fun direction(direction: Direction) = direction(JsonField.of(direction)) + + /** + * Sets [Builder.direction] to an arbitrary JSON value. + * + * You should usually call [Builder.direction] with a well-typed [Direction] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun direction(direction: JsonField) = apply { this.direction = direction } + + fun error(error: String?) = error(JsonField.ofNullable(error)) + + /** Alias for calling [Builder.error] with `error.orElse(null)`. */ + fun error(error: Optional) = error(error.getOrNull()) + + /** + * Sets [Builder.error] to an arbitrary JSON value. + * + * You should usually call [Builder.error] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun error(error: JsonField) = apply { this.error = error } + + /** Phone number or email of the contact, or group ID for group messages */ + fun externalId(externalId: String) = externalId(JsonField.of(externalId)) + + /** + * Sets [Builder.externalId] to an arbitrary JSON value. + * + * You should usually call [Builder.externalId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun externalId(externalId: JsonField) = apply { this.externalId = externalId } + + /** Organization phone number (from-number) used for this message */ + fun internalId(internalId: String?) = internalId(JsonField.ofNullable(internalId)) + + /** Alias for calling [Builder.internalId] with `internalId.orElse(null)`. */ + fun internalId(internalId: Optional) = internalId(internalId.getOrNull()) + + /** + * Sets [Builder.internalId] to an arbitrary JSON value. + * + * You should usually call [Builder.internalId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun internalId(internalId: JsonField) = apply { this.internalId = internalId } + + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, + * wire service not resolved yet (settles within seconds of send); `imessage` = + * delivered over iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back + * to SMS/MMS (green bubble); `unknown` = accepted by the carrier but the wire service + * could not be resolved before the tracking window closed (see `error`). + */ + fun protocol(protocol: Protocol) = protocol(JsonField.of(protocol)) + + /** + * Sets [Builder.protocol] to an arbitrary JSON value. + * + * You should usually call [Builder.protocol] with a well-typed [Protocol] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun protocol(protocol: JsonField) = apply { this.protocol = protocol } + + /** Reactions on this message (tapbacks and emoji reactions) */ + fun reactions(reactions: List) = reactions(JsonField.of(reactions)) + + /** + * Sets [Builder.reactions] to an arbitrary JSON value. + * + * You should usually call [Builder.reactions] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun reactions(reactions: JsonField>) = apply { + this.reactions = reactions.map { it.toMutableList() } + } + + /** + * Adds a single [Reaction] to [reactions]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addReaction(reaction: Reaction) = apply { + reactions = + (reactions ?: JsonField.of(mutableListOf())).also { + checkKnown("reactions", it).add(reaction) + } + } + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on + * every GET endpoint that returns a single message or a list of messages. + */ + fun replyTo(replyTo: ReplyTo?) = replyTo(JsonField.ofNullable(replyTo)) + + /** Alias for calling [Builder.replyTo] with `replyTo.orElse(null)`. */ + fun replyTo(replyTo: Optional) = replyTo(replyTo.getOrNull()) + + /** + * Sets [Builder.replyTo] to an arbitrary JSON value. + * + * You should usually call [Builder.replyTo] with a well-typed [ReplyTo] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun replyTo(replyTo: JsonField) = apply { this.replyTo = replyTo } + + /** + * Sender's phone number or email for inbound group messages. Null for outbound messages + * and 1-1 chats. + */ + fun sender(sender: String?) = sender(JsonField.ofNullable(sender)) + + /** Alias for calling [Builder.sender] with `sender.orElse(null)`. */ + fun sender(sender: Optional) = sender(sender.getOrNull()) + + /** + * Sets [Builder.sender] to an arbitrary JSON value. + * + * You should usually call [Builder.sender] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun sender(sender: JsonField) = apply { this.sender = sender } + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; + * `queued` = accepted and waiting to be handed to Apple/the carrier; `sent` = handed + * off to Apple/the carrier (protocol resolution happens around here); `delivered` = a + * delivery receipt was received; `failed` = could not be delivered (see `error`); + * `cancellation_requested` = a cancel was requested for a still-queued message + * (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are surfaced + * via webhooks with `received`; read receipts arrive as a `read` event. + */ + fun status(status: Status?) = status(JsonField.ofNullable(status)) + + /** Alias for calling [Builder.status] with `status.orElse(null)`. */ + fun status(status: Optional) = status(status.getOrNull()) + + /** + * Sets [Builder.status] to an arbitrary JSON value. + * + * You should usually call [Builder.status] with a well-typed [Status] 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 text(text: String?) = text(JsonField.ofNullable(text)) + + /** Alias for calling [Builder.text] with `text.orElse(null)`. */ + fun text(text: Optional) = text(text.getOrNull()) + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun text(text: JsonField) = apply { this.text = text } + + fun timeDelivered(timeDelivered: Long?) = + timeDelivered(JsonField.ofNullable(timeDelivered)) + + /** + * Alias for [Builder.timeDelivered]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun timeDelivered(timeDelivered: Long) = timeDelivered(timeDelivered as Long?) + + /** Alias for calling [Builder.timeDelivered] with `timeDelivered.orElse(null)`. */ + fun timeDelivered(timeDelivered: Optional) = + timeDelivered(timeDelivered.getOrNull()) + + /** + * Sets [Builder.timeDelivered] to an arbitrary JSON value. + * + * You should usually call [Builder.timeDelivered] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun timeDelivered(timeDelivered: JsonField) = apply { + this.timeDelivered = timeDelivered + } + + fun timeSent(timeSent: Long) = timeSent(JsonField.of(timeSent)) + + /** + * Sets [Builder.timeSent] to an arbitrary JSON value. + * + * You should usually call [Builder.timeSent] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun timeSent(timeSent: JsonField) = apply { this.timeSent = timeSent } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Message]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Message = + Message( + (attachments ?: JsonMissing.of()).map { it.toImmutable() }, + direction, + error, + externalId, + internalId, + messageId, + protocol, + (reactions ?: JsonMissing.of()).map { it.toImmutable() }, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Message = apply { + if (validated) { + return@apply + } + + attachments() + direction().ifPresent { it.validate() } + error() + externalId() + internalId() + messageId() + protocol().ifPresent { it.validate() } + reactions().ifPresent { it.forEach { it.validate() } } + replyTo().ifPresent { it.validate() } + sender() + status().ifPresent { it.validate() } + text() + timeDelivered() + timeSent() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (attachments.asKnown().getOrNull()?.size ?: 0) + + (direction.asKnown().getOrNull()?.validity() ?: 0) + + (if (error.asKnown().isPresent) 1 else 0) + + (if (externalId.asKnown().isPresent) 1 else 0) + + (if (internalId.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (protocol.asKnown().getOrNull()?.validity() ?: 0) + + (reactions.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (replyTo.asKnown().getOrNull()?.validity() ?: 0) + + (if (sender.asKnown().isPresent) 1 else 0) + + (status.asKnown().getOrNull()?.validity() ?: 0) + + (if (text.asKnown().isPresent) 1 else 0) + + (if (timeDelivered.asKnown().isPresent) 1 else 0) + + (if (timeSent.asKnown().isPresent) 1 else 0) + + class Direction @JsonCreator private constructor(private val value: JsonField) : + Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is + * on an older version than the API, then the API may respond with new members that the + * SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val INBOUND = of("inbound") + + @JvmField val OUTBOUND = of("outbound") + + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) + } + + /** An enum containing [Direction]'s known values. */ + enum class Known { + INBOUND, + OUTBOUND, + } + + /** + * An enum containing [Direction]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Direction] 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 { + INBOUND, + OUTBOUND, + /** + * An enum member indicating that [Direction] 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) { + INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND + else -> throw BlooioInvalidDataException("Unknown Direction: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Direction = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Direction && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, + * wire service not resolved yet (settles within seconds of send); `imessage` = delivered + * over iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS + * (green bubble); `unknown` = accepted by the carrier but the wire service could not be + * resolved before the tracking window closed (see `error`). + */ + class Protocol @JsonCreator private constructor(private val value: JsonField) : + Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is + * on an older version than the API, then the API may respond with new members that the + * SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PENDING = of("pending") + + @JvmField val UNKNOWN = of("unknown") + + @JvmField val IMESSAGE = of("imessage") + + @JvmField val SMS = of("sms") + + @JvmField val RCS = of("rcs") + + @JvmStatic fun of(value: String) = Protocol(JsonField.of(value)) + } + + /** An enum containing [Protocol]'s known values. */ + enum class Known { + PENDING, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + } + + /** + * An enum containing [Protocol]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Protocol] 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, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + /** + * An enum member indicating that [Protocol] 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 + UNKNOWN -> Value.UNKNOWN + IMESSAGE -> Value.IMESSAGE + SMS -> Value.SMS + RCS -> Value.RCS + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PENDING -> Known.PENDING + UNKNOWN -> Known.UNKNOWN + IMESSAGE -> Known.IMESSAGE + SMS -> Known.SMS + RCS -> Known.RCS + else -> throw BlooioInvalidDataException("Unknown Protocol: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Protocol = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Protocol && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on + * every GET endpoint that returns a single message or a list of messages. + */ + class ReplyTo + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val guid: JsonField, + private val messageId: JsonField, + private val partIndex: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("guid") @ExcludeMissing guid: JsonField = JsonMissing.of(), + @JsonProperty("message_id") + @ExcludeMissing + messageId: JsonField = JsonMissing.of(), + @JsonProperty("part_index") + @ExcludeMissing + partIndex: JsonField = JsonMissing.of(), + ) : this(guid, messageId, partIndex, mutableMapOf()) + + /** + * The raw iMessage GUID of the parent. Always populated on real inline replies; the + * on-device record-of-truth identifier that survives even when `message_id` cannot be + * resolved. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun guid(): Optional = guid.getOptional("guid") + + /** + * The Blooio `message_id` of the parent message. NULL when the parent isn't in our + * `messages` table (e.g., the original was sent from outside Blooio's pipeline). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * Which part of the parent was replied to. 0 for the common single-part case. + * + * @throws BlooioInvalidDataException 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 partIndex(): Long = partIndex.getRequired("part_index") + + /** + * Returns the raw JSON value of [guid]. + * + * Unlike [guid], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("guid") @ExcludeMissing fun _guid(): JsonField = guid + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("message_id") + @ExcludeMissing + fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [partIndex]. + * + * Unlike [partIndex], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("part_index") + @ExcludeMissing + fun _partIndex(): JsonField = partIndex + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [ReplyTo]. + * + * The following fields are required: + * ```java + * .guid() + * .messageId() + * .partIndex() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ReplyTo]. */ + class Builder internal constructor() { + + private var guid: JsonField? = null + private var messageId: JsonField? = null + private var partIndex: JsonField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(replyTo: ReplyTo) = apply { + guid = replyTo.guid + messageId = replyTo.messageId + partIndex = replyTo.partIndex + additionalProperties = replyTo.additionalProperties.toMutableMap() + } + + /** + * The raw iMessage GUID of the parent. Always populated on real inline replies; the + * on-device record-of-truth identifier that survives even when `message_id` cannot + * be resolved. + */ + fun guid(guid: String?) = guid(JsonField.ofNullable(guid)) + + /** Alias for calling [Builder.guid] with `guid.orElse(null)`. */ + fun guid(guid: Optional) = guid(guid.getOrNull()) + + /** + * Sets [Builder.guid] to an arbitrary JSON value. + * + * You should usually call [Builder.guid] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun guid(guid: JsonField) = apply { this.guid = guid } + + /** + * The Blooio `message_id` of the parent message. NULL when the parent isn't in our + * `messages` table (e.g., the original was sent from outside Blooio's pipeline). + */ + fun messageId(messageId: String?) = messageId(JsonField.ofNullable(messageId)) + + /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ + fun messageId(messageId: Optional) = messageId(messageId.getOrNull()) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** Which part of the parent was replied to. 0 for the common single-part case. */ + fun partIndex(partIndex: Long) = partIndex(JsonField.of(partIndex)) + + /** + * Sets [Builder.partIndex] to an arbitrary JSON value. + * + * You should usually call [Builder.partIndex] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun partIndex(partIndex: JsonField) = apply { this.partIndex = partIndex } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = + apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { + additionalProperties.remove(key) + } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ReplyTo]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .guid() + * .messageId() + * .partIndex() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): ReplyTo = + ReplyTo( + checkRequired("guid", guid), + checkRequired("messageId", messageId), + checkRequired("partIndex", partIndex), + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ReplyTo = apply { + if (validated) { + return@apply + } + + guid() + messageId() + partIndex() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (guid.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (if (partIndex.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ReplyTo && + guid == other.guid && + messageId == other.messageId && + partIndex == other.partIndex && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(guid, messageId, partIndex, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ReplyTo{guid=$guid, messageId=$messageId, partIndex=$partIndex, additionalProperties=$additionalProperties}" + } + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` + * = accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to + * Apple/the carrier (protocol resolution happens around here); `delivered` = a delivery + * receipt was received; `failed` = could not be delivered (see `error`); + * `cancellation_requested` = a cancel was requested for a still-queued message + * (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are surfaced via + * webhooks with `received`; read receipts arrive as a `read` event. + */ + class Status @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is + * on an older version than the API, then the API may respond with new members that the + * SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PENDING = of("pending") + + @JvmField val QUEUED = of("queued") + + @JvmField val SENT = of("sent") + + @JvmField val DELIVERED = of("delivered") + + @JvmField val FAILED = of("failed") + + @JvmField val CANCELLATION_REQUESTED = of("cancellation_requested") + + @JvmField val CANCELLED = of("cancelled") + + @JvmStatic fun of(value: String) = Status(JsonField.of(value)) + } + + /** An enum containing [Status]'s known values. */ + enum class Known { + PENDING, + QUEUED, + SENT, + DELIVERED, + FAILED, + CANCELLATION_REQUESTED, + CANCELLED, + } + + /** + * An enum containing [Status]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Status] 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, + QUEUED, + SENT, + DELIVERED, + FAILED, + CANCELLATION_REQUESTED, + CANCELLED, + /** + * An enum member indicating that [Status] 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 + QUEUED -> Value.QUEUED + SENT -> Value.SENT + DELIVERED -> Value.DELIVERED + FAILED -> Value.FAILED + CANCELLATION_REQUESTED -> Value.CANCELLATION_REQUESTED + CANCELLED -> Value.CANCELLED + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PENDING -> Known.PENDING + QUEUED -> Known.QUEUED + SENT -> Known.SENT + DELIVERED -> Known.DELIVERED + FAILED -> Known.FAILED + CANCELLATION_REQUESTED -> Known.CANCELLATION_REQUESTED + CANCELLED -> Known.CANCELLED + else -> throw BlooioInvalidDataException("Unknown Status: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Status = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Status && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Message && + attachments == other.attachments && + direction == other.direction && + error == other.error && + externalId == other.externalId && + internalId == other.internalId && + messageId == other.messageId && + protocol == other.protocol && + reactions == other.reactions && + replyTo == other.replyTo && + sender == other.sender && + status == other.status && + text == other.text && + timeDelivered == other.timeDelivered && + timeSent == other.timeSent && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + attachments, + direction, + error, + externalId, + internalId, + messageId, + protocol, + reactions, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Message{attachments=$attachments, direction=$direction, error=$error, externalId=$externalId, internalId=$internalId, messageId=$messageId, protocol=$protocol, reactions=$reactions, replyTo=$replyTo, sender=$sender, status=$status, text=$text, timeDelivered=$timeDelivered, timeSent=$timeSent, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageListResponse && + chatId == other.chatId && + messages == other.messages && + pagination == other.pagination && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(chatId, messages, pagination, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "MessageListResponse{chatId=$chatId, messages=$messages, pagination=$pagination, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactParams.kt new file mode 100644 index 0000000..f8a3eab --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactParams.kt @@ -0,0 +1,710 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.Params +import com.blooio.api.core.checkRequired +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Add or remove a reaction to a message. Supports classic iMessage tapbacks (love, like, dislike, + * laugh, emphasize, question) and emoji reactions (e.g. +😂, -😂). + * + * The messageId can be an explicit message ID (e.g., msg_xxx) or a relative index (-1 for last + * message, -2 for second-to-last, etc.). When using relative indices, you can optionally filter by + * message direction (inbound/outbound only). + */ +class MessageReactParams +private constructor( + private val chatId: String, + private val messageId: String?, + private val body: Body, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): String = chatId + + fun messageId(): Optional = Optional.ofNullable(messageId) + + /** + * The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. + * + * **Classic tapbacks:** `+love`, `-love`, `+like`, `-like`, `+dislike`, `-dislike`, `+laugh`, + * `-laugh`, `+emphasize`, `-emphasize`, `+question`, `-question` + * + * **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, `+👍`, `-🔥`). + * + * @throws BlooioInvalidDataException 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 reaction(): String = body.reaction() + + /** + * Filter by message direction (only used when messageId is a relative index like -1, -2) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun direction(): Optional = body.direction() + + /** + * Returns the raw JSON value of [reaction]. + * + * Unlike [reaction], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _reaction(): JsonField = body._reaction() + + /** + * Returns the raw JSON value of [direction]. + * + * Unlike [direction], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _direction(): JsonField = body._direction() + + fun _additionalBodyProperties(): Map = body._additionalProperties() + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [MessageReactParams]. + * + * The following fields are required: + * ```java + * .chatId() + * .reaction() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageReactParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var messageId: String? = null + private var body: Body.Builder = Body.builder() + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(messageReactParams: MessageReactParams) = apply { + chatId = messageReactParams.chatId + messageId = messageReactParams.messageId + body = messageReactParams.body.toBuilder() + additionalHeaders = messageReactParams.additionalHeaders.toBuilder() + additionalQueryParams = messageReactParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String) = apply { this.chatId = chatId } + + fun messageId(messageId: String?) = apply { this.messageId = messageId } + + /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ + fun messageId(messageId: Optional) = messageId(messageId.getOrNull()) + + /** + * Sets the entire request body. + * + * This is generally only useful if you are already constructing the body separately. + * Otherwise, it's more convenient to use the top-level setters instead: + * - [reaction] + * - [direction] + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + /** + * The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. + * + * **Classic tapbacks:** `+love`, `-love`, `+like`, `-like`, `+dislike`, `-dislike`, + * `+laugh`, `-laugh`, `+emphasize`, `-emphasize`, `+question`, `-question` + * + * **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, `+👍`, + * `-🔥`). + */ + fun reaction(reaction: String) = apply { body.reaction(reaction) } + + /** + * Sets [Builder.reaction] to an arbitrary JSON value. + * + * You should usually call [Builder.reaction] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun reaction(reaction: JsonField) = apply { body.reaction(reaction) } + + /** + * Filter by message direction (only used when messageId is a relative index like -1, -2) + */ + fun direction(direction: Direction) = apply { body.direction(direction) } + + /** + * Sets [Builder.direction] to an arbitrary JSON value. + * + * You should usually call [Builder.direction] with a well-typed [Direction] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun direction(direction: JsonField) = apply { body.direction(direction) } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + body.additionalProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + body.putAdditionalProperty(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + body.putAllAdditionalProperties(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { body.removeAdditionalProperty(key) } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + body.removeAllAdditionalProperties(keys) + } + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [MessageReactParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .chatId() + * .reaction() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): MessageReactParams = + MessageReactParams( + checkRequired("chatId", chatId), + messageId, + body.build(), + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _body(): Body = body + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId + 1 -> messageId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + class Body + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val reaction: JsonField, + private val direction: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("reaction") + @ExcludeMissing + reaction: JsonField = JsonMissing.of(), + @JsonProperty("direction") + @ExcludeMissing + direction: JsonField = JsonMissing.of(), + ) : this(reaction, direction, mutableMapOf()) + + /** + * The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. + * + * **Classic tapbacks:** `+love`, `-love`, `+like`, `-like`, `+dislike`, `-dislike`, + * `+laugh`, `-laugh`, `+emphasize`, `-emphasize`, `+question`, `-question` + * + * **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, `+👍`, + * `-🔥`). + * + * @throws BlooioInvalidDataException 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 reaction(): String = reaction.getRequired("reaction") + + /** + * Filter by message direction (only used when messageId is a relative index like -1, -2) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun direction(): Optional = direction.getOptional("direction") + + /** + * Returns the raw JSON value of [reaction]. + * + * Unlike [reaction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reaction") @ExcludeMissing fun _reaction(): JsonField = reaction + + /** + * Returns the raw JSON value of [direction]. + * + * Unlike [direction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("direction") + @ExcludeMissing + fun _direction(): JsonField = direction + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [Body]. + * + * The following fields are required: + * ```java + * .reaction() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var reaction: JsonField? = null + private var direction: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + reaction = body.reaction + direction = body.direction + additionalProperties = body.additionalProperties.toMutableMap() + } + + /** + * The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. + * + * **Classic tapbacks:** `+love`, `-love`, `+like`, `-like`, `+dislike`, `-dislike`, + * `+laugh`, `-laugh`, `+emphasize`, `-emphasize`, `+question`, `-question` + * + * **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, `+👍`, + * `-🔥`). + */ + fun reaction(reaction: String) = reaction(JsonField.of(reaction)) + + /** + * Sets [Builder.reaction] to an arbitrary JSON value. + * + * You should usually call [Builder.reaction] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun reaction(reaction: JsonField) = apply { this.reaction = reaction } + + /** + * Filter by message direction (only used when messageId is a relative index like -1, + * -2) + */ + fun direction(direction: Direction) = direction(JsonField.of(direction)) + + /** + * Sets [Builder.direction] to an arbitrary JSON value. + * + * You should usually call [Builder.direction] with a well-typed [Direction] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun direction(direction: JsonField) = apply { this.direction = direction } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Body]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .reaction() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): Body = + Body( + checkRequired("reaction", reaction), + direction, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Body = apply { + if (validated) { + return@apply + } + + reaction() + direction().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (reaction.asKnown().isPresent) 1 else 0) + + (direction.asKnown().getOrNull()?.validity() ?: 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Body && + reaction == other.reaction && + direction == other.direction && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(reaction, direction, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{reaction=$reaction, direction=$direction, additionalProperties=$additionalProperties}" + } + + /** Filter by message direction (only used when messageId is a relative index like -1, -2) */ + class Direction @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val INBOUND = of("inbound") + + @JvmField val OUTBOUND = of("outbound") + + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) + } + + /** An enum containing [Direction]'s known values. */ + enum class Known { + INBOUND, + OUTBOUND, + } + + /** + * An enum containing [Direction]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Direction] 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 { + INBOUND, + OUTBOUND, + /** + * An enum member indicating that [Direction] 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) { + INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND + else -> throw BlooioInvalidDataException("Unknown Direction: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Direction = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Direction && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageReactParams && + chatId == other.chatId && + messageId == other.messageId && + body == other.body && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(chatId, messageId, body, additionalHeaders, additionalQueryParams) + + override fun toString() = + "MessageReactParams{chatId=$chatId, messageId=$messageId, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactResponse.kt new file mode 100644 index 0000000..1c6f1e0 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageReactResponse.kt @@ -0,0 +1,415 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class MessageReactResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val action: JsonField, + private val messageId: JsonField, + private val reaction: JsonField, + private val success: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("action") @ExcludeMissing action: JsonField = JsonMissing.of(), + @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), + @JsonProperty("reaction") @ExcludeMissing reaction: JsonField = JsonMissing.of(), + @JsonProperty("success") @ExcludeMissing success: JsonField = JsonMissing.of(), + ) : this(action, messageId, reaction, success, mutableMapOf()) + + /** + * The action that was performed + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun action(): Optional = action.getOptional("action") + + /** + * The ID of the message that was reacted to + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * The reaction that was added or removed. For classic tapbacks: love, like, dislike, laugh, + * emphasize, question. For emoji reactions: the emoji character (e.g. 😂, 👍, 🔥). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun reaction(): Optional = reaction.getOptional("reaction") + + /** + * Whether the reaction was sent successfully + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun success(): Optional = success.getOptional("success") + + /** + * Returns the raw JSON value of [action]. + * + * Unlike [action], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("action") @ExcludeMissing fun _action(): JsonField = action + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [reaction]. + * + * Unlike [reaction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reaction") @ExcludeMissing fun _reaction(): JsonField = reaction + + /** + * Returns the raw JSON value of [success]. + * + * Unlike [success], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("success") @ExcludeMissing fun _success(): JsonField = success + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [MessageReactResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageReactResponse]. */ + class Builder internal constructor() { + + private var action: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var reaction: JsonField = JsonMissing.of() + private var success: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(messageReactResponse: MessageReactResponse) = apply { + action = messageReactResponse.action + messageId = messageReactResponse.messageId + reaction = messageReactResponse.reaction + success = messageReactResponse.success + additionalProperties = messageReactResponse.additionalProperties.toMutableMap() + } + + /** The action that was performed */ + fun action(action: Action) = action(JsonField.of(action)) + + /** + * Sets [Builder.action] to an arbitrary JSON value. + * + * You should usually call [Builder.action] with a well-typed [Action] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun action(action: JsonField) = apply { this.action = action } + + /** The ID of the message that was reacted to */ + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** + * The reaction that was added or removed. For classic tapbacks: love, like, dislike, laugh, + * emphasize, question. For emoji reactions: the emoji character (e.g. 😂, 👍, 🔥). + */ + fun reaction(reaction: String) = reaction(JsonField.of(reaction)) + + /** + * Sets [Builder.reaction] to an arbitrary JSON value. + * + * You should usually call [Builder.reaction] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun reaction(reaction: JsonField) = apply { this.reaction = reaction } + + /** Whether the reaction was sent successfully */ + fun success(success: Boolean) = success(JsonField.of(success)) + + /** + * Sets [Builder.success] to an arbitrary JSON value. + * + * You should usually call [Builder.success] with a well-typed [Boolean] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun success(success: JsonField) = apply { this.success = success } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [MessageReactResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageReactResponse = + MessageReactResponse( + action, + messageId, + reaction, + success, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): MessageReactResponse = apply { + if (validated) { + return@apply + } + + action().ifPresent { it.validate() } + messageId() + reaction() + success() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (action.asKnown().getOrNull()?.validity() ?: 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (if (reaction.asKnown().isPresent) 1 else 0) + + (if (success.asKnown().isPresent) 1 else 0) + + /** The action that was performed */ + class Action @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val ADD = of("add") + + @JvmField val REMOVE = of("remove") + + @JvmStatic fun of(value: String) = Action(JsonField.of(value)) + } + + /** An enum containing [Action]'s known values. */ + enum class Known { + ADD, + REMOVE, + } + + /** + * An enum containing [Action]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Action] 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 { + ADD, + REMOVE, + /** An enum member indicating that [Action] 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) { + ADD -> Value.ADD + REMOVE -> Value.REMOVE + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + ADD -> Known.ADD + REMOVE -> Known.REMOVE + else -> throw BlooioInvalidDataException("Unknown Action: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Action = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Action && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageReactResponse && + action == other.action && + messageId == other.messageId && + reaction == other.reaction && + success == other.success && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(action, messageId, reaction, success, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "MessageReactResponse{action=$action, messageId=$messageId, reaction=$reaction, success=$success, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveParams.kt similarity index 84% rename from blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveParams.kt rename to blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveParams.kt index aeea016..bcba649 100644 --- a/blooio-java-core/src/main/kotlin/com/blooio/api/models/messages/MessageRetrieveParams.kt +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveParams.kt @@ -1,25 +1,26 @@ // File generated from our OpenAPI spec by Stainless. -package com.blooio.api.models.messages +package com.blooio.api.models.chats.messages import com.blooio.api.core.Params +import com.blooio.api.core.checkRequired import com.blooio.api.core.http.Headers import com.blooio.api.core.http.QueryParams import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull -/** - * Retrieve full message metadata including direction, protocol, text length, attachments count, - * timestamps, current status, and original metadata. - */ +/** Get details for a specific message. */ class MessageRetrieveParams private constructor( + private val chatId: String, private val messageId: String?, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { + fun chatId(): String = chatId + fun messageId(): Optional = Optional.ofNullable(messageId) /** Additional headers to send with the request. */ @@ -32,26 +33,35 @@ private constructor( companion object { - @JvmStatic fun none(): MessageRetrieveParams = builder().build() - - /** Returns a mutable builder for constructing an instance of [MessageRetrieveParams]. */ + /** + * Returns a mutable builder for constructing an instance of [MessageRetrieveParams]. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + */ @JvmStatic fun builder() = Builder() } /** A builder for [MessageRetrieveParams]. */ class Builder internal constructor() { + private var chatId: String? = null private var messageId: String? = null private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() @JvmSynthetic internal fun from(messageRetrieveParams: MessageRetrieveParams) = apply { + chatId = messageRetrieveParams.chatId messageId = messageRetrieveParams.messageId additionalHeaders = messageRetrieveParams.additionalHeaders.toBuilder() additionalQueryParams = messageRetrieveParams.additionalQueryParams.toBuilder() } + fun chatId(chatId: String) = apply { this.chatId = chatId } + fun messageId(messageId: String?) = apply { this.messageId = messageId } /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ @@ -159,9 +169,17 @@ private constructor( * Returns an immutable instance of [MessageRetrieveParams]. * * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + * + * @throws IllegalStateException if any required field is unset. */ fun build(): MessageRetrieveParams = MessageRetrieveParams( + checkRequired("chatId", chatId), messageId, additionalHeaders.build(), additionalQueryParams.build(), @@ -170,7 +188,8 @@ private constructor( fun _pathParam(index: Int): String = when (index) { - 0 -> messageId ?: "" + 0 -> chatId + 1 -> messageId ?: "" else -> "" } @@ -184,13 +203,15 @@ private constructor( } return other is MessageRetrieveParams && + chatId == other.chatId && messageId == other.messageId && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams } - override fun hashCode(): Int = Objects.hash(messageId, additionalHeaders, additionalQueryParams) + override fun hashCode(): Int = + Objects.hash(chatId, messageId, additionalHeaders, additionalQueryParams) override fun toString() = - "MessageRetrieveParams{messageId=$messageId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "MessageRetrieveParams{chatId=$chatId, messageId=$messageId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveResponse.kt new file mode 100644 index 0000000..f72a5d9 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageRetrieveResponse.kt @@ -0,0 +1,1739 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.checkKnown +import com.blooio.api.core.checkRequired +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class MessageRetrieveResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val attachments: JsonField>, + private val chatId: JsonField, + private val contact: JsonField, + private val direction: JsonField, + private val error: JsonField, + private val internalId: JsonField, + private val messageId: JsonField, + private val protocol: JsonField, + private val reactions: JsonField>, + private val replyTo: JsonField, + private val sender: JsonField, + private val status: JsonField, + private val text: JsonField, + private val timeDelivered: JsonField, + private val timeSent: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("attachments") + @ExcludeMissing + attachments: JsonField> = JsonMissing.of(), + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("contact") @ExcludeMissing contact: JsonField = JsonMissing.of(), + @JsonProperty("direction") + @ExcludeMissing + direction: JsonField = JsonMissing.of(), + @JsonProperty("error") @ExcludeMissing error: JsonField = JsonMissing.of(), + @JsonProperty("internal_id") + @ExcludeMissing + internalId: JsonField = JsonMissing.of(), + @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), + @JsonProperty("protocol") @ExcludeMissing protocol: JsonField = JsonMissing.of(), + @JsonProperty("reactions") + @ExcludeMissing + reactions: JsonField> = JsonMissing.of(), + @JsonProperty("reply_to") @ExcludeMissing replyTo: JsonField = JsonMissing.of(), + @JsonProperty("sender") @ExcludeMissing sender: JsonField = JsonMissing.of(), + @JsonProperty("status") @ExcludeMissing status: JsonField = JsonMissing.of(), + @JsonProperty("text") @ExcludeMissing text: JsonField = JsonMissing.of(), + @JsonProperty("time_delivered") + @ExcludeMissing + timeDelivered: JsonField = JsonMissing.of(), + @JsonProperty("time_sent") @ExcludeMissing timeSent: JsonField = JsonMissing.of(), + ) : this( + attachments, + chatId, + contact, + direction, + error, + internalId, + messageId, + protocol, + reactions, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + mutableMapOf(), + ) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun attachments(): Optional> = attachments.getOptional("attachments") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chatId(): Optional = chatId.getOptional("chat_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun contact(): Optional = contact.getOptional("contact") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun direction(): Optional = direction.getOptional("direction") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun error(): Optional = error.getOptional("error") + + /** + * Organization phone number (from-number) used for this message + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun internalId(): Optional = internalId.getOptional("internal_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, wire + * service not resolved yet (settles within seconds of send); `imessage` = delivered over + * iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green + * bubble); `unknown` = accepted by the carrier but the wire service could not be resolved + * before the tracking window closed (see `error`). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun protocol(): Optional = protocol.getOptional("protocol") + + /** + * Reactions on this message (tapbacks and emoji reactions) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun reactions(): Optional> = reactions.getOptional("reactions") + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on every + * GET endpoint that returns a single message or a list of messages. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun replyTo(): Optional = replyTo.getOptional("reply_to") + + /** + * Sender's phone number or email for inbound group messages. Null for outbound messages and 1-1 + * chats. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun sender(): Optional = sender.getOptional("sender") + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` = + * accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + * carrier (protocol resolution happens around here); `delivered` = a delivery receipt was + * received; `failed` = could not be delivered (see `error`); `cancellation_requested` = a + * cancel was requested for a still-queued message (best-effort); `cancelled` = cancelled before + * dispatch. Inbound messages are surfaced via webhooks with `received`; read receipts arrive as + * a `read` event. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun status(): Optional = status.getOptional("status") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = text.getOptional("text") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeDelivered(): Optional = timeDelivered.getOptional("time_delivered") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeSent(): Optional = timeSent.getOptional("time_sent") + + /** + * Returns the raw JSON value of [attachments]. + * + * Unlike [attachments], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("attachments") + @ExcludeMissing + fun _attachments(): JsonField> = attachments + + /** + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [contact]. + * + * Unlike [contact], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("contact") @ExcludeMissing fun _contact(): JsonField = contact + + /** + * Returns the raw JSON value of [direction]. + * + * Unlike [direction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("direction") @ExcludeMissing fun _direction(): JsonField = direction + + /** + * Returns the raw JSON value of [error]. + * + * Unlike [error], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("error") @ExcludeMissing fun _error(): JsonField = error + + /** + * Returns the raw JSON value of [internalId]. + * + * Unlike [internalId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("internal_id") @ExcludeMissing fun _internalId(): JsonField = internalId + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [protocol]. + * + * Unlike [protocol], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("protocol") @ExcludeMissing fun _protocol(): JsonField = protocol + + /** + * Returns the raw JSON value of [reactions]. + * + * Unlike [reactions], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reactions") + @ExcludeMissing + fun _reactions(): JsonField> = reactions + + /** + * Returns the raw JSON value of [replyTo]. + * + * Unlike [replyTo], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reply_to") @ExcludeMissing fun _replyTo(): JsonField = replyTo + + /** + * Returns the raw JSON value of [sender]. + * + * Unlike [sender], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("sender") @ExcludeMissing fun _sender(): JsonField = sender + + /** + * 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 + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("text") @ExcludeMissing fun _text(): JsonField = text + + /** + * Returns the raw JSON value of [timeDelivered]. + * + * Unlike [timeDelivered], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("time_delivered") + @ExcludeMissing + fun _timeDelivered(): JsonField = timeDelivered + + /** + * Returns the raw JSON value of [timeSent]. + * + * Unlike [timeSent], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("time_sent") @ExcludeMissing fun _timeSent(): JsonField = timeSent + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [MessageRetrieveResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageRetrieveResponse]. */ + class Builder internal constructor() { + + private var attachments: JsonField>? = null + private var chatId: JsonField = JsonMissing.of() + private var contact: JsonField = JsonMissing.of() + private var direction: JsonField = JsonMissing.of() + private var error: JsonField = JsonMissing.of() + private var internalId: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var protocol: JsonField = JsonMissing.of() + private var reactions: JsonField>? = null + private var replyTo: JsonField = JsonMissing.of() + private var sender: JsonField = JsonMissing.of() + private var status: JsonField = JsonMissing.of() + private var text: JsonField = JsonMissing.of() + private var timeDelivered: JsonField = JsonMissing.of() + private var timeSent: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(messageRetrieveResponse: MessageRetrieveResponse) = apply { + attachments = messageRetrieveResponse.attachments.map { it.toMutableList() } + chatId = messageRetrieveResponse.chatId + contact = messageRetrieveResponse.contact + direction = messageRetrieveResponse.direction + error = messageRetrieveResponse.error + internalId = messageRetrieveResponse.internalId + messageId = messageRetrieveResponse.messageId + protocol = messageRetrieveResponse.protocol + reactions = messageRetrieveResponse.reactions.map { it.toMutableList() } + replyTo = messageRetrieveResponse.replyTo + sender = messageRetrieveResponse.sender + status = messageRetrieveResponse.status + text = messageRetrieveResponse.text + timeDelivered = messageRetrieveResponse.timeDelivered + timeSent = messageRetrieveResponse.timeSent + additionalProperties = messageRetrieveResponse.additionalProperties.toMutableMap() + } + + fun attachments(attachments: List) = attachments(JsonField.of(attachments)) + + /** + * Sets [Builder.attachments] to an arbitrary JSON value. + * + * You should usually call [Builder.attachments] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun attachments(attachments: JsonField>) = apply { + this.attachments = attachments.map { it.toMutableList() } + } + + /** + * Adds a single [JsonValue] to [attachments]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addAttachment(attachment: JsonValue) = apply { + attachments = + (attachments ?: JsonField.of(mutableListOf())).also { + checkKnown("attachments", it).add(attachment) + } + } + + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) + + /** + * Sets [Builder.chatId] to an arbitrary JSON value. + * + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + fun contact(contact: Contact?) = contact(JsonField.ofNullable(contact)) + + /** Alias for calling [Builder.contact] with `contact.orElse(null)`. */ + fun contact(contact: Optional) = contact(contact.getOrNull()) + + /** + * Sets [Builder.contact] to an arbitrary JSON value. + * + * You should usually call [Builder.contact] with a well-typed [Contact] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun contact(contact: JsonField) = apply { this.contact = contact } + + fun direction(direction: Direction) = direction(JsonField.of(direction)) + + /** + * Sets [Builder.direction] to an arbitrary JSON value. + * + * You should usually call [Builder.direction] with a well-typed [Direction] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun direction(direction: JsonField) = apply { this.direction = direction } + + fun error(error: String?) = error(JsonField.ofNullable(error)) + + /** Alias for calling [Builder.error] with `error.orElse(null)`. */ + fun error(error: Optional) = error(error.getOrNull()) + + /** + * Sets [Builder.error] to an arbitrary JSON value. + * + * You should usually call [Builder.error] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun error(error: JsonField) = apply { this.error = error } + + /** Organization phone number (from-number) used for this message */ + fun internalId(internalId: String?) = internalId(JsonField.ofNullable(internalId)) + + /** Alias for calling [Builder.internalId] with `internalId.orElse(null)`. */ + fun internalId(internalId: Optional) = internalId(internalId.getOrNull()) + + /** + * Sets [Builder.internalId] to an arbitrary JSON value. + * + * You should usually call [Builder.internalId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun internalId(internalId: JsonField) = apply { this.internalId = internalId } + + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, + * wire service not resolved yet (settles within seconds of send); `imessage` = delivered + * over iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS + * (green bubble); `unknown` = accepted by the carrier but the wire service could not be + * resolved before the tracking window closed (see `error`). + */ + fun protocol(protocol: Protocol) = protocol(JsonField.of(protocol)) + + /** + * Sets [Builder.protocol] to an arbitrary JSON value. + * + * You should usually call [Builder.protocol] with a well-typed [Protocol] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun protocol(protocol: JsonField) = apply { this.protocol = protocol } + + /** Reactions on this message (tapbacks and emoji reactions) */ + fun reactions(reactions: List) = reactions(JsonField.of(reactions)) + + /** + * Sets [Builder.reactions] to an arbitrary JSON value. + * + * You should usually call [Builder.reactions] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun reactions(reactions: JsonField>) = apply { + this.reactions = reactions.map { it.toMutableList() } + } + + /** + * Adds a single [Reaction] to [reactions]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addReaction(reaction: Reaction) = apply { + reactions = + (reactions ?: JsonField.of(mutableListOf())).also { + checkKnown("reactions", it).add(reaction) + } + } + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on + * every GET endpoint that returns a single message or a list of messages. + */ + fun replyTo(replyTo: ReplyTo?) = replyTo(JsonField.ofNullable(replyTo)) + + /** Alias for calling [Builder.replyTo] with `replyTo.orElse(null)`. */ + fun replyTo(replyTo: Optional) = replyTo(replyTo.getOrNull()) + + /** + * Sets [Builder.replyTo] to an arbitrary JSON value. + * + * You should usually call [Builder.replyTo] with a well-typed [ReplyTo] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun replyTo(replyTo: JsonField) = apply { this.replyTo = replyTo } + + /** + * Sender's phone number or email for inbound group messages. Null for outbound messages and + * 1-1 chats. + */ + fun sender(sender: String?) = sender(JsonField.ofNullable(sender)) + + /** Alias for calling [Builder.sender] with `sender.orElse(null)`. */ + fun sender(sender: Optional) = sender(sender.getOrNull()) + + /** + * Sets [Builder.sender] to an arbitrary JSON value. + * + * You should usually call [Builder.sender] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun sender(sender: JsonField) = apply { this.sender = sender } + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` + * = accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to + * Apple/the carrier (protocol resolution happens around here); `delivered` = a delivery + * receipt was received; `failed` = could not be delivered (see `error`); + * `cancellation_requested` = a cancel was requested for a still-queued message + * (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are surfaced via + * webhooks with `received`; read receipts arrive as a `read` event. + */ + fun status(status: Status?) = status(JsonField.ofNullable(status)) + + /** Alias for calling [Builder.status] with `status.orElse(null)`. */ + fun status(status: Optional) = status(status.getOrNull()) + + /** + * Sets [Builder.status] to an arbitrary JSON value. + * + * You should usually call [Builder.status] with a well-typed [Status] 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 text(text: String?) = text(JsonField.ofNullable(text)) + + /** Alias for calling [Builder.text] with `text.orElse(null)`. */ + fun text(text: Optional) = text(text.getOrNull()) + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun text(text: JsonField) = apply { this.text = text } + + fun timeDelivered(timeDelivered: Long?) = timeDelivered(JsonField.ofNullable(timeDelivered)) + + /** + * Alias for [Builder.timeDelivered]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun timeDelivered(timeDelivered: Long) = timeDelivered(timeDelivered as Long?) + + /** Alias for calling [Builder.timeDelivered] with `timeDelivered.orElse(null)`. */ + fun timeDelivered(timeDelivered: Optional) = timeDelivered(timeDelivered.getOrNull()) + + /** + * Sets [Builder.timeDelivered] to an arbitrary JSON value. + * + * You should usually call [Builder.timeDelivered] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun timeDelivered(timeDelivered: JsonField) = apply { + this.timeDelivered = timeDelivered + } + + fun timeSent(timeSent: Long) = timeSent(JsonField.of(timeSent)) + + /** + * Sets [Builder.timeSent] to an arbitrary JSON value. + * + * You should usually call [Builder.timeSent] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun timeSent(timeSent: JsonField) = apply { this.timeSent = timeSent } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [MessageRetrieveResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageRetrieveResponse = + MessageRetrieveResponse( + (attachments ?: JsonMissing.of()).map { it.toImmutable() }, + chatId, + contact, + direction, + error, + internalId, + messageId, + protocol, + (reactions ?: JsonMissing.of()).map { it.toImmutable() }, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): MessageRetrieveResponse = apply { + if (validated) { + return@apply + } + + attachments() + chatId() + contact().ifPresent { it.validate() } + direction().ifPresent { it.validate() } + error() + internalId() + messageId() + protocol().ifPresent { it.validate() } + reactions().ifPresent { it.forEach { it.validate() } } + replyTo().ifPresent { it.validate() } + sender() + status().ifPresent { it.validate() } + text() + timeDelivered() + timeSent() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (attachments.asKnown().getOrNull()?.size ?: 0) + + (if (chatId.asKnown().isPresent) 1 else 0) + + (contact.asKnown().getOrNull()?.validity() ?: 0) + + (direction.asKnown().getOrNull()?.validity() ?: 0) + + (if (error.asKnown().isPresent) 1 else 0) + + (if (internalId.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (protocol.asKnown().getOrNull()?.validity() ?: 0) + + (reactions.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (replyTo.asKnown().getOrNull()?.validity() ?: 0) + + (if (sender.asKnown().isPresent) 1 else 0) + + (status.asKnown().getOrNull()?.validity() ?: 0) + + (if (text.asKnown().isPresent) 1 else 0) + + (if (timeDelivered.asKnown().isPresent) 1 else 0) + + (if (timeSent.asKnown().isPresent) 1 else 0) + + class Contact + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val contactId: JsonField, + private val identifier: JsonField, + private val name: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("contact_id") + @ExcludeMissing + contactId: JsonField = JsonMissing.of(), + @JsonProperty("identifier") + @ExcludeMissing + identifier: JsonField = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + ) : this(contactId, identifier, name, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun contactId(): Optional = contactId.getOptional("contact_id") + + /** + * The contact's phone number or email + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun identifier(): Optional = identifier.getOptional("identifier") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun name(): Optional = name.getOptional("name") + + /** + * Returns the raw JSON value of [contactId]. + * + * Unlike [contactId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("contact_id") @ExcludeMissing fun _contactId(): JsonField = contactId + + /** + * Returns the raw JSON value of [identifier]. + * + * Unlike [identifier], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("identifier") + @ExcludeMissing + fun _identifier(): JsonField = identifier + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Contact]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Contact]. */ + class Builder internal constructor() { + + private var contactId: JsonField = JsonMissing.of() + private var identifier: JsonField = JsonMissing.of() + private var name: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(contact: Contact) = apply { + contactId = contact.contactId + identifier = contact.identifier + name = contact.name + additionalProperties = contact.additionalProperties.toMutableMap() + } + + fun contactId(contactId: String) = contactId(JsonField.of(contactId)) + + /** + * Sets [Builder.contactId] to an arbitrary JSON value. + * + * You should usually call [Builder.contactId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun contactId(contactId: JsonField) = apply { this.contactId = contactId } + + /** The contact's phone number or email */ + fun identifier(identifier: String) = identifier(JsonField.of(identifier)) + + /** + * Sets [Builder.identifier] to an arbitrary JSON value. + * + * You should usually call [Builder.identifier] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun identifier(identifier: JsonField) = apply { this.identifier = identifier } + + fun name(name: String?) = name(JsonField.ofNullable(name)) + + /** Alias for calling [Builder.name] with `name.orElse(null)`. */ + fun name(name: Optional) = name(name.getOrNull()) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun name(name: JsonField) = apply { this.name = name } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Contact]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Contact = + Contact(contactId, identifier, name, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Contact = apply { + if (validated) { + return@apply + } + + contactId() + identifier() + name() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (contactId.asKnown().isPresent) 1 else 0) + + (if (identifier.asKnown().isPresent) 1 else 0) + + (if (name.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Contact && + contactId == other.contactId && + identifier == other.identifier && + name == other.name && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(contactId, identifier, name, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Contact{contactId=$contactId, identifier=$identifier, name=$name, additionalProperties=$additionalProperties}" + } + + class Direction @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val INBOUND = of("inbound") + + @JvmField val OUTBOUND = of("outbound") + + @JvmStatic fun of(value: String) = Direction(JsonField.of(value)) + } + + /** An enum containing [Direction]'s known values. */ + enum class Known { + INBOUND, + OUTBOUND, + } + + /** + * An enum containing [Direction]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Direction] 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 { + INBOUND, + OUTBOUND, + /** + * An enum member indicating that [Direction] 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) { + INBOUND -> Value.INBOUND + OUTBOUND -> Value.OUTBOUND + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + INBOUND -> Known.INBOUND + OUTBOUND -> Known.OUTBOUND + else -> throw BlooioInvalidDataException("Unknown Direction: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Direction = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Direction && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** + * Transport used to carry the message; never null. `pending` = accepted and dispatched, wire + * service not resolved yet (settles within seconds of send); `imessage` = delivered over + * iMessage (blue bubble); `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green + * bubble); `unknown` = accepted by the carrier but the wire service could not be resolved + * before the tracking window closed (see `error`). + */ + class Protocol @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PENDING = of("pending") + + @JvmField val UNKNOWN = of("unknown") + + @JvmField val IMESSAGE = of("imessage") + + @JvmField val SMS = of("sms") + + @JvmField val RCS = of("rcs") + + @JvmStatic fun of(value: String) = Protocol(JsonField.of(value)) + } + + /** An enum containing [Protocol]'s known values. */ + enum class Known { + PENDING, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + } + + /** + * An enum containing [Protocol]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Protocol] 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, + UNKNOWN, + IMESSAGE, + SMS, + RCS, + /** An enum member indicating that [Protocol] 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 + UNKNOWN -> Value.UNKNOWN + IMESSAGE -> Value.IMESSAGE + SMS -> Value.SMS + RCS -> Value.RCS + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PENDING -> Known.PENDING + UNKNOWN -> Known.UNKNOWN + IMESSAGE -> Known.IMESSAGE + SMS -> Known.SMS + RCS -> Known.RCS + else -> throw BlooioInvalidDataException("Unknown Protocol: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Protocol = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Protocol && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** + * Inline-reply parent reference. Identical shape on `message.received` webhooks and on every + * GET endpoint that returns a single message or a list of messages. + */ + class ReplyTo + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val guid: JsonField, + private val messageId: JsonField, + private val partIndex: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("guid") @ExcludeMissing guid: JsonField = JsonMissing.of(), + @JsonProperty("message_id") + @ExcludeMissing + messageId: JsonField = JsonMissing.of(), + @JsonProperty("part_index") + @ExcludeMissing + partIndex: JsonField = JsonMissing.of(), + ) : this(guid, messageId, partIndex, mutableMapOf()) + + /** + * The raw iMessage GUID of the parent. Always populated on real inline replies; the + * on-device record-of-truth identifier that survives even when `message_id` cannot be + * resolved. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun guid(): Optional = guid.getOptional("guid") + + /** + * The Blooio `message_id` of the parent message. NULL when the parent isn't in our + * `messages` table (e.g., the original was sent from outside Blooio's pipeline). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * Which part of the parent was replied to. 0 for the common single-part case. + * + * @throws BlooioInvalidDataException 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 partIndex(): Long = partIndex.getRequired("part_index") + + /** + * Returns the raw JSON value of [guid]. + * + * Unlike [guid], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("guid") @ExcludeMissing fun _guid(): JsonField = guid + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [partIndex]. + * + * Unlike [partIndex], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("part_index") @ExcludeMissing fun _partIndex(): JsonField = partIndex + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [ReplyTo]. + * + * The following fields are required: + * ```java + * .guid() + * .messageId() + * .partIndex() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ReplyTo]. */ + class Builder internal constructor() { + + private var guid: JsonField? = null + private var messageId: JsonField? = null + private var partIndex: JsonField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(replyTo: ReplyTo) = apply { + guid = replyTo.guid + messageId = replyTo.messageId + partIndex = replyTo.partIndex + additionalProperties = replyTo.additionalProperties.toMutableMap() + } + + /** + * The raw iMessage GUID of the parent. Always populated on real inline replies; the + * on-device record-of-truth identifier that survives even when `message_id` cannot be + * resolved. + */ + fun guid(guid: String?) = guid(JsonField.ofNullable(guid)) + + /** Alias for calling [Builder.guid] with `guid.orElse(null)`. */ + fun guid(guid: Optional) = guid(guid.getOrNull()) + + /** + * Sets [Builder.guid] to an arbitrary JSON value. + * + * You should usually call [Builder.guid] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun guid(guid: JsonField) = apply { this.guid = guid } + + /** + * The Blooio `message_id` of the parent message. NULL when the parent isn't in our + * `messages` table (e.g., the original was sent from outside Blooio's pipeline). + */ + fun messageId(messageId: String?) = messageId(JsonField.ofNullable(messageId)) + + /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ + fun messageId(messageId: Optional) = messageId(messageId.getOrNull()) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** Which part of the parent was replied to. 0 for the common single-part case. */ + fun partIndex(partIndex: Long) = partIndex(JsonField.of(partIndex)) + + /** + * Sets [Builder.partIndex] to an arbitrary JSON value. + * + * You should usually call [Builder.partIndex] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun partIndex(partIndex: JsonField) = apply { this.partIndex = partIndex } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ReplyTo]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .guid() + * .messageId() + * .partIndex() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): ReplyTo = + ReplyTo( + checkRequired("guid", guid), + checkRequired("messageId", messageId), + checkRequired("partIndex", partIndex), + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ReplyTo = apply { + if (validated) { + return@apply + } + + guid() + messageId() + partIndex() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (guid.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (if (partIndex.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ReplyTo && + guid == other.guid && + messageId == other.messageId && + partIndex == other.partIndex && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(guid, messageId, partIndex, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ReplyTo{guid=$guid, messageId=$messageId, partIndex=$partIndex, additionalProperties=$additionalProperties}" + } + + /** + * Delivery lifecycle state. `pending` = persisted and being prepared for dispatch; `queued` = + * accepted and waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + * carrier (protocol resolution happens around here); `delivered` = a delivery receipt was + * received; `failed` = could not be delivered (see `error`); `cancellation_requested` = a + * cancel was requested for a still-queued message (best-effort); `cancelled` = cancelled before + * dispatch. Inbound messages are surfaced via webhooks with `received`; read receipts arrive as + * a `read` event. + */ + class Status @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val PENDING = of("pending") + + @JvmField val QUEUED = of("queued") + + @JvmField val SENT = of("sent") + + @JvmField val DELIVERED = of("delivered") + + @JvmField val FAILED = of("failed") + + @JvmField val CANCELLATION_REQUESTED = of("cancellation_requested") + + @JvmField val CANCELLED = of("cancelled") + + @JvmStatic fun of(value: String) = Status(JsonField.of(value)) + } + + /** An enum containing [Status]'s known values. */ + enum class Known { + PENDING, + QUEUED, + SENT, + DELIVERED, + FAILED, + CANCELLATION_REQUESTED, + CANCELLED, + } + + /** + * An enum containing [Status]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Status] 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, + QUEUED, + SENT, + DELIVERED, + FAILED, + CANCELLATION_REQUESTED, + CANCELLED, + /** An enum member indicating that [Status] 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 + QUEUED -> Value.QUEUED + SENT -> Value.SENT + DELIVERED -> Value.DELIVERED + FAILED -> Value.FAILED + CANCELLATION_REQUESTED -> Value.CANCELLATION_REQUESTED + CANCELLED -> Value.CANCELLED + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PENDING -> Known.PENDING + QUEUED -> Known.QUEUED + SENT -> Known.SENT + DELIVERED -> Known.DELIVERED + FAILED -> Known.FAILED + CANCELLATION_REQUESTED -> Known.CANCELLATION_REQUESTED + CANCELLED -> Known.CANCELLED + else -> throw BlooioInvalidDataException("Unknown Status: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Status = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Status && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageRetrieveResponse && + attachments == other.attachments && + chatId == other.chatId && + contact == other.contact && + direction == other.direction && + error == other.error && + internalId == other.internalId && + messageId == other.messageId && + protocol == other.protocol && + reactions == other.reactions && + replyTo == other.replyTo && + sender == other.sender && + status == other.status && + text == other.text && + timeDelivered == other.timeDelivered && + timeSent == other.timeSent && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + attachments, + chatId, + contact, + direction, + error, + internalId, + messageId, + protocol, + reactions, + replyTo, + sender, + status, + text, + timeDelivered, + timeSent, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "MessageRetrieveResponse{attachments=$attachments, chatId=$chatId, contact=$contact, direction=$direction, error=$error, internalId=$internalId, messageId=$messageId, protocol=$protocol, reactions=$reactions, replyTo=$replyTo, sender=$sender, status=$status, text=$text, timeDelivered=$timeDelivered, timeSent=$timeSent, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendParams.kt new file mode 100644 index 0000000..4de8d50 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendParams.kt @@ -0,0 +1,2786 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.BaseDeserializer +import com.blooio.api.core.BaseSerializer +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.Params +import com.blooio.api.core.allMaxBy +import com.blooio.api.core.checkKnown +import com.blooio.api.core.checkRequired +import com.blooio.api.core.getOrThrow +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Send a message to a chat. The chatId can be: (1) E.164 phone number, (2) email address, (3) group + * ID (grp_xxxx), or (4) comma-separated list of phone/email for multi-recipient chats. For + * multi-recipient, an unnamed group is automatically created or reused if the exact participant + * combination already exists. For explicit groups, the group must be linked to an existing iMessage + * chat. + * + * **iMessage send-with-effect:** set the optional `effect` field to attach an Apple expressive send + * (slam, loud, gentle, invisible-ink) or screen effect (echo, spotlight, balloons, confetti, love, + * lasers, fireworks, celebration). Effects are an iMessage-only feature — when the recipient is on + * SMS/RCS the message is delivered without the animation. Effects are not supported in multipart + * (`parts`) mode. + * + * **Threaded replies (iMessage inline reply):** set the optional `reply_to` field to send the + * outgoing message as a reply to a specific earlier message. Two shapes are accepted: `{ + * "message_id": "msg_…" }` references a Blooio-minted message in the same chat (most common — the + * message_id returned by an earlier send or surfaced on a `message.received` webhook), or `{ + * "guid": "…", "part_index": 0 }` references the raw iMessage GUID for the rare case where the + * parent wasn't recorded by Blooio. The reply must target the same chat and the same from-number as + * the new send, and the parent must be no older than 30 days (the iMessage on-device retention + * horizon). Reply support is iMessage-only and is rejected on Twilio, dashboard-Twilio, and hybrid + * send paths; it's also rejected on multi-message fan-outs (`text` array or per-part URL-balloon + * batch). See the `400` responses for the full set of `reply_target_*` error codes. + */ +class MessageSendParams +private constructor( + private val chatId: String?, + private val idempotencyKey: String?, + private val body: Body, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): Optional = Optional.ofNullable(chatId) + + fun idempotencyKey(): Optional = Optional.ofNullable(idempotencyKey) + + /** + * Array of attachment URLs or objects with url/name + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun attachments(): Optional> = body.attachments() + + /** + * Optional. Attach an iMessage send-with-effect to the outgoing message. + * + * **Bubble effects** (apply to a single text bubble): + * - `slam` — Slam + * - `loud` — Loud + * - `gentle` — Gentle + * - `invisible-ink` — Invisible Ink + * + * **Screen effects** (full-screen animation in the recipient's chat): + * - `echo` — Echo + * - `spotlight` — Spotlight + * - `balloons` — Balloons + * - `confetti` — Confetti + * - `love` — Love (heart) + * - `lasers` — Lasers + * - `fireworks` — Fireworks + * - `celebration` — Celebration (sparkles) + * + * Values are case-insensitive and accept either dashes or spaces (`"Invisible Ink"` and + * `"invisible-ink"` both work). Pass `"none"` or omit the field to send without an effect. + * + * **Limitations:** + * - iMessage-only — when the chat is delivered as SMS or RCS the message is sent without an + * animation. + * - Not supported alongside the `parts` array (multipart bubbles cannot carry an effect). Use + * the top-level `text` field instead. + * - When `text` is an array, every message in the array is sent with the same effect. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun effect(): Optional = body.effect() + + /** + * E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, the + * first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, this selects a + * specific number from your pool. Must be a number assigned to your API key. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun fromNumber(): Optional = body.fromNumber() + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are optional. + * Only applies when the message text (or the concatenated part text) is exactly a single + * http(s) URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open Graph + * metadata to generate a preview. If the image download fails, the send still succeeds — Blooio + * silently falls back to the auto-generated preview. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun linkPreview(): Optional = body.linkPreview() + + /** + * Ordered array of message parts. Two modes: + * 1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text and + * attachment parts). This is the default. + * 2. **URL-balloon batch mode** — triggered when any part has a `link_preview` object. Each + * part becomes its own rich-link-preview iMessage; parts are sent sequentially in array + * order. In batch mode every part must be text-only with `text` being a single http(s) URL. + * Response contains `message_ids[]` + `count` instead of `message_id`. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun parts(): Optional> = body.parts() + + /** + * Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` (preferred — + * references a Blooio-minted message) or `guid` (raw iMessage GUID, useful for replying to + * messages received before the row was minted in Blooio). The new send is dispatched to Lava + * with the resolved `selectedMessageGuid` + `partIndex`, which iMessage renders as an inline + * reply on the recipient's device. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun replyTo(): Optional = body.replyTo() + + /** + * If true, the contact card (Name & Photo) will be shared with this message. The contact card + * is piggybacked onto the outgoing message. Defaults to false. ⚠️ Only available on **Dedicated + * Commercial** and **Dedicated Enterprise** plans — other plans receive a `403`. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun shareContact(): Optional = body.shareContact() + + /** + * Message text. Can be a single string or array of strings (each becomes a separate message) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = body.text() + + /** + * Whether to show typing indicator before sending. Defaults to org preference. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun useTypingIndicator(): Optional = body.useTypingIndicator() + + /** + * Returns the raw JSON value of [attachments]. + * + * Unlike [attachments], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _attachments(): JsonField> = body._attachments() + + /** + * Returns the raw JSON value of [effect]. + * + * Unlike [effect], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _effect(): JsonField = body._effect() + + /** + * Returns the raw JSON value of [fromNumber]. + * + * Unlike [fromNumber], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _fromNumber(): JsonField = body._fromNumber() + + /** + * Returns the raw JSON value of [linkPreview]. + * + * Unlike [linkPreview], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _linkPreview(): JsonField = body._linkPreview() + + /** + * Returns the raw JSON value of [parts]. + * + * Unlike [parts], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _parts(): JsonField> = body._parts() + + /** + * Returns the raw JSON value of [replyTo]. + * + * Unlike [replyTo], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _replyTo(): JsonField = body._replyTo() + + /** + * Returns the raw JSON value of [shareContact]. + * + * Unlike [shareContact], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _shareContact(): JsonField = body._shareContact() + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _text(): JsonField = body._text() + + /** + * Returns the raw JSON value of [useTypingIndicator]. + * + * Unlike [useTypingIndicator], this method doesn't throw if the JSON field has an unexpected + * type. + */ + fun _useTypingIndicator(): JsonField = body._useTypingIndicator() + + fun _additionalBodyProperties(): Map = body._additionalProperties() + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): MessageSendParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [MessageSendParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageSendParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var idempotencyKey: String? = null + private var body: Body.Builder = Body.builder() + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(messageSendParams: MessageSendParams) = apply { + chatId = messageSendParams.chatId + idempotencyKey = messageSendParams.idempotencyKey + body = messageSendParams.body.toBuilder() + additionalHeaders = messageSendParams.additionalHeaders.toBuilder() + additionalQueryParams = messageSendParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String?) = apply { this.chatId = chatId } + + /** Alias for calling [Builder.chatId] with `chatId.orElse(null)`. */ + fun chatId(chatId: Optional) = chatId(chatId.getOrNull()) + + fun idempotencyKey(idempotencyKey: String?) = apply { this.idempotencyKey = idempotencyKey } + + /** Alias for calling [Builder.idempotencyKey] with `idempotencyKey.orElse(null)`. */ + fun idempotencyKey(idempotencyKey: Optional) = + idempotencyKey(idempotencyKey.getOrNull()) + + /** + * Sets the entire request body. + * + * This is generally only useful if you are already constructing the body separately. + * Otherwise, it's more convenient to use the top-level setters instead: + * - [attachments] + * - [effect] + * - [fromNumber] + * - [linkPreview] + * - [parts] + * - etc. + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + /** Array of attachment URLs or objects with url/name */ + fun attachments(attachments: List) = apply { body.attachments(attachments) } + + /** + * Sets [Builder.attachments] to an arbitrary JSON value. + * + * You should usually call [Builder.attachments] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun attachments(attachments: JsonField>) = apply { + body.attachments(attachments) + } + + /** + * Adds a single [Attachment] to [attachments]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addAttachment(attachment: Attachment) = apply { body.addAttachment(attachment) } + + /** Alias for calling [addAttachment] with `Attachment.ofString(string)`. */ + fun addAttachment(string: String) = apply { body.addAttachment(string) } + + /** + * Alias for calling [addAttachment] with + * `Attachment.ofUnionObjectVariant1(unionObjectVariant1)`. + */ + fun addAttachment(unionObjectVariant1: Attachment.UnionObjectVariant1) = apply { + body.addAttachment(unionObjectVariant1) + } + + /** + * Optional. Attach an iMessage send-with-effect to the outgoing message. + * + * **Bubble effects** (apply to a single text bubble): + * - `slam` — Slam + * - `loud` — Loud + * - `gentle` — Gentle + * - `invisible-ink` — Invisible Ink + * + * **Screen effects** (full-screen animation in the recipient's chat): + * - `echo` — Echo + * - `spotlight` — Spotlight + * - `balloons` — Balloons + * - `confetti` — Confetti + * - `love` — Love (heart) + * - `lasers` — Lasers + * - `fireworks` — Fireworks + * - `celebration` — Celebration (sparkles) + * + * Values are case-insensitive and accept either dashes or spaces (`"Invisible Ink"` and + * `"invisible-ink"` both work). Pass `"none"` or omit the field to send without an effect. + * + * **Limitations:** + * - iMessage-only — when the chat is delivered as SMS or RCS the message is sent without an + * animation. + * - Not supported alongside the `parts` array (multipart bubbles cannot carry an effect). + * Use the top-level `text` field instead. + * - When `text` is an array, every message in the array is sent with the same effect. + */ + fun effect(effect: Effect?) = apply { body.effect(effect) } + + /** Alias for calling [Builder.effect] with `effect.orElse(null)`. */ + fun effect(effect: Optional) = effect(effect.getOrNull()) + + /** + * Sets [Builder.effect] to an arbitrary JSON value. + * + * You should usually call [Builder.effect] with a well-typed [Effect] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun effect(effect: JsonField) = apply { body.effect(effect) } + + /** + * E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, the + * first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, this + * selects a specific number from your pool. Must be a number assigned to your API key. + */ + fun fromNumber(fromNumber: String) = apply { body.fromNumber(fromNumber) } + + /** + * Sets [Builder.fromNumber] to an arbitrary JSON value. + * + * You should usually call [Builder.fromNumber] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun fromNumber(fromNumber: JsonField) = apply { body.fromNumber(fromNumber) } + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are + * optional. Only applies when the message text (or the concatenated part text) is exactly a + * single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open + * Graph metadata to generate a preview. If the image download fails, the send still + * succeeds — Blooio silently falls back to the auto-generated preview. + */ + fun linkPreview(linkPreview: LinkPreview?) = apply { body.linkPreview(linkPreview) } + + /** Alias for calling [Builder.linkPreview] with `linkPreview.orElse(null)`. */ + fun linkPreview(linkPreview: Optional) = linkPreview(linkPreview.getOrNull()) + + /** + * Sets [Builder.linkPreview] to an arbitrary JSON value. + * + * You should usually call [Builder.linkPreview] with a well-typed [LinkPreview] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun linkPreview(linkPreview: JsonField) = apply { + body.linkPreview(linkPreview) + } + + /** + * Ordered array of message parts. Two modes: + * 1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text and + * attachment parts). This is the default. + * 2. **URL-balloon batch mode** — triggered when any part has a `link_preview` object. Each + * part becomes its own rich-link-preview iMessage; parts are sent sequentially in array + * order. In batch mode every part must be text-only with `text` being a single http(s) + * URL. Response contains `message_ids[]` + `count` instead of `message_id`. + */ + fun parts(parts: List) = apply { body.parts(parts) } + + /** + * Sets [Builder.parts] to an arbitrary JSON value. + * + * You should usually call [Builder.parts] with a well-typed `List` value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun parts(parts: JsonField>) = apply { body.parts(parts) } + + /** + * Adds a single [Part] to [parts]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addPart(part: Part) = apply { body.addPart(part) } + + /** + * Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` + * (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, useful for + * replying to messages received before the row was minted in Blooio). The new send is + * dispatched to Lava with the resolved `selectedMessageGuid` + `partIndex`, which iMessage + * renders as an inline reply on the recipient's device. + */ + fun replyTo(replyTo: ReplyTo?) = apply { body.replyTo(replyTo) } + + /** Alias for calling [Builder.replyTo] with `replyTo.orElse(null)`. */ + fun replyTo(replyTo: Optional) = replyTo(replyTo.getOrNull()) + + /** + * Sets [Builder.replyTo] to an arbitrary JSON value. + * + * You should usually call [Builder.replyTo] with a well-typed [ReplyTo] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun replyTo(replyTo: JsonField) = apply { body.replyTo(replyTo) } + + /** + * If true, the contact card (Name & Photo) will be shared with this message. The contact + * card is piggybacked onto the outgoing message. Defaults to false. ⚠️ Only available on + * **Dedicated Commercial** and **Dedicated Enterprise** plans — other plans receive a + * `403`. + */ + fun shareContact(shareContact: Boolean) = apply { body.shareContact(shareContact) } + + /** + * Sets [Builder.shareContact] to an arbitrary JSON value. + * + * You should usually call [Builder.shareContact] with a well-typed [Boolean] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun shareContact(shareContact: JsonField) = apply { + body.shareContact(shareContact) + } + + /** + * Message text. Can be a single string or array of strings (each becomes a separate + * message) + */ + fun text(text: Text) = apply { body.text(text) } + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [Text] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun text(text: JsonField) = apply { body.text(text) } + + /** Alias for calling [text] with `Text.ofString(string)`. */ + fun text(string: String) = apply { body.text(string) } + + /** Alias for calling [text] with `Text.ofStrings(strings)`. */ + fun textOfStrings(strings: List) = apply { body.textOfStrings(strings) } + + /** Whether to show typing indicator before sending. Defaults to org preference. */ + fun useTypingIndicator(useTypingIndicator: Boolean) = apply { + body.useTypingIndicator(useTypingIndicator) + } + + /** + * Sets [Builder.useTypingIndicator] to an arbitrary JSON value. + * + * You should usually call [Builder.useTypingIndicator] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun useTypingIndicator(useTypingIndicator: JsonField) = apply { + body.useTypingIndicator(useTypingIndicator) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + body.additionalProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + body.putAdditionalProperty(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + body.putAllAdditionalProperties(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { body.removeAdditionalProperty(key) } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + body.removeAllAdditionalProperties(keys) + } + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [MessageSendParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageSendParams = + MessageSendParams( + chatId, + idempotencyKey, + body.build(), + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _body(): Body = body + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId ?: "" + else -> "" + } + + override fun _headers(): Headers = + Headers.builder() + .apply { + idempotencyKey?.let { put("Idempotency-Key", it) } + putAll(additionalHeaders) + } + .build() + + override fun _queryParams(): QueryParams = additionalQueryParams + + /** Request body for sending a message */ + class Body + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val attachments: JsonField>, + private val effect: JsonField, + private val fromNumber: JsonField, + private val linkPreview: JsonField, + private val parts: JsonField>, + private val replyTo: JsonField, + private val shareContact: JsonField, + private val text: JsonField, + private val useTypingIndicator: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("attachments") + @ExcludeMissing + attachments: JsonField> = JsonMissing.of(), + @JsonProperty("effect") @ExcludeMissing effect: JsonField = JsonMissing.of(), + @JsonProperty("from_number") + @ExcludeMissing + fromNumber: JsonField = JsonMissing.of(), + @JsonProperty("link_preview") + @ExcludeMissing + linkPreview: JsonField = JsonMissing.of(), + @JsonProperty("parts") @ExcludeMissing parts: JsonField> = JsonMissing.of(), + @JsonProperty("reply_to") + @ExcludeMissing + replyTo: JsonField = JsonMissing.of(), + @JsonProperty("share_contact") + @ExcludeMissing + shareContact: JsonField = JsonMissing.of(), + @JsonProperty("text") @ExcludeMissing text: JsonField = JsonMissing.of(), + @JsonProperty("use_typing_indicator") + @ExcludeMissing + useTypingIndicator: JsonField = JsonMissing.of(), + ) : this( + attachments, + effect, + fromNumber, + linkPreview, + parts, + replyTo, + shareContact, + text, + useTypingIndicator, + mutableMapOf(), + ) + + /** + * Array of attachment URLs or objects with url/name + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun attachments(): Optional> = attachments.getOptional("attachments") + + /** + * Optional. Attach an iMessage send-with-effect to the outgoing message. + * + * **Bubble effects** (apply to a single text bubble): + * - `slam` — Slam + * - `loud` — Loud + * - `gentle` — Gentle + * - `invisible-ink` — Invisible Ink + * + * **Screen effects** (full-screen animation in the recipient's chat): + * - `echo` — Echo + * - `spotlight` — Spotlight + * - `balloons` — Balloons + * - `confetti` — Confetti + * - `love` — Love (heart) + * - `lasers` — Lasers + * - `fireworks` — Fireworks + * - `celebration` — Celebration (sparkles) + * + * Values are case-insensitive and accept either dashes or spaces (`"Invisible Ink"` and + * `"invisible-ink"` both work). Pass `"none"` or omit the field to send without an effect. + * + * **Limitations:** + * - iMessage-only — when the chat is delivered as SMS or RCS the message is sent without an + * animation. + * - Not supported alongside the `parts` array (multipart bubbles cannot carry an effect). + * Use the top-level `text` field instead. + * - When `text` is an array, every message in the array is sent with the same effect. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun effect(): Optional = effect.getOptional("effect") + + /** + * E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, the + * first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, this + * selects a specific number from your pool. Must be a number assigned to your API key. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun fromNumber(): Optional = fromNumber.getOptional("from_number") + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are + * optional. Only applies when the message text (or the concatenated part text) is exactly a + * single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open + * Graph metadata to generate a preview. If the image download fails, the send still + * succeeds — Blooio silently falls back to the auto-generated preview. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun linkPreview(): Optional = linkPreview.getOptional("link_preview") + + /** + * Ordered array of message parts. Two modes: + * 1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text and + * attachment parts). This is the default. + * 2. **URL-balloon batch mode** — triggered when any part has a `link_preview` object. Each + * part becomes its own rich-link-preview iMessage; parts are sent sequentially in array + * order. In batch mode every part must be text-only with `text` being a single http(s) + * URL. Response contains `message_ids[]` + `count` instead of `message_id`. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun parts(): Optional> = parts.getOptional("parts") + + /** + * Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` + * (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, useful for + * replying to messages received before the row was minted in Blooio). The new send is + * dispatched to Lava with the resolved `selectedMessageGuid` + `partIndex`, which iMessage + * renders as an inline reply on the recipient's device. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun replyTo(): Optional = replyTo.getOptional("reply_to") + + /** + * If true, the contact card (Name & Photo) will be shared with this message. The contact + * card is piggybacked onto the outgoing message. Defaults to false. ⚠️ Only available on + * **Dedicated Commercial** and **Dedicated Enterprise** plans — other plans receive a + * `403`. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun shareContact(): Optional = shareContact.getOptional("share_contact") + + /** + * Message text. Can be a single string or array of strings (each becomes a separate + * message) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = text.getOptional("text") + + /** + * Whether to show typing indicator before sending. Defaults to org preference. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun useTypingIndicator(): Optional = + useTypingIndicator.getOptional("use_typing_indicator") + + /** + * Returns the raw JSON value of [attachments]. + * + * Unlike [attachments], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("attachments") + @ExcludeMissing + fun _attachments(): JsonField> = attachments + + /** + * Returns the raw JSON value of [effect]. + * + * Unlike [effect], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("effect") @ExcludeMissing fun _effect(): JsonField = effect + + /** + * Returns the raw JSON value of [fromNumber]. + * + * Unlike [fromNumber], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("from_number") + @ExcludeMissing + fun _fromNumber(): JsonField = fromNumber + + /** + * Returns the raw JSON value of [linkPreview]. + * + * Unlike [linkPreview], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("link_preview") + @ExcludeMissing + fun _linkPreview(): JsonField = linkPreview + + /** + * Returns the raw JSON value of [parts]. + * + * Unlike [parts], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("parts") @ExcludeMissing fun _parts(): JsonField> = parts + + /** + * Returns the raw JSON value of [replyTo]. + * + * Unlike [replyTo], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reply_to") @ExcludeMissing fun _replyTo(): JsonField = replyTo + + /** + * Returns the raw JSON value of [shareContact]. + * + * Unlike [shareContact], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("share_contact") + @ExcludeMissing + fun _shareContact(): JsonField = shareContact + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("text") @ExcludeMissing fun _text(): JsonField = text + + /** + * Returns the raw JSON value of [useTypingIndicator]. + * + * Unlike [useTypingIndicator], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("use_typing_indicator") + @ExcludeMissing + fun _useTypingIndicator(): JsonField = useTypingIndicator + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Body]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var attachments: JsonField>? = null + private var effect: JsonField = JsonMissing.of() + private var fromNumber: JsonField = JsonMissing.of() + private var linkPreview: JsonField = JsonMissing.of() + private var parts: JsonField>? = null + private var replyTo: JsonField = JsonMissing.of() + private var shareContact: JsonField = JsonMissing.of() + private var text: JsonField = JsonMissing.of() + private var useTypingIndicator: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + attachments = body.attachments.map { it.toMutableList() } + effect = body.effect + fromNumber = body.fromNumber + linkPreview = body.linkPreview + parts = body.parts.map { it.toMutableList() } + replyTo = body.replyTo + shareContact = body.shareContact + text = body.text + useTypingIndicator = body.useTypingIndicator + additionalProperties = body.additionalProperties.toMutableMap() + } + + /** Array of attachment URLs or objects with url/name */ + fun attachments(attachments: List) = attachments(JsonField.of(attachments)) + + /** + * Sets [Builder.attachments] to an arbitrary JSON value. + * + * You should usually call [Builder.attachments] with a well-typed `List` + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun attachments(attachments: JsonField>) = apply { + this.attachments = attachments.map { it.toMutableList() } + } + + /** + * Adds a single [Attachment] to [attachments]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addAttachment(attachment: Attachment) = apply { + attachments = + (attachments ?: JsonField.of(mutableListOf())).also { + checkKnown("attachments", it).add(attachment) + } + } + + /** Alias for calling [addAttachment] with `Attachment.ofString(string)`. */ + fun addAttachment(string: String) = addAttachment(Attachment.ofString(string)) + + /** + * Alias for calling [addAttachment] with + * `Attachment.ofUnionObjectVariant1(unionObjectVariant1)`. + */ + fun addAttachment(unionObjectVariant1: Attachment.UnionObjectVariant1) = + addAttachment(Attachment.ofUnionObjectVariant1(unionObjectVariant1)) + + /** + * Optional. Attach an iMessage send-with-effect to the outgoing message. + * + * **Bubble effects** (apply to a single text bubble): + * - `slam` — Slam + * - `loud` — Loud + * - `gentle` — Gentle + * - `invisible-ink` — Invisible Ink + * + * **Screen effects** (full-screen animation in the recipient's chat): + * - `echo` — Echo + * - `spotlight` — Spotlight + * - `balloons` — Balloons + * - `confetti` — Confetti + * - `love` — Love (heart) + * - `lasers` — Lasers + * - `fireworks` — Fireworks + * - `celebration` — Celebration (sparkles) + * + * Values are case-insensitive and accept either dashes or spaces (`"Invisible Ink"` and + * `"invisible-ink"` both work). Pass `"none"` or omit the field to send without an + * effect. + * + * **Limitations:** + * - iMessage-only — when the chat is delivered as SMS or RCS the message is sent + * without an animation. + * - Not supported alongside the `parts` array (multipart bubbles cannot carry an + * effect). Use the top-level `text` field instead. + * - When `text` is an array, every message in the array is sent with the same effect. + */ + fun effect(effect: Effect?) = effect(JsonField.ofNullable(effect)) + + /** Alias for calling [Builder.effect] with `effect.orElse(null)`. */ + fun effect(effect: Optional) = effect(effect.getOrNull()) + + /** + * Sets [Builder.effect] to an arbitrary JSON value. + * + * You should usually call [Builder.effect] with a well-typed [Effect] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun effect(effect: JsonField) = apply { this.effect = effect } + + /** + * E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, + * the first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, + * this selects a specific number from your pool. Must be a number assigned to your API + * key. + */ + fun fromNumber(fromNumber: String) = fromNumber(JsonField.of(fromNumber)) + + /** + * Sets [Builder.fromNumber] to an arbitrary JSON value. + * + * You should usually call [Builder.fromNumber] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun fromNumber(fromNumber: JsonField) = apply { this.fromNumber = fromNumber } + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are + * optional. Only applies when the message text (or the concatenated part text) is + * exactly a single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches + * the page's Open Graph metadata to generate a preview. If the image download fails, + * the send still succeeds — Blooio silently falls back to the auto-generated preview. + */ + fun linkPreview(linkPreview: LinkPreview?) = + linkPreview(JsonField.ofNullable(linkPreview)) + + /** Alias for calling [Builder.linkPreview] with `linkPreview.orElse(null)`. */ + fun linkPreview(linkPreview: Optional) = + linkPreview(linkPreview.getOrNull()) + + /** + * Sets [Builder.linkPreview] to an arbitrary JSON value. + * + * You should usually call [Builder.linkPreview] with a well-typed [LinkPreview] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun linkPreview(linkPreview: JsonField) = apply { + this.linkPreview = linkPreview + } + + /** + * Ordered array of message parts. Two modes: + * 1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text + * and attachment parts). This is the default. + * 2. **URL-balloon batch mode** — triggered when any part has a `link_preview` object. + * Each part becomes its own rich-link-preview iMessage; parts are sent sequentially + * in array order. In batch mode every part must be text-only with `text` being a + * single http(s) URL. Response contains `message_ids[]` + `count` instead of + * `message_id`. + */ + fun parts(parts: List) = parts(JsonField.of(parts)) + + /** + * Sets [Builder.parts] to an arbitrary JSON value. + * + * You should usually call [Builder.parts] with a well-typed `List` value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun parts(parts: JsonField>) = apply { + this.parts = parts.map { it.toMutableList() } + } + + /** + * Adds a single [Part] to [parts]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addPart(part: Part) = apply { + parts = + (parts ?: JsonField.of(mutableListOf())).also { + checkKnown("parts", it).add(part) + } + } + + /** + * Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` + * (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, useful + * for replying to messages received before the row was minted in Blooio). The new send + * is dispatched to Lava with the resolved `selectedMessageGuid` + `partIndex`, which + * iMessage renders as an inline reply on the recipient's device. + */ + fun replyTo(replyTo: ReplyTo?) = replyTo(JsonField.ofNullable(replyTo)) + + /** Alias for calling [Builder.replyTo] with `replyTo.orElse(null)`. */ + fun replyTo(replyTo: Optional) = replyTo(replyTo.getOrNull()) + + /** + * Sets [Builder.replyTo] to an arbitrary JSON value. + * + * You should usually call [Builder.replyTo] with a well-typed [ReplyTo] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun replyTo(replyTo: JsonField) = apply { this.replyTo = replyTo } + + /** + * If true, the contact card (Name & Photo) will be shared with this message. The + * contact card is piggybacked onto the outgoing message. Defaults to false. ⚠️ Only + * available on **Dedicated Commercial** and **Dedicated Enterprise** plans — other + * plans receive a `403`. + */ + fun shareContact(shareContact: Boolean) = shareContact(JsonField.of(shareContact)) + + /** + * Sets [Builder.shareContact] to an arbitrary JSON value. + * + * You should usually call [Builder.shareContact] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun shareContact(shareContact: JsonField) = apply { + this.shareContact = shareContact + } + + /** + * Message text. Can be a single string or array of strings (each becomes a separate + * message) + */ + fun text(text: Text) = text(JsonField.of(text)) + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [Text] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun text(text: JsonField) = apply { this.text = text } + + /** Alias for calling [text] with `Text.ofString(string)`. */ + fun text(string: String) = text(Text.ofString(string)) + + /** Alias for calling [text] with `Text.ofStrings(strings)`. */ + fun textOfStrings(strings: List) = text(Text.ofStrings(strings)) + + /** Whether to show typing indicator before sending. Defaults to org preference. */ + fun useTypingIndicator(useTypingIndicator: Boolean) = + useTypingIndicator(JsonField.of(useTypingIndicator)) + + /** + * Sets [Builder.useTypingIndicator] to an arbitrary JSON value. + * + * You should usually call [Builder.useTypingIndicator] with a well-typed [Boolean] + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun useTypingIndicator(useTypingIndicator: JsonField) = apply { + this.useTypingIndicator = useTypingIndicator + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Body]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Body = + Body( + (attachments ?: JsonMissing.of()).map { it.toImmutable() }, + effect, + fromNumber, + linkPreview, + (parts ?: JsonMissing.of()).map { it.toImmutable() }, + replyTo, + shareContact, + text, + useTypingIndicator, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Body = apply { + if (validated) { + return@apply + } + + attachments().ifPresent { it.forEach { it.validate() } } + effect().ifPresent { it.validate() } + fromNumber() + linkPreview().ifPresent { it.validate() } + parts().ifPresent { it.forEach { it.validate() } } + replyTo().ifPresent { it.validate() } + shareContact() + text().ifPresent { it.validate() } + useTypingIndicator() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (attachments.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (effect.asKnown().getOrNull()?.validity() ?: 0) + + (if (fromNumber.asKnown().isPresent) 1 else 0) + + (linkPreview.asKnown().getOrNull()?.validity() ?: 0) + + (parts.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (replyTo.asKnown().getOrNull()?.validity() ?: 0) + + (if (shareContact.asKnown().isPresent) 1 else 0) + + (text.asKnown().getOrNull()?.validity() ?: 0) + + (if (useTypingIndicator.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Body && + attachments == other.attachments && + effect == other.effect && + fromNumber == other.fromNumber && + linkPreview == other.linkPreview && + parts == other.parts && + replyTo == other.replyTo && + shareContact == other.shareContact && + text == other.text && + useTypingIndicator == other.useTypingIndicator && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + attachments, + effect, + fromNumber, + linkPreview, + parts, + replyTo, + shareContact, + text, + useTypingIndicator, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{attachments=$attachments, effect=$effect, fromNumber=$fromNumber, linkPreview=$linkPreview, parts=$parts, replyTo=$replyTo, shareContact=$shareContact, text=$text, useTypingIndicator=$useTypingIndicator, additionalProperties=$additionalProperties}" + } + + /** URL to the attachment */ + @JsonDeserialize(using = Attachment.Deserializer::class) + @JsonSerialize(using = Attachment.Serializer::class) + class Attachment + private constructor( + private val string: String? = null, + private val unionObjectVariant1: UnionObjectVariant1? = null, + private val _json: JsonValue? = null, + ) { + + /** URL to the attachment */ + fun string(): Optional = Optional.ofNullable(string) + + fun unionObjectVariant1(): Optional = + Optional.ofNullable(unionObjectVariant1) + + fun isString(): Boolean = string != null + + fun isUnionObjectVariant1(): Boolean = unionObjectVariant1 != null + + /** URL to the attachment */ + fun asString(): String = string.getOrThrow("string") + + fun asUnionObjectVariant1(): UnionObjectVariant1 = + unionObjectVariant1.getOrThrow("unionObjectVariant1") + + fun _json(): Optional = Optional.ofNullable(_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]: + * ```java + * import com.blooio.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = attachment.accept(new Attachment.Visitor>() { + * @Override + * public Optional visitString(String string) { + * return Optional.of(string.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws BlooioInvalidDataException 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) + unionObjectVariant1 != null -> visitor.visitUnionObjectVariant1(unionObjectVariant1) + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Attachment = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitString(string: String) {} + + override fun visitUnionObjectVariant1( + unionObjectVariant1: UnionObjectVariant1 + ) { + unionObjectVariant1.validate() + } + } + ) + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + accept( + object : Visitor { + override fun visitString(string: String) = 1 + + override fun visitUnionObjectVariant1( + unionObjectVariant1: UnionObjectVariant1 + ) = unionObjectVariant1.validity() + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Attachment && + string == other.string && + unionObjectVariant1 == other.unionObjectVariant1 + } + + override fun hashCode(): Int = Objects.hash(string, unionObjectVariant1) + + override fun toString(): String = + when { + string != null -> "Attachment{string=$string}" + unionObjectVariant1 != null -> + "Attachment{unionObjectVariant1=$unionObjectVariant1}" + _json != null -> "Attachment{_unknown=$_json}" + else -> throw IllegalStateException("Invalid Attachment") + } + + companion object { + + /** URL to the attachment */ + @JvmStatic fun ofString(string: String) = Attachment(string = string) + + @JvmStatic + fun ofUnionObjectVariant1(unionObjectVariant1: UnionObjectVariant1) = + Attachment(unionObjectVariant1 = unionObjectVariant1) + } + + /** + * An interface that defines how to map each variant of [Attachment] to a value of type [T]. + */ + interface Visitor { + + /** URL to the attachment */ + fun visitString(string: String): T + + fun visitUnionObjectVariant1(unionObjectVariant1: UnionObjectVariant1): T + + /** + * Maps an unknown variant of [Attachment] to a value of type [T]. + * + * An instance of [Attachment] 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 BlooioInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw BlooioInvalidDataException("Unknown Attachment: $json") + } + } + + internal class Deserializer : BaseDeserializer(Attachment::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): Attachment { + val json = JsonValue.fromJsonNode(node) + + val bestMatches = + sequenceOf( + tryDeserialize(node, jacksonTypeRef())?.let { + Attachment(unionObjectVariant1 = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef())?.let { + Attachment(string = 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 (e.g. deserializing from boolean). + 0 -> Attachment(_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(Attachment::class) { + + override fun serialize( + value: Attachment, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.string != null -> generator.writeObject(value.string) + value.unionObjectVariant1 != null -> + generator.writeObject(value.unionObjectVariant1) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid Attachment") + } + } + } + + class UnionObjectVariant1 + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val url: JsonField, + private val name: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + ) : this(url, name, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException 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 url(): String = url.getRequired("url") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun name(): Optional = name.getOptional("name") + + /** + * Returns the raw JSON value of [url]. + * + * Unlike [url], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("url") @ExcludeMissing fun _url(): JsonField = url + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [UnionObjectVariant1]. + * + * The following fields are required: + * ```java + * .url() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [UnionObjectVariant1]. */ + class Builder internal constructor() { + + private var url: JsonField? = null + private var name: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(unionObjectVariant1: UnionObjectVariant1) = apply { + url = unionObjectVariant1.url + name = unionObjectVariant1.name + additionalProperties = unionObjectVariant1.additionalProperties.toMutableMap() + } + + fun url(url: String) = url(JsonField.of(url)) + + /** + * Sets [Builder.url] to an arbitrary JSON value. + * + * You should usually call [Builder.url] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun url(url: JsonField) = apply { this.url = url } + + fun name(name: String) = name(JsonField.of(name)) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun name(name: JsonField) = apply { this.name = name } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = + apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { + additionalProperties.remove(key) + } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [UnionObjectVariant1]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .url() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): UnionObjectVariant1 = + UnionObjectVariant1( + checkRequired("url", url), + name, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): UnionObjectVariant1 = apply { + if (validated) { + return@apply + } + + url() + name() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (url.asKnown().isPresent) 1 else 0) + (if (name.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is UnionObjectVariant1 && + url == other.url && + name == other.name && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(url, name, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "UnionObjectVariant1{url=$url, name=$name, additionalProperties=$additionalProperties}" + } + } + + /** + * Optional. Attach an iMessage send-with-effect to the outgoing message. + * + * **Bubble effects** (apply to a single text bubble): + * - `slam` — Slam + * - `loud` — Loud + * - `gentle` — Gentle + * - `invisible-ink` — Invisible Ink + * + * **Screen effects** (full-screen animation in the recipient's chat): + * - `echo` — Echo + * - `spotlight` — Spotlight + * - `balloons` — Balloons + * - `confetti` — Confetti + * - `love` — Love (heart) + * - `lasers` — Lasers + * - `fireworks` — Fireworks + * - `celebration` — Celebration (sparkles) + * + * Values are case-insensitive and accept either dashes or spaces (`"Invisible Ink"` and + * `"invisible-ink"` both work). Pass `"none"` or omit the field to send without an effect. + * + * **Limitations:** + * - iMessage-only — when the chat is delivered as SMS or RCS the message is sent without an + * animation. + * - Not supported alongside the `parts` array (multipart bubbles cannot carry an effect). Use + * the top-level `text` field instead. + * - When `text` is an array, every message in the array is sent with the same effect. + */ + class Effect @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val SLAM = of("slam") + + @JvmField val LOUD = of("loud") + + @JvmField val GENTLE = of("gentle") + + @JvmField val INVISIBLE_INK = of("invisible-ink") + + @JvmField val ECHO = of("echo") + + @JvmField val SPOTLIGHT = of("spotlight") + + @JvmField val BALLOONS = of("balloons") + + @JvmField val CONFETTI = of("confetti") + + @JvmField val LOVE = of("love") + + @JvmField val LASERS = of("lasers") + + @JvmField val FIREWORKS = of("fireworks") + + @JvmField val CELEBRATION = of("celebration") + + @JvmField val NONE = of("none") + + @JvmStatic fun of(value: String) = Effect(JsonField.of(value)) + } + + /** An enum containing [Effect]'s known values. */ + enum class Known { + SLAM, + LOUD, + GENTLE, + INVISIBLE_INK, + ECHO, + SPOTLIGHT, + BALLOONS, + CONFETTI, + LOVE, + LASERS, + FIREWORKS, + CELEBRATION, + NONE, + } + + /** + * An enum containing [Effect]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Effect] 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 { + SLAM, + LOUD, + GENTLE, + INVISIBLE_INK, + ECHO, + SPOTLIGHT, + BALLOONS, + CONFETTI, + LOVE, + LASERS, + FIREWORKS, + CELEBRATION, + NONE, + /** An enum member indicating that [Effect] 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) { + SLAM -> Value.SLAM + LOUD -> Value.LOUD + GENTLE -> Value.GENTLE + INVISIBLE_INK -> Value.INVISIBLE_INK + ECHO -> Value.ECHO + SPOTLIGHT -> Value.SPOTLIGHT + BALLOONS -> Value.BALLOONS + CONFETTI -> Value.CONFETTI + LOVE -> Value.LOVE + LASERS -> Value.LASERS + FIREWORKS -> Value.FIREWORKS + CELEBRATION -> Value.CELEBRATION + NONE -> Value.NONE + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + SLAM -> Known.SLAM + LOUD -> Known.LOUD + GENTLE -> Known.GENTLE + INVISIBLE_INK -> Known.INVISIBLE_INK + ECHO -> Known.ECHO + SPOTLIGHT -> Known.SPOTLIGHT + BALLOONS -> Known.BALLOONS + CONFETTI -> Known.CONFETTI + LOVE -> Known.LOVE + LASERS -> Known.LASERS + FIREWORKS -> Known.FIREWORKS + CELEBRATION -> Known.CELEBRATION + NONE -> Known.NONE + else -> throw BlooioInvalidDataException("Unknown Effect: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Effect = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Effect && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + class Part + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val linkPreview: JsonField, + private val mention: JsonField, + private val name: JsonField, + private val text: JsonField, + private val url: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("link_preview") + @ExcludeMissing + linkPreview: JsonField = JsonMissing.of(), + @JsonProperty("mention") @ExcludeMissing mention: JsonField = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + @JsonProperty("text") @ExcludeMissing text: JsonField = JsonMissing.of(), + @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(), + ) : this(linkPreview, mention, name, text, url, mutableMapOf()) + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are + * optional. Only applies when the message text (or the concatenated part text) is exactly a + * single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open + * Graph metadata to generate a preview. If the image download fails, the send still + * succeeds — Blooio silently falls back to the auto-generated preview. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun linkPreview(): Optional = linkPreview.getOptional("link_preview") + + /** + * Participant phone number or email to @-mention. Only valid with 'text'. The entire text + * of the part is rendered as the mention. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun mention(): Optional = mention.getOptional("mention") + + /** + * Filename for the attachment. Only valid with 'url'. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun name(): Optional = name.getOptional("name") + + /** + * Text content for this part. Mutually exclusive with 'url'. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun text(): Optional = text.getOptional("text") + + /** + * URL to an attachment for this part. Mutually exclusive with 'text'. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun url(): Optional = url.getOptional("url") + + /** + * Returns the raw JSON value of [linkPreview]. + * + * Unlike [linkPreview], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("link_preview") + @ExcludeMissing + fun _linkPreview(): JsonField = linkPreview + + /** + * Returns the raw JSON value of [mention]. + * + * Unlike [mention], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("mention") @ExcludeMissing fun _mention(): JsonField = mention + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + /** + * Returns the raw JSON value of [text]. + * + * Unlike [text], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("text") @ExcludeMissing fun _text(): JsonField = text + + /** + * Returns the raw JSON value of [url]. + * + * Unlike [url], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("url") @ExcludeMissing fun _url(): JsonField = url + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Part]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Part]. */ + class Builder internal constructor() { + + private var linkPreview: JsonField = JsonMissing.of() + private var mention: JsonField = JsonMissing.of() + private var name: JsonField = JsonMissing.of() + private var text: JsonField = JsonMissing.of() + private var url: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(part: Part) = apply { + linkPreview = part.linkPreview + mention = part.mention + name = part.name + text = part.text + url = part.url + additionalProperties = part.additionalProperties.toMutableMap() + } + + /** + * Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are + * optional. Only applies when the message text (or the concatenated part text) is + * exactly a single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches + * the page's Open Graph metadata to generate a preview. If the image download fails, + * the send still succeeds — Blooio silently falls back to the auto-generated preview. + */ + fun linkPreview(linkPreview: LinkPreview?) = + linkPreview(JsonField.ofNullable(linkPreview)) + + /** Alias for calling [Builder.linkPreview] with `linkPreview.orElse(null)`. */ + fun linkPreview(linkPreview: Optional) = + linkPreview(linkPreview.getOrNull()) + + /** + * Sets [Builder.linkPreview] to an arbitrary JSON value. + * + * You should usually call [Builder.linkPreview] with a well-typed [LinkPreview] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun linkPreview(linkPreview: JsonField) = apply { + this.linkPreview = linkPreview + } + + /** + * Participant phone number or email to @-mention. Only valid with 'text'. The entire + * text of the part is rendered as the mention. + */ + fun mention(mention: String) = mention(JsonField.of(mention)) + + /** + * Sets [Builder.mention] to an arbitrary JSON value. + * + * You should usually call [Builder.mention] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun mention(mention: JsonField) = apply { this.mention = mention } + + /** Filename for the attachment. Only valid with 'url'. */ + fun name(name: String) = name(JsonField.of(name)) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun name(name: JsonField) = apply { this.name = name } + + /** Text content for this part. Mutually exclusive with 'url'. */ + fun text(text: String) = text(JsonField.of(text)) + + /** + * Sets [Builder.text] to an arbitrary JSON value. + * + * You should usually call [Builder.text] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun text(text: JsonField) = apply { this.text = text } + + /** URL to an attachment for this part. Mutually exclusive with 'text'. */ + fun url(url: String) = url(JsonField.of(url)) + + /** + * Sets [Builder.url] to an arbitrary JSON value. + * + * You should usually call [Builder.url] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun url(url: JsonField) = apply { this.url = url } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Part]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Part = + Part(linkPreview, mention, name, text, url, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Part = apply { + if (validated) { + return@apply + } + + linkPreview().ifPresent { it.validate() } + mention() + name() + text() + url() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (linkPreview.asKnown().getOrNull()?.validity() ?: 0) + + (if (mention.asKnown().isPresent) 1 else 0) + + (if (name.asKnown().isPresent) 1 else 0) + + (if (text.asKnown().isPresent) 1 else 0) + + (if (url.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Part && + linkPreview == other.linkPreview && + mention == other.mention && + name == other.name && + text == other.text && + url == other.url && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(linkPreview, mention, name, text, url, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Part{linkPreview=$linkPreview, mention=$mention, name=$name, text=$text, url=$url, additionalProperties=$additionalProperties}" + } + + /** + * Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` (preferred — + * references a Blooio-minted message) or `guid` (raw iMessage GUID, useful for replying to + * messages received before the row was minted in Blooio). The new send is dispatched to Lava + * with the resolved `selectedMessageGuid` + `partIndex`, which iMessage renders as an inline + * reply on the recipient's device. + */ + class ReplyTo + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val guid: JsonField, + private val messageId: JsonField, + private val partIndex: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("guid") @ExcludeMissing guid: JsonField = JsonMissing.of(), + @JsonProperty("message_id") + @ExcludeMissing + messageId: JsonField = JsonMissing.of(), + @JsonProperty("part_index") + @ExcludeMissing + partIndex: JsonField = JsonMissing.of(), + ) : this(guid, messageId, partIndex, mutableMapOf()) + + /** + * Raw iMessage GUID of the parent. When supplied without a `message_id`, Blooio attempts to + * look up the parent via `provider_message_guid`; if the parent isn't in our table the send + * still proceeds (Lava will thread on the device when possible) and the response carries + * `parent_unresolved: true`. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun guid(): Optional = guid.getOptional("guid") + + /** + * Blooio `message_id` of the parent. Must belong to the same chat, same from-number, and be + * no older than 30 days. Returns 404 `reply_target_not_found` if unknown. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * Which part of the parent to reply to. Defaults to 0 (covers the 99% case of replying to a + * single-part text message). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun partIndex(): Optional = partIndex.getOptional("part_index") + + /** + * Returns the raw JSON value of [guid]. + * + * Unlike [guid], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("guid") @ExcludeMissing fun _guid(): JsonField = guid + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [partIndex]. + * + * Unlike [partIndex], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("part_index") @ExcludeMissing fun _partIndex(): JsonField = partIndex + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [ReplyTo]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ReplyTo]. */ + class Builder internal constructor() { + + private var guid: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var partIndex: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(replyTo: ReplyTo) = apply { + guid = replyTo.guid + messageId = replyTo.messageId + partIndex = replyTo.partIndex + additionalProperties = replyTo.additionalProperties.toMutableMap() + } + + /** + * Raw iMessage GUID of the parent. When supplied without a `message_id`, Blooio + * attempts to look up the parent via `provider_message_guid`; if the parent isn't in + * our table the send still proceeds (Lava will thread on the device when possible) and + * the response carries `parent_unresolved: true`. + */ + fun guid(guid: String) = guid(JsonField.of(guid)) + + /** + * Sets [Builder.guid] to an arbitrary JSON value. + * + * You should usually call [Builder.guid] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun guid(guid: JsonField) = apply { this.guid = guid } + + /** + * Blooio `message_id` of the parent. Must belong to the same chat, same from-number, + * and be no older than 30 days. Returns 404 `reply_target_not_found` if unknown. + */ + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** + * Which part of the parent to reply to. Defaults to 0 (covers the 99% case of replying + * to a single-part text message). + */ + fun partIndex(partIndex: Long) = partIndex(JsonField.of(partIndex)) + + /** + * Sets [Builder.partIndex] to an arbitrary JSON value. + * + * You should usually call [Builder.partIndex] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun partIndex(partIndex: JsonField) = apply { this.partIndex = partIndex } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ReplyTo]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): ReplyTo = + ReplyTo(guid, messageId, partIndex, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ReplyTo = apply { + if (validated) { + return@apply + } + + guid() + messageId() + partIndex() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (guid.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (if (partIndex.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ReplyTo && + guid == other.guid && + messageId == other.messageId && + partIndex == other.partIndex && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(guid, messageId, partIndex, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ReplyTo{guid=$guid, messageId=$messageId, partIndex=$partIndex, additionalProperties=$additionalProperties}" + } + + /** + * Message text. Can be a single string or array of strings (each becomes a separate message) + */ + @JsonDeserialize(using = Text.Deserializer::class) + @JsonSerialize(using = Text.Serializer::class) + class Text + private constructor( + private val string: String? = null, + private val strings: List? = null, + private val _json: JsonValue? = null, + ) { + + fun string(): Optional = Optional.ofNullable(string) + + fun strings(): Optional> = Optional.ofNullable(strings) + + fun isString(): Boolean = string != null + + fun isStrings(): Boolean = strings != null + + fun asString(): String = string.getOrThrow("string") + + fun asStrings(): List = strings.getOrThrow("strings") + + fun _json(): Optional = Optional.ofNullable(_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]: + * ```java + * import com.blooio.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = text.accept(new Text.Visitor>() { + * @Override + * public Optional visitString(String string) { + * return Optional.of(string.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws BlooioInvalidDataException 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) + strings != null -> visitor.visitStrings(strings) + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Text = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitString(string: String) {} + + override fun visitStrings(strings: List) {} + } + ) + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + accept( + object : Visitor { + override fun visitString(string: String) = 1 + + override fun visitStrings(strings: List) = strings.size + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Text && string == other.string && strings == other.strings + } + + override fun hashCode(): Int = Objects.hash(string, strings) + + override fun toString(): String = + when { + string != null -> "Text{string=$string}" + strings != null -> "Text{strings=$strings}" + _json != null -> "Text{_unknown=$_json}" + else -> throw IllegalStateException("Invalid Text") + } + + companion object { + + @JvmStatic fun ofString(string: String) = Text(string = string) + + @JvmStatic fun ofStrings(strings: List) = Text(strings = strings.toImmutable()) + } + + /** An interface that defines how to map each variant of [Text] to a value of type [T]. */ + interface Visitor { + + fun visitString(string: String): T + + fun visitStrings(strings: List): T + + /** + * Maps an unknown variant of [Text] to a value of type [T]. + * + * An instance of [Text] 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 BlooioInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw BlooioInvalidDataException("Unknown Text: $json") + } + } + + internal class Deserializer : BaseDeserializer(Text::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): Text { + val json = JsonValue.fromJsonNode(node) + + val bestMatches = + sequenceOf( + tryDeserialize(node, jacksonTypeRef())?.let { + Text(string = it, _json = json) + }, + tryDeserialize(node, jacksonTypeRef>())?.let { + Text(strings = 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 (e.g. deserializing from boolean). + 0 -> Text(_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(Text::class) { + + override fun serialize( + value: Text, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.string != null -> generator.writeObject(value.string) + value.strings != null -> generator.writeObject(value.strings) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid Text") + } + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageSendParams && + chatId == other.chatId && + idempotencyKey == other.idempotencyKey && + body == other.body && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(chatId, idempotencyKey, body, additionalHeaders, additionalQueryParams) + + override fun toString() = + "MessageSendParams{chatId=$chatId, idempotencyKey=$idempotencyKey, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendResponse.kt new file mode 100644 index 0000000..437439e --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/MessageSendResponse.kt @@ -0,0 +1,646 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.Enum +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.checkKnown +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** Response after sending a message */ +class MessageSendResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val count: JsonField, + private val groupCreated: JsonField, + private val groupId: JsonField, + private val messageId: JsonField, + private val messageIds: JsonField>, + private val parentUnresolved: JsonField, + private val participants: JsonField>, + private val status: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("count") @ExcludeMissing count: JsonField = JsonMissing.of(), + @JsonProperty("group_created") + @ExcludeMissing + groupCreated: JsonField = JsonMissing.of(), + @JsonProperty("group_id") @ExcludeMissing groupId: JsonField = JsonMissing.of(), + @JsonProperty("message_id") @ExcludeMissing messageId: JsonField = JsonMissing.of(), + @JsonProperty("message_ids") + @ExcludeMissing + messageIds: JsonField> = JsonMissing.of(), + @JsonProperty("parent_unresolved") + @ExcludeMissing + parentUnresolved: JsonField = JsonMissing.of(), + @JsonProperty("participants") + @ExcludeMissing + participants: JsonField> = JsonMissing.of(), + @JsonProperty("status") @ExcludeMissing status: JsonField = JsonMissing.of(), + ) : this( + count, + groupCreated, + groupId, + messageId, + messageIds, + parentUnresolved, + participants, + status, + mutableMapOf(), + ) + + /** + * Number of messages sent. Only present in URL-balloon batch mode. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun count(): Optional = count.getOptional("count") + + /** + * True if a new unnamed group was created for this multi-recipient message + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupCreated(): Optional = groupCreated.getOptional("group_created") + + /** + * Group ID when sending to multi-recipient (new or existing) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun groupId(): Optional = groupId.getOptional("group_id") + + /** + * ID of the sent message (single-message sends) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageId(): Optional = messageId.getOptional("message_id") + + /** + * IDs of sent messages. Present when `text` is an array or when `parts` uses per-part + * `link_preview` (URL-balloon batch mode). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun messageIds(): Optional> = messageIds.getOptional("message_ids") + + /** + * Present (and `true`) only when `reply_to.guid` was supplied without a `message_id` and the + * GUID didn't map to any Blooio-minted row. The send still proceeds and the device may still + * thread it; this flag signals that Blooio couldn't link the new message to a known parent. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun parentUnresolved(): Optional = parentUnresolved.getOptional("parent_unresolved") + + /** + * List of participants (present for multi-recipient) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun participants(): Optional> = participants.getOptional("participants") + + /** + * Initial status of the message(s). `queued` = accepted for delivery (the normal 202 result); + * `failed` = rejected before dispatch. Subsequent transitions (`sent` → `delivered`, or + * `failed`) are reported via the status endpoint and `message.status` webhooks. + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun status(): Optional = status.getOptional("status") + + /** + * Returns the raw JSON value of [count]. + * + * Unlike [count], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("count") @ExcludeMissing fun _count(): JsonField = count + + /** + * Returns the raw JSON value of [groupCreated]. + * + * Unlike [groupCreated], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_created") + @ExcludeMissing + fun _groupCreated(): JsonField = groupCreated + + /** + * Returns the raw JSON value of [groupId]. + * + * Unlike [groupId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("group_id") @ExcludeMissing fun _groupId(): JsonField = groupId + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_id") @ExcludeMissing fun _messageId(): JsonField = messageId + + /** + * Returns the raw JSON value of [messageIds]. + * + * Unlike [messageIds], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("message_ids") + @ExcludeMissing + fun _messageIds(): JsonField> = messageIds + + /** + * Returns the raw JSON value of [parentUnresolved]. + * + * Unlike [parentUnresolved], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("parent_unresolved") + @ExcludeMissing + fun _parentUnresolved(): JsonField = parentUnresolved + + /** + * Returns the raw JSON value of [participants]. + * + * Unlike [participants], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("participants") + @ExcludeMissing + fun _participants(): JsonField> = participants + + /** + * 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) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [MessageSendResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageSendResponse]. */ + class Builder internal constructor() { + + private var count: JsonField = JsonMissing.of() + private var groupCreated: JsonField = JsonMissing.of() + private var groupId: JsonField = JsonMissing.of() + private var messageId: JsonField = JsonMissing.of() + private var messageIds: JsonField>? = null + private var parentUnresolved: JsonField = JsonMissing.of() + private var participants: JsonField>? = null + private var status: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(messageSendResponse: MessageSendResponse) = apply { + count = messageSendResponse.count + groupCreated = messageSendResponse.groupCreated + groupId = messageSendResponse.groupId + messageId = messageSendResponse.messageId + messageIds = messageSendResponse.messageIds.map { it.toMutableList() } + parentUnresolved = messageSendResponse.parentUnresolved + participants = messageSendResponse.participants.map { it.toMutableList() } + status = messageSendResponse.status + additionalProperties = messageSendResponse.additionalProperties.toMutableMap() + } + + /** Number of messages sent. Only present in URL-balloon batch mode. */ + fun count(count: Long) = count(JsonField.of(count)) + + /** + * Sets [Builder.count] to an arbitrary JSON value. + * + * You should usually call [Builder.count] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun count(count: JsonField) = apply { this.count = count } + + /** True if a new unnamed group was created for this multi-recipient message */ + fun groupCreated(groupCreated: Boolean) = groupCreated(JsonField.of(groupCreated)) + + /** + * Sets [Builder.groupCreated] to an arbitrary JSON value. + * + * You should usually call [Builder.groupCreated] with a well-typed [Boolean] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun groupCreated(groupCreated: JsonField) = apply { + this.groupCreated = groupCreated + } + + /** Group ID when sending to multi-recipient (new or existing) */ + fun groupId(groupId: String) = groupId(JsonField.of(groupId)) + + /** + * Sets [Builder.groupId] to an arbitrary JSON value. + * + * You should usually call [Builder.groupId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun groupId(groupId: JsonField) = apply { this.groupId = groupId } + + /** ID of the sent message (single-message sends) */ + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + /** + * IDs of sent messages. Present when `text` is an array or when `parts` uses per-part + * `link_preview` (URL-balloon batch mode). + */ + fun messageIds(messageIds: List) = messageIds(JsonField.of(messageIds)) + + /** + * Sets [Builder.messageIds] to an arbitrary JSON value. + * + * You should usually call [Builder.messageIds] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun messageIds(messageIds: JsonField>) = apply { + this.messageIds = messageIds.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [messageIds]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addMessageId(messageId: String) = apply { + messageIds = + (messageIds ?: JsonField.of(mutableListOf())).also { + checkKnown("messageIds", it).add(messageId) + } + } + + /** + * Present (and `true`) only when `reply_to.guid` was supplied without a `message_id` and + * the GUID didn't map to any Blooio-minted row. The send still proceeds and the device may + * still thread it; this flag signals that Blooio couldn't link the new message to a known + * parent. + */ + fun parentUnresolved(parentUnresolved: Boolean) = + parentUnresolved(JsonField.of(parentUnresolved)) + + /** + * Sets [Builder.parentUnresolved] to an arbitrary JSON value. + * + * You should usually call [Builder.parentUnresolved] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun parentUnresolved(parentUnresolved: JsonField) = apply { + this.parentUnresolved = parentUnresolved + } + + /** List of participants (present for multi-recipient) */ + fun participants(participants: List) = participants(JsonField.of(participants)) + + /** + * Sets [Builder.participants] to an arbitrary JSON value. + * + * You should usually call [Builder.participants] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun participants(participants: JsonField>) = apply { + this.participants = participants.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [participants]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addParticipant(participant: String) = apply { + participants = + (participants ?: JsonField.of(mutableListOf())).also { + checkKnown("participants", it).add(participant) + } + } + + /** + * Initial status of the message(s). `queued` = accepted for delivery (the normal 202 + * result); `failed` = rejected before dispatch. Subsequent transitions (`sent` → + * `delivered`, or `failed`) are reported via the status endpoint and `message.status` + * webhooks. + */ + fun status(status: Status) = status(JsonField.of(status)) + + /** + * Sets [Builder.status] to an arbitrary JSON value. + * + * You should usually call [Builder.status] with a well-typed [Status] 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) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [MessageSendResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageSendResponse = + MessageSendResponse( + count, + groupCreated, + groupId, + messageId, + (messageIds ?: JsonMissing.of()).map { it.toImmutable() }, + parentUnresolved, + (participants ?: JsonMissing.of()).map { it.toImmutable() }, + 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): MessageSendResponse = apply { + if (validated) { + return@apply + } + + count() + groupCreated() + groupId() + messageId() + messageIds() + parentUnresolved() + participants() + status().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (count.asKnown().isPresent) 1 else 0) + + (if (groupCreated.asKnown().isPresent) 1 else 0) + + (if (groupId.asKnown().isPresent) 1 else 0) + + (if (messageId.asKnown().isPresent) 1 else 0) + + (messageIds.asKnown().getOrNull()?.size ?: 0) + + (if (parentUnresolved.asKnown().isPresent) 1 else 0) + + (participants.asKnown().getOrNull()?.size ?: 0) + + (status.asKnown().getOrNull()?.validity() ?: 0) + + /** + * Initial status of the message(s). `queued` = accepted for delivery (the normal 202 result); + * `failed` = rejected before dispatch. Subsequent transitions (`sent` → `delivered`, or + * `failed`) are reported via the status endpoint and `message.status` webhooks. + */ + class Status @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val QUEUED = of("queued") + + @JvmField val FAILED = of("failed") + + @JvmStatic fun of(value: String) = Status(JsonField.of(value)) + } + + /** An enum containing [Status]'s known values. */ + enum class Known { + QUEUED, + FAILED, + } + + /** + * An enum containing [Status]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Status] 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 { + QUEUED, + FAILED, + /** An enum member indicating that [Status] 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) { + QUEUED -> Value.QUEUED + FAILED -> Value.FAILED + 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 BlooioInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + QUEUED -> Known.QUEUED + FAILED -> Known.FAILED + else -> throw BlooioInvalidDataException("Unknown Status: $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 BlooioInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { BlooioInvalidDataException("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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Status = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Status && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageSendResponse && + count == other.count && + groupCreated == other.groupCreated && + groupId == other.groupId && + messageId == other.messageId && + messageIds == other.messageIds && + parentUnresolved == other.parentUnresolved && + participants == other.participants && + status == other.status && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + count, + groupCreated, + groupId, + messageId, + messageIds, + parentUnresolved, + participants, + status, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "MessageSendResponse{count=$count, groupCreated=$groupCreated, groupId=$groupId, messageId=$messageId, messageIds=$messageIds, parentUnresolved=$parentUnresolved, participants=$participants, status=$status, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/Reaction.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/Reaction.kt new file mode 100644 index 0000000..6c3ad0f --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/messages/Reaction.kt @@ -0,0 +1,279 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.messages + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class Reaction +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val isAdded: JsonField, + private val reaction: JsonField, + private val sender: JsonField, + private val timeSent: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("is_added") @ExcludeMissing isAdded: JsonField = JsonMissing.of(), + @JsonProperty("reaction") @ExcludeMissing reaction: JsonField = JsonMissing.of(), + @JsonProperty("sender") @ExcludeMissing sender: JsonField = JsonMissing.of(), + @JsonProperty("time_sent") @ExcludeMissing timeSent: JsonField = JsonMissing.of(), + ) : this(isAdded, reaction, sender, timeSent, mutableMapOf()) + + /** + * Whether the reaction is currently active (true) or was removed (false) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun isAdded(): Optional = isAdded.getOptional("is_added") + + /** + * The reaction value. Classic tapbacks: love, like, dislike, laugh, emphasize, question. Emoji + * reactions: the emoji character (e.g. 😂, 👍). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun reaction(): Optional = reaction.getOptional("reaction") + + /** + * Phone number or email of who sent the reaction. Null when the reaction was sent by you + * (outbound). + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun sender(): Optional = sender.getOptional("sender") + + /** + * Timestamp when the reaction was sent (ms) + * + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeSent(): Optional = timeSent.getOptional("time_sent") + + /** + * Returns the raw JSON value of [isAdded]. + * + * Unlike [isAdded], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("is_added") @ExcludeMissing fun _isAdded(): JsonField = isAdded + + /** + * Returns the raw JSON value of [reaction]. + * + * Unlike [reaction], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("reaction") @ExcludeMissing fun _reaction(): JsonField = reaction + + /** + * Returns the raw JSON value of [sender]. + * + * Unlike [sender], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("sender") @ExcludeMissing fun _sender(): JsonField = sender + + /** + * Returns the raw JSON value of [timeSent]. + * + * Unlike [timeSent], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("time_sent") @ExcludeMissing fun _timeSent(): JsonField = timeSent + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Reaction]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Reaction]. */ + class Builder internal constructor() { + + private var isAdded: JsonField = JsonMissing.of() + private var reaction: JsonField = JsonMissing.of() + private var sender: JsonField = JsonMissing.of() + private var timeSent: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(reaction: Reaction) = apply { + isAdded = reaction.isAdded + this.reaction = reaction.reaction + sender = reaction.sender + timeSent = reaction.timeSent + additionalProperties = reaction.additionalProperties.toMutableMap() + } + + /** Whether the reaction is currently active (true) or was removed (false) */ + fun isAdded(isAdded: Boolean) = isAdded(JsonField.of(isAdded)) + + /** + * Sets [Builder.isAdded] to an arbitrary JSON value. + * + * You should usually call [Builder.isAdded] with a well-typed [Boolean] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun isAdded(isAdded: JsonField) = apply { this.isAdded = isAdded } + + /** + * The reaction value. Classic tapbacks: love, like, dislike, laugh, emphasize, question. + * Emoji reactions: the emoji character (e.g. 😂, 👍). + */ + fun reaction(reaction: String) = reaction(JsonField.of(reaction)) + + /** + * Sets [Builder.reaction] to an arbitrary JSON value. + * + * You should usually call [Builder.reaction] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun reaction(reaction: JsonField) = apply { this.reaction = reaction } + + /** + * Phone number or email of who sent the reaction. Null when the reaction was sent by you + * (outbound). + */ + fun sender(sender: String?) = sender(JsonField.ofNullable(sender)) + + /** Alias for calling [Builder.sender] with `sender.orElse(null)`. */ + fun sender(sender: Optional) = sender(sender.getOrNull()) + + /** + * Sets [Builder.sender] to an arbitrary JSON value. + * + * You should usually call [Builder.sender] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun sender(sender: JsonField) = apply { this.sender = sender } + + /** Timestamp when the reaction was sent (ms) */ + fun timeSent(timeSent: Long) = timeSent(JsonField.of(timeSent)) + + /** + * Sets [Builder.timeSent] to an arbitrary JSON value. + * + * You should usually call [Builder.timeSent] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun timeSent(timeSent: JsonField) = apply { this.timeSent = timeSent } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Reaction]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Reaction = + Reaction(isAdded, reaction, sender, timeSent, 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 BlooioInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): Reaction = apply { + if (validated) { + return@apply + } + + isAdded() + reaction() + sender() + timeSent() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BlooioInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (isAdded.asKnown().isPresent) 1 else 0) + + (if (reaction.asKnown().isPresent) 1 else 0) + + (if (sender.asKnown().isPresent) 1 else 0) + + (if (timeSent.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Reaction && + isAdded == other.isAdded && + reaction == other.reaction && + sender == other.sender && + timeSent == other.timeSent && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(isAdded, reaction, sender, timeSent, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Reaction{isAdded=$isAdded, reaction=$reaction, sender=$sender, timeSent=$timeSent, additionalProperties=$additionalProperties}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsParams.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsParams.kt new file mode 100644 index 0000000..b15a832 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsParams.kt @@ -0,0 +1,220 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.polls + +import com.blooio.api.core.Params +import com.blooio.api.core.checkRequired +import com.blooio.api.core.http.Headers +import com.blooio.api.core.http.QueryParams +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Retrieve a poll's definition and aggregated vote counts. The pollId is the poll_id returned in + * the poll.received or poll.created webhook event. + */ +class PollGetResultsParams +private constructor( + private val chatId: String, + private val pollId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun chatId(): String = chatId + + fun pollId(): Optional = Optional.ofNullable(pollId) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [PollGetResultsParams]. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [PollGetResultsParams]. */ + class Builder internal constructor() { + + private var chatId: String? = null + private var pollId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(pollGetResultsParams: PollGetResultsParams) = apply { + chatId = pollGetResultsParams.chatId + pollId = pollGetResultsParams.pollId + additionalHeaders = pollGetResultsParams.additionalHeaders.toBuilder() + additionalQueryParams = pollGetResultsParams.additionalQueryParams.toBuilder() + } + + fun chatId(chatId: String) = apply { this.chatId = chatId } + + fun pollId(pollId: String?) = apply { this.pollId = pollId } + + /** Alias for calling [Builder.pollId] with `pollId.orElse(null)`. */ + fun pollId(pollId: Optional) = pollId(pollId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [PollGetResultsParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .chatId() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): PollGetResultsParams = + PollGetResultsParams( + checkRequired("chatId", chatId), + pollId, + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _pathParam(index: Int): String = + when (index) { + 0 -> chatId + 1 -> pollId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is PollGetResultsParams && + chatId == other.chatId && + pollId == other.pollId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(chatId, pollId, additionalHeaders, additionalQueryParams) + + override fun toString() = + "PollGetResultsParams{chatId=$chatId, pollId=$pollId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsResponse.kt b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsResponse.kt new file mode 100644 index 0000000..76969a9 --- /dev/null +++ b/blooio-java-core/src/main/kotlin/com/blooio/api/models/chats/polls/PollGetResultsResponse.kt @@ -0,0 +1,491 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.blooio.api.models.chats.polls + +import com.blooio.api.core.ExcludeMissing +import com.blooio.api.core.JsonField +import com.blooio.api.core.JsonMissing +import com.blooio.api.core.JsonValue +import com.blooio.api.core.checkKnown +import com.blooio.api.core.toImmutable +import com.blooio.api.errors.BlooioInvalidDataException +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 java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +class PollGetResultsResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val chatId: JsonField, + private val options: JsonField>, + private val pollId: JsonField, + private val title: JsonField, + private val totalVotes: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("chat_id") @ExcludeMissing chatId: JsonField = JsonMissing.of(), + @JsonProperty("options") + @ExcludeMissing + options: JsonField> = JsonMissing.of(), + @JsonProperty("poll_id") @ExcludeMissing pollId: JsonField = JsonMissing.of(), + @JsonProperty("title") @ExcludeMissing title: JsonField = JsonMissing.of(), + @JsonProperty("total_votes") @ExcludeMissing totalVotes: JsonField = JsonMissing.of(), + ) : this(chatId, options, pollId, title, totalVotes, mutableMapOf()) + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun chatId(): Optional = chatId.getOptional("chat_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun options(): Optional> = options.getOptional("options") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun pollId(): Optional = pollId.getOptional("poll_id") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun title(): Optional = title.getOptional("title") + + /** + * @throws BlooioInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun totalVotes(): Optional = totalVotes.getOptional("total_votes") + + /** + * Returns the raw JSON value of [chatId]. + * + * Unlike [chatId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("chat_id") @ExcludeMissing fun _chatId(): JsonField = chatId + + /** + * Returns the raw JSON value of [options]. + * + * Unlike [options], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("options") @ExcludeMissing fun _options(): JsonField> = options + + /** + * Returns the raw JSON value of [pollId]. + * + * Unlike [pollId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("poll_id") @ExcludeMissing fun _pollId(): JsonField = pollId + + /** + * Returns the raw JSON value of [title]. + * + * Unlike [title], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("title") @ExcludeMissing fun _title(): JsonField = title + + /** + * Returns the raw JSON value of [totalVotes]. + * + * Unlike [totalVotes], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("total_votes") @ExcludeMissing fun _totalVotes(): JsonField = totalVotes + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [PollGetResultsResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [PollGetResultsResponse]. */ + class Builder internal constructor() { + + private var chatId: JsonField = JsonMissing.of() + private var options: JsonField>? = null + private var pollId: JsonField = JsonMissing.of() + private var title: JsonField = JsonMissing.of() + private var totalVotes: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(pollGetResultsResponse: PollGetResultsResponse) = apply { + chatId = pollGetResultsResponse.chatId + options = pollGetResultsResponse.options.map { it.toMutableList() } + pollId = pollGetResultsResponse.pollId + title = pollGetResultsResponse.title + totalVotes = pollGetResultsResponse.totalVotes + additionalProperties = pollGetResultsResponse.additionalProperties.toMutableMap() + } + + fun chatId(chatId: String) = chatId(JsonField.of(chatId)) + + /** + * Sets [Builder.chatId] to an arbitrary JSON value. + * + * You should usually call [Builder.chatId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun chatId(chatId: JsonField) = apply { this.chatId = chatId } + + fun options(options: List