diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 45a08d37..9b818855 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,10 +20,10 @@ jobs:
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: temurin
java-version: |
@@ -40,14 +40,17 @@ jobs:
build:
timeout-minutes: 15
name: build
+ permissions:
+ contents: read
+ id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/prelude-java' && '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@v6
- name: Set up Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: temurin
java-version: |
@@ -61,16 +64,35 @@ jobs:
- name: Build SDK
run: ./scripts/build
+ - name: Get GitHub OIDC Token
+ if: |-
+ github.repository == 'stainless-sdks/prelude-java' &&
+ !startsWith(github.ref, 'refs/heads/stl/')
+ id: github-oidc
+ uses: actions/github-script@v8
+ with:
+ script: core.setOutput('github_token', await core.getIDToken());
+
+ - name: Build and upload Maven artifacts
+ if: |-
+ github.repository == 'stainless-sdks/prelude-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: prelude-java
+ run: ./scripts/upload-artifacts
test:
timeout-minutes: 15
name: test
runs-on: ${{ github.repository == 'stainless-sdks/prelude-java' && '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@v6
- name: Set up Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: temurin
java-version: |
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index 7279cb1e..fdbed593 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@v6
- name: Set up Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: temurin
java-version: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index f5b43959..326496d4 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'prelude-so/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@v6
- name: Check release environment
run: |
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 091cfb12..f7014c35 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.10.0"
+ ".": "0.11.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 3559e655..d3333fdd 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude%2Fprelude-fe14bed3c1b6245444e1a53e7b0b172301c207ef9500dc68f4d8e58a7bfec436.yml
-openapi_spec_hash: 4acdc9dd487011b2391f6fe02812a6bd
-config_hash: 55380048fe5cf686421acf7eeaae21be
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude%2Fprelude-c9124c257dd54dd0728cb57306b9082007439bfefac11642542605edb3e7606d.yml
+openapi_spec_hash: 61dc64cc814d10975a8825ec88fd9c1c
+config_hash: 107ae5754168e80c4ad2cd779a75bc36
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8c22f7f..42fc7ae4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,60 @@
# Changelog
+## 0.11.0 (2026-03-07)
+
+Full Changelog: [v0.10.0...v0.11.0](https://github.com/prelude-so/java-sdk/compare/v0.10.0...v0.11.0)
+
+### Features
+
+* **api:** api update ([ca9ee11](https://github.com/prelude-so/java-sdk/commit/ca9ee118f3800cfde6765103742c06a015f47111))
+* **api:** api update ([d683d44](https://github.com/prelude-so/java-sdk/commit/d683d441c3d8d89015ee9ab971ad883fc8f81b32))
+* **api:** api update ([01e0e53](https://github.com/prelude-so/java-sdk/commit/01e0e5369605017ae1ef44d473d593584ef07a71))
+* **api:** api update ([53cbdac](https://github.com/prelude-so/java-sdk/commit/53cbdac9a2e4b4cffaab963be9edeb77f4a07cf3))
+* **api:** api update ([7b287a1](https://github.com/prelude-so/java-sdk/commit/7b287a1a1f2bae3f1185bb8650a49169947bbeb0))
+* **api:** api update ([aabe63c](https://github.com/prelude-so/java-sdk/commit/aabe63c8899e9177d6d053cdd998806302c9c111))
+* **api:** api update ([74b758c](https://github.com/prelude-so/java-sdk/commit/74b758c2e80672f942000e1201edd081d67b0d18))
+* **api:** api update ([dc0fc24](https://github.com/prelude-so/java-sdk/commit/dc0fc245ce976922680aaa766216f76b039f3d46))
+* **client:** add `HttpRequest#url()` method ([6b5a7b3](https://github.com/prelude-so/java-sdk/commit/6b5a7b3018b374bfc0c8337b82d9ae2cc8ca20ac))
+* **client:** add connection pooling option ([dabf164](https://github.com/prelude-so/java-sdk/commit/dabf164c493a424cd9542d1355af0113cc16c6be))
+* **client:** allow configuring dispatcher executor service ([960cc19](https://github.com/prelude-so/java-sdk/commit/960cc193330b9e3b9fb29d48283430029846c3aa))
+* **client:** send `X-Stainless-Kotlin-Version` header ([32f4d72](https://github.com/prelude-so/java-sdk/commit/32f4d72f068581e4257c5934d3c58b75128f0289))
+
+
+### Bug Fixes
+
+* **client:** disallow coercion from float to int ([a498a2f](https://github.com/prelude-so/java-sdk/commit/a498a2f53afeaef5b2dc34b4ade246af2fec6167))
+* **client:** fully respect max retries ([db20e56](https://github.com/prelude-so/java-sdk/commit/db20e56fa9a8b373305bff3ae10afea7bbd0ff53))
+* **client:** preserve time zone in lenient date-time parsing ([62d4ba4](https://github.com/prelude-so/java-sdk/commit/62d4ba43268ead94f6f1ab811138bea8053a906e))
+* **client:** send retry count header for max retries 0 ([db20e56](https://github.com/prelude-so/java-sdk/commit/db20e56fa9a8b373305bff3ae10afea7bbd0ff53))
+* date time deserialization leniency ([b53df43](https://github.com/prelude-so/java-sdk/commit/b53df4388bfd39591bc9952d8039ea5e76bc6a99))
+* fix request delays for retrying to be more respectful of high requested delays ([249bfd4](https://github.com/prelude-so/java-sdk/commit/249bfd4b8dc6f64ff54c9cab37a66ab27ca258b5))
+
+
+### Chores
+
+* **ci:** skip uploading artifacts on stainless-internal branches ([2737021](https://github.com/prelude-so/java-sdk/commit/27370217d777fa1ebf591100e2be408388575241))
+* **ci:** upgrade `actions/github-script` ([47e61f7](https://github.com/prelude-so/java-sdk/commit/47e61f7b26522ad8336e16e76149d6aeb34ad4e5))
+* **ci:** upgrade `actions/setup-java` ([e3a09ff](https://github.com/prelude-so/java-sdk/commit/e3a09ffbe9d651ef627986ab8665898ddbdaf089))
+* **docs:** add missing descriptions ([edc87cc](https://github.com/prelude-so/java-sdk/commit/edc87cc96ccd7f8788b4394d4d6f8274f501ad91))
+* drop apache dependency ([ea51709](https://github.com/prelude-so/java-sdk/commit/ea517099a868cfbc5692b303c02f3f26078442b3))
+* **internal:** allow passing args to `./scripts/test` ([68d4da0](https://github.com/prelude-so/java-sdk/commit/68d4da01ab6ef8c673849405051b98d5d876841c))
+* **internal:** bump palantir-java-format ([4460207](https://github.com/prelude-so/java-sdk/commit/4460207c304c03b09598c8a2b1cfa7c86a4a6190))
+* **internal:** clean up maven repo artifact script and add html documentation to repo root ([88364fc](https://github.com/prelude-so/java-sdk/commit/88364fca4159730830a8d2a41b93d7684c04c01f))
+* **internal:** correct cache invalidation for `SKIP_MOCK_TESTS` ([27772a9](https://github.com/prelude-so/java-sdk/commit/27772a95464ad4ca072493d67655fb09cfb0ab14))
+* **internal:** depend on packages directly in example ([db20e56](https://github.com/prelude-so/java-sdk/commit/db20e56fa9a8b373305bff3ae10afea7bbd0ff53))
+* **internal:** expand imports ([ee924bd](https://github.com/prelude-so/java-sdk/commit/ee924bd9b90bfd9454c4622c6ba891fb21a0fd5c))
+* **internal:** improve maven repo docs ([d891959](https://github.com/prelude-so/java-sdk/commit/d8919592a63c3c766cc07a682294c0918a896aea))
+* **internal:** make `OkHttp` constructor internal ([4d0d7a8](https://github.com/prelude-so/java-sdk/commit/4d0d7a8469d9a062be145775327227919a816340))
+* **internal:** support uploading Maven repo artifacts to stainless package server ([141a5d6](https://github.com/prelude-so/java-sdk/commit/141a5d618305ea5cb1d17eb2da4f3b578727fd02))
+* **internal:** update `actions/checkout` version ([bcb84ae](https://github.com/prelude-so/java-sdk/commit/bcb84ae66e0256dc8509d802e905bb73610f85cb))
+* **internal:** update `TestServerExtension` comment ([2f1209d](https://github.com/prelude-so/java-sdk/commit/2f1209d3d5703a3565f11f4d3b08b09da524335b))
+* **internal:** update maven repo doc to include authentication ([95ce4a8](https://github.com/prelude-so/java-sdk/commit/95ce4a8a1c321fe80673d83ce10289ece6dc1895))
+* **internal:** upgrade AssertJ ([626ff7d](https://github.com/prelude-so/java-sdk/commit/626ff7d2113f7346c428e92a824ec50c38aec643))
+* make `Properties` more resilient to `null` ([7e517d6](https://github.com/prelude-so/java-sdk/commit/7e517d6ca2f9ed57d2263b98cd0c7ac8cd1b60b9))
+* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/prelude-so/java-sdk/issues/3240) in tests ([b53df43](https://github.com/prelude-so/java-sdk/commit/b53df4388bfd39591bc9952d8039ea5e76bc6a99))
+* **test:** do not count install time for mock server timeout ([d631a4b](https://github.com/prelude-so/java-sdk/commit/d631a4b7a630579d45fa2330c9c2a87c22d34b2c))
+* update mock server docs ([9ab611b](https://github.com/prelude-so/java-sdk/commit/9ab611b92520a6f17ad9c8ad4017bfe34ddf1c5e))
+
## 0.10.0 (2025-12-05)
Full Changelog: [v0.9.0...v0.10.0](https://github.com/prelude-so/java-sdk/compare/v0.9.0...v0.10.0)
diff --git a/LICENSE b/LICENSE
index 69f0a677..ed784599 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 Prelude
+ Copyright 2026 Prelude
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 c825dd98..dfe6ae52 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/so.prelude.sdk/prelude-java/0.10.0)
-[](https://javadoc.io/doc/so.prelude.sdk/prelude-java/0.10.0)
+[](https://central.sonatype.com/artifact/so.prelude.sdk/prelude-java/0.11.0)
+[](https://javadoc.io/doc/so.prelude.sdk/prelude-java/0.11.0)
@@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/).
-The REST API documentation can be found on [docs.prelude.so](https://docs.prelude.so). Javadocs are available on [javadoc.io](https://javadoc.io/doc/so.prelude.sdk/prelude-java/0.10.0).
+The REST API documentation can be found on [docs.prelude.so](https://docs.prelude.so). Javadocs are available on [javadoc.io](https://javadoc.io/doc/so.prelude.sdk/prelude-java/0.11.0).
@@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.prelude.so](https://docs.prelud
### Gradle
```kotlin
-implementation("so.prelude.sdk:prelude-java:0.10.0")
+implementation("so.prelude.sdk:prelude-java:0.11.0")
```
### Maven
@@ -33,7 +33,7 @@ implementation("so.prelude.sdk:prelude-java:0.10.0")
so.prelude.sdk
prelude-java
- 0.10.0
+ 0.11.0
```
@@ -282,6 +282,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
@@ -357,6 +359,25 @@ PreludeClient client = PreludeOkHttpClient.builder()
.build();
```
+### Connection pooling
+
+To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods:
+
+```java
+import java.time.Duration;
+import so.prelude.sdk.client.PreludeClient;
+import so.prelude.sdk.client.okhttp.PreludeOkHttpClient;
+
+PreludeClient client = PreludeOkHttpClient.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]
diff --git a/build.gradle.kts b/build.gradle.kts
index 7627e51d..1e231a8c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "so.prelude.sdk"
- version = "0.10.0" // x-release-please-version
+ version = "0.11.0" // x-release-please-version
}
subprojects {
diff --git a/buildSrc/src/main/kotlin/prelude.java.gradle.kts b/buildSrc/src/main/kotlin/prelude.java.gradle.kts
index 70fc33f4..8f4f902a 100644
--- a/buildSrc/src/main/kotlin/prelude.java.gradle.kts
+++ b/buildSrc/src/main/kotlin/prelude.java.gradle.kts
@@ -45,7 +45,7 @@ tasks.withType().configureEach {
val palantir by configurations.creating
dependencies {
- palantir("com.palantir.javaformat:palantir-java-format:2.73.0")
+ palantir("com.palantir.javaformat:palantir-java-format:2.89.0")
}
fun registerPalantir(
diff --git a/buildSrc/src/main/kotlin/prelude.kotlin.gradle.kts b/buildSrc/src/main/kotlin/prelude.kotlin.gradle.kts
index 08157260..d3d6ee7e 100644
--- a/buildSrc/src/main/kotlin/prelude.kotlin.gradle.kts
+++ b/buildSrc/src/main/kotlin/prelude.kotlin.gradle.kts
@@ -33,6 +33,9 @@ kotlin {
tasks.withType().configureEach {
systemProperty("junit.jupiter.execution.parallel.enabled", true)
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
+
+ // `SKIP_MOCK_TESTS` affects which tests run so it must be added as input for proper cache invalidation.
+ inputs.property("skipMockTests", System.getenv("SKIP_MOCK_TESTS")).optional(true)
}
val ktfmt by configurations.creating
diff --git a/buildSrc/src/main/kotlin/prelude.publish.gradle.kts b/buildSrc/src/main/kotlin/prelude.publish.gradle.kts
index 4c60cca0..4aae353d 100644
--- a/buildSrc/src/main/kotlin/prelude.publish.gradle.kts
+++ b/buildSrc/src/main/kotlin/prelude.publish.gradle.kts
@@ -7,6 +7,17 @@ plugins {
id("com.vanniktech.maven.publish")
}
+publishing {
+ repositories {
+ if (project.hasProperty("publishLocal")) {
+ maven {
+ name = "LocalFileSystem"
+ url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo")
+ }
+ }
+ }
+}
+
repositories {
gradlePluginPortal()
mavenCentral()
@@ -17,8 +28,10 @@ extra["signingInMemoryKeyId"] = System.getenv("GPG_SIGNING_KEY_ID")
extra["signingInMemoryKeyPassword"] = System.getenv("GPG_SIGNING_PASSWORD")
configure {
- signAllPublications()
- publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
+ if (!project.hasProperty("publishLocal")) {
+ signAllPublications()
+ publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
+ }
coordinates(project.group.toString(), project.name, project.version.toString())
configure(
diff --git a/prelude-java-client-okhttp/build.gradle.kts b/prelude-java-client-okhttp/build.gradle.kts
index d5d05715..a3b01928 100644
--- a/prelude-java-client-okhttp/build.gradle.kts
+++ b/prelude-java-client-okhttp/build.gradle.kts
@@ -10,6 +10,6 @@ dependencies {
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/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/OkHttpClient.kt b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/OkHttpClient.kt
index 8ab0eba3..5c5772fd 100644
--- a/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/OkHttpClient.kt
+++ b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/OkHttpClient.kt
@@ -6,11 +6,15 @@ 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 okhttp3.Call
import okhttp3.Callback
+import okhttp3.ConnectionPool
+import okhttp3.Dispatcher
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
@@ -31,7 +35,7 @@ import so.prelude.sdk.core.http.HttpResponse
import so.prelude.sdk.errors.PreludeIoException
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)
@@ -198,6 +202,9 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien
private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = 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 +215,32 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ /**
+ * 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 +256,32 @@ 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 {
+ 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) {
diff --git a/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClient.kt b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClient.kt
index 17a2890f..efeb2c4b 100644
--- a/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClient.kt
+++ b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClient.kt
@@ -7,6 +7,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 +45,78 @@ class PreludeOkHttpClient 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 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())
+ /**
+ * 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.
*
@@ -297,6 +360,9 @@ class PreludeOkHttpClient private constructor() {
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
+ .maxIdleConnections(maxIdleConnections)
+ .keepAliveDuration(keepAliveDuration)
+ .dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
.hostnameVerifier(hostnameVerifier)
diff --git a/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClientAsync.kt b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClientAsync.kt
index b49e17e6..6263f2f7 100644
--- a/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClientAsync.kt
+++ b/prelude-java-client-okhttp/src/main/kotlin/so/prelude/sdk/client/okhttp/PreludeOkHttpClientAsync.kt
@@ -7,6 +7,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 +45,78 @@ class PreludeOkHttpClientAsync 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 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())
+ /**
+ * 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.
*
@@ -297,6 +360,9 @@ class PreludeOkHttpClientAsync private constructor() {
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
+ .maxIdleConnections(maxIdleConnections)
+ .keepAliveDuration(keepAliveDuration)
+ .dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
.hostnameVerifier(hostnameVerifier)
diff --git a/prelude-java-core/build.gradle.kts b/prelude-java-core/build.gradle.kts
index a880fda5..31f51449 100644
--- a/prelude-java-core/build.gradle.kts
+++ b/prelude-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(":prelude-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/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClient.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClient.kt
index 5e094710..278c33cf 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClient.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClient.kt
@@ -47,16 +47,25 @@ interface PreludeClient {
*/
fun withOptions(modifier: Consumer): PreludeClient
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
fun lookup(): LookupService
+ /** Send transactional and marketing messages with compliance enforcement. */
fun notify(): NotifyService
+ /** Send transactional messages (deprecated - use Notify API instead). */
fun transactional(): TransactionalService
+ /** Verify phone numbers. */
fun verification(): VerificationService
+ /** Verify phone numbers. */
fun verificationManagement(): VerificationManagementService
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
fun watch(): WatchService
/**
@@ -82,16 +91,25 @@ interface PreludeClient {
*/
fun withOptions(modifier: Consumer): PreludeClient.WithRawResponse
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
fun lookup(): LookupService.WithRawResponse
+ /** Send transactional and marketing messages with compliance enforcement. */
fun notify(): NotifyService.WithRawResponse
+ /** Send transactional messages (deprecated - use Notify API instead). */
fun transactional(): TransactionalService.WithRawResponse
+ /** Verify phone numbers. */
fun verification(): VerificationService.WithRawResponse
+ /** Verify phone numbers. */
fun verificationManagement(): VerificationManagementService.WithRawResponse
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
fun watch(): WatchService.WithRawResponse
}
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsync.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsync.kt
index 634a2518..2fd36276 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsync.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsync.kt
@@ -47,16 +47,25 @@ interface PreludeClientAsync {
*/
fun withOptions(modifier: Consumer): PreludeClientAsync
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
fun lookup(): LookupServiceAsync
+ /** Send transactional and marketing messages with compliance enforcement. */
fun notify(): NotifyServiceAsync
+ /** Send transactional messages (deprecated - use Notify API instead). */
fun transactional(): TransactionalServiceAsync
+ /** Verify phone numbers. */
fun verification(): VerificationServiceAsync
+ /** Verify phone numbers. */
fun verificationManagement(): VerificationManagementServiceAsync
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
fun watch(): WatchServiceAsync
/**
@@ -86,16 +95,25 @@ interface PreludeClientAsync {
modifier: Consumer
): PreludeClientAsync.WithRawResponse
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
fun lookup(): LookupServiceAsync.WithRawResponse
+ /** Send transactional and marketing messages with compliance enforcement. */
fun notify(): NotifyServiceAsync.WithRawResponse
+ /** Send transactional messages (deprecated - use Notify API instead). */
fun transactional(): TransactionalServiceAsync.WithRawResponse
+ /** Verify phone numbers. */
fun verification(): VerificationServiceAsync.WithRawResponse
+ /** Verify phone numbers. */
fun verificationManagement(): VerificationManagementServiceAsync.WithRawResponse
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
fun watch(): WatchServiceAsync.WithRawResponse
}
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsyncImpl.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsyncImpl.kt
index b28b3eff..2fab033f 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsyncImpl.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientAsyncImpl.kt
@@ -66,17 +66,26 @@ class PreludeClientAsyncImpl(private val clientOptions: ClientOptions) : Prelude
override fun withOptions(modifier: Consumer): PreludeClientAsync =
PreludeClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
override fun lookup(): LookupServiceAsync = lookup
+ /** Send transactional and marketing messages with compliance enforcement. */
override fun notify(): NotifyServiceAsync = notify
+ /** Send transactional messages (deprecated - use Notify API instead). */
override fun transactional(): TransactionalServiceAsync = transactional
+ /** Verify phone numbers. */
override fun verification(): VerificationServiceAsync = verification
+ /** Verify phone numbers. */
override fun verificationManagement(): VerificationManagementServiceAsync =
verificationManagement
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
override fun watch(): WatchServiceAsync = watch
override fun close() = clientOptions.close()
@@ -116,17 +125,26 @@ class PreludeClientAsyncImpl(private val clientOptions: ClientOptions) : Prelude
clientOptions.toBuilder().apply(modifier::accept).build()
)
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
override fun lookup(): LookupServiceAsync.WithRawResponse = lookup
+ /** Send transactional and marketing messages with compliance enforcement. */
override fun notify(): NotifyServiceAsync.WithRawResponse = notify
+ /** Send transactional messages (deprecated - use Notify API instead). */
override fun transactional(): TransactionalServiceAsync.WithRawResponse = transactional
+ /** Verify phone numbers. */
override fun verification(): VerificationServiceAsync.WithRawResponse = verification
+ /** Verify phone numbers. */
override fun verificationManagement(): VerificationManagementServiceAsync.WithRawResponse =
verificationManagement
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
override fun watch(): WatchServiceAsync.WithRawResponse = watch
}
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientImpl.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientImpl.kt
index 23d7e0b3..f3848303 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientImpl.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/client/PreludeClientImpl.kt
@@ -60,16 +60,25 @@ class PreludeClientImpl(private val clientOptions: ClientOptions) : PreludeClien
override fun withOptions(modifier: Consumer): PreludeClient =
PreludeClientImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
override fun lookup(): LookupService = lookup
+ /** Send transactional and marketing messages with compliance enforcement. */
override fun notify(): NotifyService = notify
+ /** Send transactional messages (deprecated - use Notify API instead). */
override fun transactional(): TransactionalService = transactional
+ /** Verify phone numbers. */
override fun verification(): VerificationService = verification
+ /** Verify phone numbers. */
override fun verificationManagement(): VerificationManagementService = verificationManagement
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
override fun watch(): WatchService = watch
override fun close() = clientOptions.close()
@@ -108,17 +117,26 @@ class PreludeClientImpl(private val clientOptions: ClientOptions) : PreludeClien
clientOptions.toBuilder().apply(modifier::accept).build()
)
+ /**
+ * Retrieve detailed information about a phone number including carrier data, line type, and
+ * portability status.
+ */
override fun lookup(): LookupService.WithRawResponse = lookup
+ /** Send transactional and marketing messages with compliance enforcement. */
override fun notify(): NotifyService.WithRawResponse = notify
+ /** Send transactional messages (deprecated - use Notify API instead). */
override fun transactional(): TransactionalService.WithRawResponse = transactional
+ /** Verify phone numbers. */
override fun verification(): VerificationService.WithRawResponse = verification
+ /** Verify phone numbers. */
override fun verificationManagement(): VerificationManagementService.WithRawResponse =
verificationManagement
+ /** Evaluate email addresses and phone numbers for trustworthiness. */
override fun watch(): WatchService.WithRawResponse = watch
}
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ClientOptions.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ClientOptions.kt
index a4ffe36f..3c3879ef 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ClientOptions.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ClientOptions.kt
@@ -404,6 +404,7 @@ 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())
apiToken.let {
if (!it.isEmpty()) {
headers.put("Authorization", "Bearer $it")
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ObjectMappers.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ObjectMappers.kt
index ac9f9608..4a638086 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ObjectMappers.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/ObjectMappers.kt
@@ -24,7 +24,8 @@ 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
@@ -36,7 +37,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 +48,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 +66,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 +132,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 +149,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 +158,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/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/Properties.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/Properties.kt
index 66c3c20c..63a9b65f 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/Properties.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/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 =
- PreludeClient::class.java.`package`.implementationVersion ?: "unknown"
+ PreludeClient::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/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequest.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequest.kt
index cf5ac118..071b534c 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequest.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequest.kt
@@ -1,5 +1,6 @@
package so.prelude.sdk.core.http
+import java.net.URLEncoder
import so.prelude.sdk.core.checkRequired
import so.prelude.sdk.core.toImmutable
@@ -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/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequestBodies.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequestBodies.kt
index 2e17bc28..8347737c 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequestBodies.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/HttpRequestBodies.kt
@@ -7,13 +7,13 @@ package so.prelude.sdk.core.http
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
import so.prelude.sdk.core.MultipartField
+import so.prelude.sdk.core.toImmutable
import so.prelude.sdk.errors.PreludeInvalidDataException
@JvmSynthetic
@@ -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 PreludeInvalidDataException(
- "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 PreludeInvalidDataException("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 PreludeInvalidDataException(
+ "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 PreludeInvalidDataException("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/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/RetryingHttpClient.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/RetryingHttpClient.kt
index bced2b28..e1b8e20b 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/RetryingHttpClient.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/core/http/RetryingHttpClient.kt
@@ -1,3 +1,5 @@
+// File generated from our OpenAPI spec by Stainless.
+
package so.prelude.sdk.core.http
import java.io.IOException
@@ -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?,
@@ -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/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchParams.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchParams.kt
index ff7a3895..d742433f 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchParams.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchParams.kt
@@ -64,6 +64,15 @@ private constructor(
*/
fun correlationId(): Optional = body.correlationId()
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = body.document()
+
/**
* The message expiration date in RFC3339 format. Messages will not be sent after this time.
*
@@ -141,6 +150,13 @@ private constructor(
*/
fun _correlationId(): JsonField = body._correlationId()
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ fun _document(): JsonField = body._document()
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -231,7 +247,7 @@ private constructor(
* - [to]
* - [callbackUrl]
* - [correlationId]
- * - [expiresAt]
+ * - [document]
* - etc.
*/
fun body(body: Body) = apply { this.body = body.toBuilder() }
@@ -292,6 +308,21 @@ private constructor(
body.correlationId(correlationId)
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = apply { body.document(document) }
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun document(document: JsonField) = apply { body.document(document) }
+
/**
* The message expiration date in RFC3339 format. Messages will not be sent after this time.
*/
@@ -524,6 +555,7 @@ private constructor(
private val to: JsonField>,
private val callbackUrl: JsonField,
private val correlationId: JsonField,
+ private val document: JsonField,
private val expiresAt: JsonField,
private val from: JsonField,
private val locale: JsonField,
@@ -545,6 +577,9 @@ private constructor(
@JsonProperty("correlation_id")
@ExcludeMissing
correlationId: JsonField = JsonMissing.of(),
+ @JsonProperty("document")
+ @ExcludeMissing
+ document: JsonField = JsonMissing.of(),
@JsonProperty("expires_at")
@ExcludeMissing
expiresAt: JsonField = JsonMissing.of(),
@@ -564,6 +599,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -605,6 +641,15 @@ private constructor(
*/
fun correlationId(): Optional = correlationId.getOptional("correlation_id")
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = document.getOptional("document")
+
/**
* The message expiration date in RFC3339 format. Messages will not be sent after this time.
*
@@ -690,6 +735,13 @@ private constructor(
@ExcludeMissing
fun _correlationId(): JsonField = correlationId
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("document") @ExcludeMissing fun _document(): JsonField = document
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -774,6 +826,7 @@ private constructor(
private var to: JsonField>? = null
private var callbackUrl: JsonField = JsonMissing.of()
private var correlationId: JsonField = JsonMissing.of()
+ private var document: JsonField = JsonMissing.of()
private var expiresAt: JsonField = JsonMissing.of()
private var from: JsonField = JsonMissing.of()
private var locale: JsonField = JsonMissing.of()
@@ -788,6 +841,7 @@ private constructor(
to = body.to.map { it.toMutableList() }
callbackUrl = body.callbackUrl
correlationId = body.correlationId
+ document = body.document
expiresAt = body.expiresAt
from = body.from
locale = body.locale
@@ -859,6 +913,21 @@ private constructor(
this.correlationId = correlationId
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = document(JsonField.of(document))
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun document(document: JsonField) = apply { this.document = document }
+
/**
* The message expiration date in RFC3339 format. Messages will not be sent after this
* time.
@@ -982,6 +1051,7 @@ private constructor(
checkRequired("to", to).map { it.toImmutable() },
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -1003,6 +1073,7 @@ private constructor(
to()
callbackUrl()
correlationId()
+ document().ifPresent { it.validate() }
expiresAt()
from()
locale()
@@ -1032,6 +1103,7 @@ private constructor(
(to.asKnown().getOrNull()?.size ?: 0) +
(if (callbackUrl.asKnown().isPresent) 1 else 0) +
(if (correlationId.asKnown().isPresent) 1 else 0) +
+ (document.asKnown().getOrNull()?.validity() ?: 0) +
(if (expiresAt.asKnown().isPresent) 1 else 0) +
(if (from.asKnown().isPresent) 1 else 0) +
(if (locale.asKnown().isPresent) 1 else 0) +
@@ -1049,6 +1121,7 @@ private constructor(
to == other.to &&
callbackUrl == other.callbackUrl &&
correlationId == other.correlationId &&
+ document == other.document &&
expiresAt == other.expiresAt &&
from == other.from &&
locale == other.locale &&
@@ -1064,6 +1137,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -1077,7 +1151,210 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, scheduleAt=$scheduleAt, variables=$variables, additionalProperties=$additionalProperties}"
+ "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, document=$document, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, scheduleAt=$scheduleAt, variables=$variables, additionalProperties=$additionalProperties}"
+ }
+
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ class Document
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val filename: JsonField,
+ private val url: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("filename")
+ @ExcludeMissing
+ filename: JsonField = JsonMissing.of(),
+ @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(),
+ ) : this(filename, url, mutableMapOf())
+
+ /**
+ * The filename to display for the document.
+ *
+ * @throws PreludeInvalidDataException 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 filename(): String = filename.getRequired("filename")
+
+ /**
+ * The URL of the document to attach. Must be a valid HTTP or HTTPS URL.
+ *
+ * @throws PreludeInvalidDataException 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")
+
+ /**
+ * Returns the raw JSON value of [filename].
+ *
+ * Unlike [filename], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("filename") @ExcludeMissing fun _filename(): JsonField = filename
+
+ /**
+ * 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 [Document].
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Document]. */
+ class Builder internal constructor() {
+
+ private var filename: JsonField? = null
+ private var url: JsonField? = null
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(document: Document) = apply {
+ filename = document.filename
+ url = document.url
+ additionalProperties = document.additionalProperties.toMutableMap()
+ }
+
+ /** The filename to display for the document. */
+ fun filename(filename: String) = filename(JsonField.of(filename))
+
+ /**
+ * Sets [Builder.filename] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.filename] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun filename(filename: JsonField) = apply { this.filename = filename }
+
+ /** The URL of the document to attach. Must be a valid HTTP or HTTPS URL. */
+ 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 [Document].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Document =
+ Document(
+ checkRequired("filename", filename),
+ checkRequired("url", url),
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Document = apply {
+ if (validated) {
+ return@apply
+ }
+
+ filename()
+ url()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: PreludeInvalidDataException) {
+ 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 (filename.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 Document &&
+ filename == other.filename &&
+ url == other.url &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy { Objects.hash(filename, url, additionalProperties) }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "Document{filename=$filename, url=$url, additionalProperties=$additionalProperties}"
}
/** Preferred channel for delivery. If unavailable, automatic fallback applies. */
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchResponse.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchResponse.kt
index 3cd0ea0c..9249a3eb 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchResponse.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendBatchResponse.kt
@@ -11,6 +11,7 @@ import java.util.Collections
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
+import so.prelude.sdk.core.Enum
import so.prelude.sdk.core.ExcludeMissing
import so.prelude.sdk.core.JsonField
import so.prelude.sdk.core.JsonMissing
@@ -885,6 +886,8 @@ private constructor(
private val id: JsonField,
private val correlationId: JsonField,
private val createdAt: JsonField,
+ private val encoding: JsonField,
+ private val estimatedSegmentCount: JsonField,
private val expiresAt: JsonField,
private val from: JsonField,
private val locale: JsonField,
@@ -902,6 +905,12 @@ private constructor(
@JsonProperty("created_at")
@ExcludeMissing
createdAt: JsonField = JsonMissing.of(),
+ @JsonProperty("encoding")
+ @ExcludeMissing
+ encoding: JsonField = JsonMissing.of(),
+ @JsonProperty("estimated_segment_count")
+ @ExcludeMissing
+ estimatedSegmentCount: JsonField = JsonMissing.of(),
@JsonProperty("expires_at")
@ExcludeMissing
expiresAt: JsonField = JsonMissing.of(),
@@ -917,6 +926,8 @@ private constructor(
id,
correlationId,
createdAt,
+ encoding,
+ estimatedSegmentCount,
expiresAt,
from,
locale,
@@ -949,6 +960,27 @@ private constructor(
*/
fun createdAt(): Optional = createdAt.getOptional("created_at")
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard characters
+ * (up to 160 chars per segment), while UCS-2 supports Unicode including emoji (up to 70
+ * chars per segment). Only present for SMS messages.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun encoding(): Optional = encoding.getOptional("encoding")
+
+ /**
+ * The estimated number of SMS segments for this message. This value is not contractual;
+ * the actual segment count will be determined after the SMS is sent by the provider.
+ * Only present for SMS messages.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun estimatedSegmentCount(): Optional =
+ estimatedSegmentCount.getOptional("estimated_segment_count")
+
/**
* The message expiration date in RFC3339 format.
*
@@ -1016,6 +1048,26 @@ private constructor(
@ExcludeMissing
fun _createdAt(): JsonField = createdAt
+ /**
+ * Returns the raw JSON value of [encoding].
+ *
+ * Unlike [encoding], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("encoding")
+ @ExcludeMissing
+ fun _encoding(): JsonField = encoding
+
+ /**
+ * Returns the raw JSON value of [estimatedSegmentCount].
+ *
+ * Unlike [estimatedSegmentCount], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("estimated_segment_count")
+ @ExcludeMissing
+ fun _estimatedSegmentCount(): JsonField = estimatedSegmentCount
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -1081,6 +1133,8 @@ private constructor(
private var id: JsonField = JsonMissing.of()
private var correlationId: JsonField = JsonMissing.of()
private var createdAt: JsonField = JsonMissing.of()
+ private var encoding: JsonField = JsonMissing.of()
+ private var estimatedSegmentCount: JsonField = JsonMissing.of()
private var expiresAt: JsonField = JsonMissing.of()
private var from: JsonField = JsonMissing.of()
private var locale: JsonField = JsonMissing.of()
@@ -1093,6 +1147,8 @@ private constructor(
id = message.id
correlationId = message.correlationId
createdAt = message.createdAt
+ encoding = message.encoding
+ estimatedSegmentCount = message.estimatedSegmentCount
expiresAt = message.expiresAt
from = message.from
locale = message.locale
@@ -1142,6 +1198,41 @@ private constructor(
this.createdAt = createdAt
}
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard
+ * characters (up to 160 chars per segment), while UCS-2 supports Unicode including
+ * emoji (up to 70 chars per segment). Only present for SMS messages.
+ */
+ fun encoding(encoding: Encoding) = encoding(JsonField.of(encoding))
+
+ /**
+ * Sets [Builder.encoding] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.encoding] with a well-typed [Encoding] value
+ * instead. This method is primarily for setting the field to an undocumented or not
+ * yet supported value.
+ */
+ fun encoding(encoding: JsonField) = apply { this.encoding = encoding }
+
+ /**
+ * The estimated number of SMS segments for this message. This value is not
+ * contractual; the actual segment count will be determined after the SMS is sent by
+ * the provider. Only present for SMS messages.
+ */
+ fun estimatedSegmentCount(estimatedSegmentCount: Long) =
+ estimatedSegmentCount(JsonField.of(estimatedSegmentCount))
+
+ /**
+ * Sets [Builder.estimatedSegmentCount] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.estimatedSegmentCount] with a well-typed [Long]
+ * value instead. This method is primarily for setting the field to an undocumented
+ * or not yet supported value.
+ */
+ fun estimatedSegmentCount(estimatedSegmentCount: JsonField) = apply {
+ this.estimatedSegmentCount = estimatedSegmentCount
+ }
+
/** The message expiration date in RFC3339 format. */
fun expiresAt(expiresAt: OffsetDateTime) = expiresAt(JsonField.of(expiresAt))
@@ -1240,6 +1331,8 @@ private constructor(
id,
correlationId,
createdAt,
+ encoding,
+ estimatedSegmentCount,
expiresAt,
from,
locale,
@@ -1259,6 +1352,8 @@ private constructor(
id()
correlationId()
createdAt()
+ encoding().ifPresent { it.validate() }
+ estimatedSegmentCount()
expiresAt()
from()
locale()
@@ -1286,12 +1381,150 @@ private constructor(
(if (id.asKnown().isPresent) 1 else 0) +
(if (correlationId.asKnown().isPresent) 1 else 0) +
(if (createdAt.asKnown().isPresent) 1 else 0) +
+ (encoding.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (estimatedSegmentCount.asKnown().isPresent) 1 else 0) +
(if (expiresAt.asKnown().isPresent) 1 else 0) +
(if (from.asKnown().isPresent) 1 else 0) +
(if (locale.asKnown().isPresent) 1 else 0) +
(if (scheduleAt.asKnown().isPresent) 1 else 0) +
(if (to.asKnown().isPresent) 1 else 0)
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard characters
+ * (up to 160 chars per segment), while UCS-2 supports Unicode including emoji (up to 70
+ * chars per segment). Only present for SMS messages.
+ */
+ class Encoding @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 GSM_7 = of("GSM-7")
+
+ @JvmField val UCS_2 = of("UCS-2")
+
+ @JvmStatic fun of(value: String) = Encoding(JsonField.of(value))
+ }
+
+ /** An enum containing [Encoding]'s known values. */
+ enum class Known {
+ GSM_7,
+ UCS_2,
+ }
+
+ /**
+ * An enum containing [Encoding]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [Encoding] 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 {
+ GSM_7,
+ UCS_2,
+ /**
+ * An enum member indicating that [Encoding] 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) {
+ GSM_7 -> Value.GSM_7
+ UCS_2 -> Value.UCS_2
+ 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 PreludeInvalidDataException if this class instance's value is a not a
+ * known member.
+ */
+ fun known(): Known =
+ when (this) {
+ GSM_7 -> Known.GSM_7
+ UCS_2 -> Known.UCS_2
+ else -> throw PreludeInvalidDataException("Unknown Encoding: $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 PreludeInvalidDataException if this class instance's value does not have
+ * the expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString().orElseThrow {
+ PreludeInvalidDataException("Value is not a String")
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Encoding = apply {
+ if (validated) {
+ return@apply
+ }
+
+ known()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: PreludeInvalidDataException) {
+ 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 Encoding && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
+
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
@@ -1301,6 +1534,8 @@ private constructor(
id == other.id &&
correlationId == other.correlationId &&
createdAt == other.createdAt &&
+ encoding == other.encoding &&
+ estimatedSegmentCount == other.estimatedSegmentCount &&
expiresAt == other.expiresAt &&
from == other.from &&
locale == other.locale &&
@@ -1314,6 +1549,8 @@ private constructor(
id,
correlationId,
createdAt,
+ encoding,
+ estimatedSegmentCount,
expiresAt,
from,
locale,
@@ -1326,7 +1563,7 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Message{id=$id, correlationId=$correlationId, createdAt=$createdAt, expiresAt=$expiresAt, from=$from, locale=$locale, scheduleAt=$scheduleAt, to=$to, additionalProperties=$additionalProperties}"
+ "Message{id=$id, correlationId=$correlationId, createdAt=$createdAt, encoding=$encoding, estimatedSegmentCount=$estimatedSegmentCount, expiresAt=$expiresAt, from=$from, locale=$locale, scheduleAt=$scheduleAt, to=$to, additionalProperties=$additionalProperties}"
}
override fun equals(other: Any?): Boolean {
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendParams.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendParams.kt
index f62667b8..c0907faf 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendParams.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendParams.kt
@@ -67,6 +67,15 @@ private constructor(
*/
fun correlationId(): Optional = body.correlationId()
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = body.document()
+
/**
* The message expiration date in RFC3339 format. The message will not be sent if this time is
* reached.
@@ -150,6 +159,13 @@ private constructor(
*/
fun _correlationId(): JsonField = body._correlationId()
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ fun _document(): JsonField = body._document()
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -240,7 +256,7 @@ private constructor(
* - [to]
* - [callbackUrl]
* - [correlationId]
- * - [expiresAt]
+ * - [document]
* - etc.
*/
fun body(body: Body) = apply { this.body = body.toBuilder() }
@@ -297,6 +313,21 @@ private constructor(
body.correlationId(correlationId)
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = apply { body.document(document) }
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun document(document: JsonField) = apply { body.document(document) }
+
/**
* The message expiration date in RFC3339 format. The message will not be sent if this time
* is reached.
@@ -535,6 +566,7 @@ private constructor(
private val to: JsonField,
private val callbackUrl: JsonField,
private val correlationId: JsonField,
+ private val document: JsonField,
private val expiresAt: JsonField,
private val from: JsonField,
private val locale: JsonField,
@@ -556,6 +588,9 @@ private constructor(
@JsonProperty("correlation_id")
@ExcludeMissing
correlationId: JsonField = JsonMissing.of(),
+ @JsonProperty("document")
+ @ExcludeMissing
+ document: JsonField = JsonMissing.of(),
@JsonProperty("expires_at")
@ExcludeMissing
expiresAt: JsonField = JsonMissing.of(),
@@ -575,6 +610,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -617,6 +653,15 @@ private constructor(
*/
fun correlationId(): Optional = correlationId.getOptional("correlation_id")
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = document.getOptional("document")
+
/**
* The message expiration date in RFC3339 format. The message will not be sent if this time
* is reached.
@@ -708,6 +753,13 @@ private constructor(
@ExcludeMissing
fun _correlationId(): JsonField = correlationId
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("document") @ExcludeMissing fun _document(): JsonField = document
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -792,6 +844,7 @@ private constructor(
private var to: JsonField? = null
private var callbackUrl: JsonField = JsonMissing.of()
private var correlationId: JsonField = JsonMissing.of()
+ private var document: JsonField = JsonMissing.of()
private var expiresAt: JsonField = JsonMissing.of()
private var from: JsonField = JsonMissing.of()
private var locale: JsonField = JsonMissing.of()
@@ -806,6 +859,7 @@ private constructor(
to = body.to
callbackUrl = body.callbackUrl
correlationId = body.correlationId
+ document = body.document
expiresAt = body.expiresAt
from = body.from
locale = body.locale
@@ -870,6 +924,21 @@ private constructor(
this.correlationId = correlationId
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = document(JsonField.of(document))
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun document(document: JsonField) = apply { this.document = document }
+
/**
* The message expiration date in RFC3339 format. The message will not be sent if this
* time is reached.
@@ -1002,6 +1071,7 @@ private constructor(
checkRequired("to", to),
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -1023,6 +1093,7 @@ private constructor(
to()
callbackUrl()
correlationId()
+ document().ifPresent { it.validate() }
expiresAt()
from()
locale()
@@ -1052,6 +1123,7 @@ private constructor(
(if (to.asKnown().isPresent) 1 else 0) +
(if (callbackUrl.asKnown().isPresent) 1 else 0) +
(if (correlationId.asKnown().isPresent) 1 else 0) +
+ (document.asKnown().getOrNull()?.validity() ?: 0) +
(if (expiresAt.asKnown().isPresent) 1 else 0) +
(if (from.asKnown().isPresent) 1 else 0) +
(if (locale.asKnown().isPresent) 1 else 0) +
@@ -1069,6 +1141,7 @@ private constructor(
to == other.to &&
callbackUrl == other.callbackUrl &&
correlationId == other.correlationId &&
+ document == other.document &&
expiresAt == other.expiresAt &&
from == other.from &&
locale == other.locale &&
@@ -1084,6 +1157,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -1097,7 +1171,210 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, scheduleAt=$scheduleAt, variables=$variables, additionalProperties=$additionalProperties}"
+ "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, document=$document, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, scheduleAt=$scheduleAt, variables=$variables, additionalProperties=$additionalProperties}"
+ }
+
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ class Document
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val filename: JsonField,
+ private val url: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("filename")
+ @ExcludeMissing
+ filename: JsonField = JsonMissing.of(),
+ @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(),
+ ) : this(filename, url, mutableMapOf())
+
+ /**
+ * The filename to display for the document.
+ *
+ * @throws PreludeInvalidDataException 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 filename(): String = filename.getRequired("filename")
+
+ /**
+ * The URL of the document to attach. Must be a valid HTTP or HTTPS URL.
+ *
+ * @throws PreludeInvalidDataException 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")
+
+ /**
+ * Returns the raw JSON value of [filename].
+ *
+ * Unlike [filename], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("filename") @ExcludeMissing fun _filename(): JsonField = filename
+
+ /**
+ * 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 [Document].
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Document]. */
+ class Builder internal constructor() {
+
+ private var filename: JsonField? = null
+ private var url: JsonField? = null
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(document: Document) = apply {
+ filename = document.filename
+ url = document.url
+ additionalProperties = document.additionalProperties.toMutableMap()
+ }
+
+ /** The filename to display for the document. */
+ fun filename(filename: String) = filename(JsonField.of(filename))
+
+ /**
+ * Sets [Builder.filename] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.filename] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun filename(filename: JsonField) = apply { this.filename = filename }
+
+ /** The URL of the document to attach. Must be a valid HTTP or HTTPS URL. */
+ 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 [Document].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Document =
+ Document(
+ checkRequired("filename", filename),
+ checkRequired("url", url),
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Document = apply {
+ if (validated) {
+ return@apply
+ }
+
+ filename()
+ url()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: PreludeInvalidDataException) {
+ 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 (filename.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 Document &&
+ filename == other.filename &&
+ url == other.url &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy { Objects.hash(filename, url, additionalProperties) }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "Document{filename=$filename, url=$url, additionalProperties=$additionalProperties}"
}
/**
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendResponse.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendResponse.kt
index c6cb2997..e3b5c37c 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendResponse.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/NotifySendResponse.kt
@@ -11,6 +11,7 @@ import java.util.Collections
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
+import so.prelude.sdk.core.Enum
import so.prelude.sdk.core.ExcludeMissing
import so.prelude.sdk.core.JsonField
import so.prelude.sdk.core.JsonMissing
@@ -30,6 +31,8 @@ private constructor(
private val variables: JsonField,
private val callbackUrl: JsonField,
private val correlationId: JsonField,
+ private val encoding: JsonField,
+ private val estimatedSegmentCount: JsonField,
private val from: JsonField,
private val scheduleAt: JsonField,
private val additionalProperties: MutableMap,
@@ -57,6 +60,10 @@ private constructor(
@JsonProperty("correlation_id")
@ExcludeMissing
correlationId: JsonField = JsonMissing.of(),
+ @JsonProperty("encoding") @ExcludeMissing encoding: JsonField = JsonMissing.of(),
+ @JsonProperty("estimated_segment_count")
+ @ExcludeMissing
+ estimatedSegmentCount: JsonField = JsonMissing.of(),
@JsonProperty("from") @ExcludeMissing from: JsonField = JsonMissing.of(),
@JsonProperty("schedule_at")
@ExcludeMissing
@@ -70,6 +77,8 @@ private constructor(
variables,
callbackUrl,
correlationId,
+ encoding,
+ estimatedSegmentCount,
from,
scheduleAt,
mutableMapOf(),
@@ -139,6 +148,27 @@ private constructor(
*/
fun correlationId(): Optional = correlationId.getOptional("correlation_id")
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard characters (up to 160
+ * chars per segment), while UCS-2 supports Unicode including emoji (up to 70 chars per
+ * segment). Only present for SMS messages.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun encoding(): Optional = encoding.getOptional("encoding")
+
+ /**
+ * The estimated number of SMS segments for this message. This value is not contractual; the
+ * actual segment count will be determined after the SMS is sent by the provider. Only present
+ * for SMS messages.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun estimatedSegmentCount(): Optional =
+ estimatedSegmentCount.getOptional("estimated_segment_count")
+
/**
* The Sender ID used for this message.
*
@@ -221,6 +251,23 @@ private constructor(
@ExcludeMissing
fun _correlationId(): JsonField = correlationId
+ /**
+ * Returns the raw JSON value of [encoding].
+ *
+ * Unlike [encoding], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("encoding") @ExcludeMissing fun _encoding(): JsonField = encoding
+
+ /**
+ * Returns the raw JSON value of [estimatedSegmentCount].
+ *
+ * Unlike [estimatedSegmentCount], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("estimated_segment_count")
+ @ExcludeMissing
+ fun _estimatedSegmentCount(): JsonField = estimatedSegmentCount
+
/**
* Returns the raw JSON value of [from].
*
@@ -278,6 +325,8 @@ private constructor(
private var variables: JsonField? = null
private var callbackUrl: JsonField = JsonMissing.of()
private var correlationId: JsonField = JsonMissing.of()
+ private var encoding: JsonField = JsonMissing.of()
+ private var estimatedSegmentCount: JsonField = JsonMissing.of()
private var from: JsonField = JsonMissing.of()
private var scheduleAt: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -292,6 +341,8 @@ private constructor(
variables = notifySendResponse.variables
callbackUrl = notifySendResponse.callbackUrl
correlationId = notifySendResponse.correlationId
+ encoding = notifySendResponse.encoding
+ estimatedSegmentCount = notifySendResponse.estimatedSegmentCount
from = notifySendResponse.from
scheduleAt = notifySendResponse.scheduleAt
additionalProperties = notifySendResponse.additionalProperties.toMutableMap()
@@ -393,6 +444,41 @@ private constructor(
this.correlationId = correlationId
}
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard characters (up to
+ * 160 chars per segment), while UCS-2 supports Unicode including emoji (up to 70 chars per
+ * segment). Only present for SMS messages.
+ */
+ fun encoding(encoding: Encoding) = encoding(JsonField.of(encoding))
+
+ /**
+ * Sets [Builder.encoding] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.encoding] with a well-typed [Encoding] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun encoding(encoding: JsonField) = apply { this.encoding = encoding }
+
+ /**
+ * The estimated number of SMS segments for this message. This value is not contractual; the
+ * actual segment count will be determined after the SMS is sent by the provider. Only
+ * present for SMS messages.
+ */
+ fun estimatedSegmentCount(estimatedSegmentCount: Long) =
+ estimatedSegmentCount(JsonField.of(estimatedSegmentCount))
+
+ /**
+ * Sets [Builder.estimatedSegmentCount] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.estimatedSegmentCount] with a well-typed [Long] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun estimatedSegmentCount(estimatedSegmentCount: JsonField) = apply {
+ this.estimatedSegmentCount = estimatedSegmentCount
+ }
+
/** The Sender ID used for this message. */
fun from(from: String) = from(JsonField.of(from))
@@ -468,6 +554,8 @@ private constructor(
checkRequired("variables", variables),
callbackUrl,
correlationId,
+ encoding,
+ estimatedSegmentCount,
from,
scheduleAt,
additionalProperties.toMutableMap(),
@@ -489,6 +577,8 @@ private constructor(
variables().validate()
callbackUrl()
correlationId()
+ encoding().ifPresent { it.validate() }
+ estimatedSegmentCount()
from()
scheduleAt()
validated = true
@@ -517,6 +607,8 @@ private constructor(
(variables.asKnown().getOrNull()?.validity() ?: 0) +
(if (callbackUrl.asKnown().isPresent) 1 else 0) +
(if (correlationId.asKnown().isPresent) 1 else 0) +
+ (encoding.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (estimatedSegmentCount.asKnown().isPresent) 1 else 0) +
(if (from.asKnown().isPresent) 1 else 0) +
(if (scheduleAt.asKnown().isPresent) 1 else 0)
@@ -620,6 +712,136 @@ private constructor(
override fun toString() = "Variables{additionalProperties=$additionalProperties}"
}
+ /**
+ * The SMS encoding type based on message content. GSM-7 supports standard characters (up to 160
+ * chars per segment), while UCS-2 supports Unicode including emoji (up to 70 chars per
+ * segment). Only present for SMS messages.
+ */
+ class Encoding @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 GSM_7 = of("GSM-7")
+
+ @JvmField val UCS_2 = of("UCS-2")
+
+ @JvmStatic fun of(value: String) = Encoding(JsonField.of(value))
+ }
+
+ /** An enum containing [Encoding]'s known values. */
+ enum class Known {
+ GSM_7,
+ UCS_2,
+ }
+
+ /**
+ * An enum containing [Encoding]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [Encoding] 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 {
+ GSM_7,
+ UCS_2,
+ /** An enum member indicating that [Encoding] 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) {
+ GSM_7 -> Value.GSM_7
+ UCS_2 -> Value.UCS_2
+ 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 PreludeInvalidDataException if this class instance's value is a not a known
+ * member.
+ */
+ fun known(): Known =
+ when (this) {
+ GSM_7 -> Known.GSM_7
+ UCS_2 -> Known.UCS_2
+ else -> throw PreludeInvalidDataException("Unknown Encoding: $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 PreludeInvalidDataException if this class instance's value does not have the
+ * expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString().orElseThrow { PreludeInvalidDataException("Value is not a String") }
+
+ private var validated: Boolean = false
+
+ fun validate(): Encoding = apply {
+ if (validated) {
+ return@apply
+ }
+
+ known()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: PreludeInvalidDataException) {
+ 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 Encoding && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
+
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
@@ -634,6 +856,8 @@ private constructor(
variables == other.variables &&
callbackUrl == other.callbackUrl &&
correlationId == other.correlationId &&
+ encoding == other.encoding &&
+ estimatedSegmentCount == other.estimatedSegmentCount &&
from == other.from &&
scheduleAt == other.scheduleAt &&
additionalProperties == other.additionalProperties
@@ -649,6 +873,8 @@ private constructor(
variables,
callbackUrl,
correlationId,
+ encoding,
+ estimatedSegmentCount,
from,
scheduleAt,
additionalProperties,
@@ -658,5 +884,5 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "NotifySendResponse{id=$id, createdAt=$createdAt, expiresAt=$expiresAt, templateId=$templateId, to=$to, variables=$variables, callbackUrl=$callbackUrl, correlationId=$correlationId, from=$from, scheduleAt=$scheduleAt, additionalProperties=$additionalProperties}"
+ "NotifySendResponse{id=$id, createdAt=$createdAt, expiresAt=$expiresAt, templateId=$templateId, to=$to, variables=$variables, callbackUrl=$callbackUrl, correlationId=$correlationId, encoding=$encoding, estimatedSegmentCount=$estimatedSegmentCount, from=$from, scheduleAt=$scheduleAt, additionalProperties=$additionalProperties}"
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/TransactionalSendParams.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/TransactionalSendParams.kt
index a54e10b9..25157c5e 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/TransactionalSendParams.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/TransactionalSendParams.kt
@@ -64,6 +64,15 @@ private constructor(
*/
fun correlationId(): Optional = body.correlationId()
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = body.document()
+
/**
* The message expiration date.
*
@@ -141,6 +150,13 @@ private constructor(
*/
fun _correlationId(): JsonField = body._correlationId()
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ fun _document(): JsonField = body._document()
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -224,7 +240,7 @@ private constructor(
* - [to]
* - [callbackUrl]
* - [correlationId]
- * - [expiresAt]
+ * - [document]
* - etc.
*/
fun body(body: Body) = apply { this.body = body.toBuilder() }
@@ -281,6 +297,21 @@ private constructor(
body.correlationId(correlationId)
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = apply { body.document(document) }
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun document(document: JsonField) = apply { body.document(document) }
+
/** The message expiration date. */
fun expiresAt(expiresAt: String) = apply { body.expiresAt(expiresAt) }
@@ -507,6 +538,7 @@ private constructor(
private val to: JsonField,
private val callbackUrl: JsonField,
private val correlationId: JsonField,
+ private val document: JsonField,
private val expiresAt: JsonField,
private val from: JsonField,
private val locale: JsonField,
@@ -527,6 +559,9 @@ private constructor(
@JsonProperty("correlation_id")
@ExcludeMissing
correlationId: JsonField = JsonMissing.of(),
+ @JsonProperty("document")
+ @ExcludeMissing
+ document: JsonField = JsonMissing.of(),
@JsonProperty("expires_at")
@ExcludeMissing
expiresAt: JsonField = JsonMissing.of(),
@@ -543,6 +578,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -584,6 +620,15 @@ private constructor(
*/
fun correlationId(): Optional = correlationId.getOptional("correlation_id")
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ *
+ * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun document(): Optional = document.getOptional("document")
+
/**
* The message expiration date.
*
@@ -669,6 +714,13 @@ private constructor(
@ExcludeMissing
fun _correlationId(): JsonField = correlationId
+ /**
+ * Returns the raw JSON value of [document].
+ *
+ * Unlike [document], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("document") @ExcludeMissing fun _document(): JsonField = document
+
/**
* Returns the raw JSON value of [expiresAt].
*
@@ -742,6 +794,7 @@ private constructor(
private var to: JsonField? = null
private var callbackUrl: JsonField = JsonMissing.of()
private var correlationId: JsonField = JsonMissing.of()
+ private var document: JsonField = JsonMissing.of()
private var expiresAt: JsonField = JsonMissing.of()
private var from: JsonField = JsonMissing.of()
private var locale: JsonField = JsonMissing.of()
@@ -755,6 +808,7 @@ private constructor(
to = body.to
callbackUrl = body.callbackUrl
correlationId = body.correlationId
+ document = body.document
expiresAt = body.expiresAt
from = body.from
locale = body.locale
@@ -819,6 +873,21 @@ private constructor(
this.correlationId = correlationId
}
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ fun document(document: Document) = document(JsonField.of(document))
+
+ /**
+ * Sets [Builder.document] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.document] with a well-typed [Document] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun document(document: JsonField) = apply { this.document = document }
+
/** The message expiration date. */
fun expiresAt(expiresAt: String) = expiresAt(JsonField.of(expiresAt))
@@ -934,6 +1003,7 @@ private constructor(
checkRequired("to", to),
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -954,6 +1024,7 @@ private constructor(
to()
callbackUrl()
correlationId()
+ document().ifPresent { it.validate() }
expiresAt()
from()
locale()
@@ -982,6 +1053,7 @@ private constructor(
(if (to.asKnown().isPresent) 1 else 0) +
(if (callbackUrl.asKnown().isPresent) 1 else 0) +
(if (correlationId.asKnown().isPresent) 1 else 0) +
+ (document.asKnown().getOrNull()?.validity() ?: 0) +
(if (expiresAt.asKnown().isPresent) 1 else 0) +
(if (from.asKnown().isPresent) 1 else 0) +
(if (locale.asKnown().isPresent) 1 else 0) +
@@ -998,6 +1070,7 @@ private constructor(
to == other.to &&
callbackUrl == other.callbackUrl &&
correlationId == other.correlationId &&
+ document == other.document &&
expiresAt == other.expiresAt &&
from == other.from &&
locale == other.locale &&
@@ -1012,6 +1085,7 @@ private constructor(
to,
callbackUrl,
correlationId,
+ document,
expiresAt,
from,
locale,
@@ -1024,7 +1098,210 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, variables=$variables, additionalProperties=$additionalProperties}"
+ "Body{templateId=$templateId, to=$to, callbackUrl=$callbackUrl, correlationId=$correlationId, document=$document, expiresAt=$expiresAt, from=$from, locale=$locale, preferredChannel=$preferredChannel, variables=$variables, additionalProperties=$additionalProperties}"
+ }
+
+ /**
+ * A document to attach to the message. Only supported on WhatsApp templates that have a
+ * document header.
+ */
+ class Document
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val filename: JsonField,
+ private val url: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("filename")
+ @ExcludeMissing
+ filename: JsonField = JsonMissing.of(),
+ @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(),
+ ) : this(filename, url, mutableMapOf())
+
+ /**
+ * The filename to display for the document.
+ *
+ * @throws PreludeInvalidDataException 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 filename(): String = filename.getRequired("filename")
+
+ /**
+ * The URL of the document to attach. Must be a valid HTTP or HTTPS URL.
+ *
+ * @throws PreludeInvalidDataException 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")
+
+ /**
+ * Returns the raw JSON value of [filename].
+ *
+ * Unlike [filename], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("filename") @ExcludeMissing fun _filename(): JsonField = filename
+
+ /**
+ * 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 [Document].
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Document]. */
+ class Builder internal constructor() {
+
+ private var filename: JsonField? = null
+ private var url: JsonField? = null
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(document: Document) = apply {
+ filename = document.filename
+ url = document.url
+ additionalProperties = document.additionalProperties.toMutableMap()
+ }
+
+ /** The filename to display for the document. */
+ fun filename(filename: String) = filename(JsonField.of(filename))
+
+ /**
+ * Sets [Builder.filename] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.filename] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun filename(filename: JsonField) = apply { this.filename = filename }
+
+ /** The URL of the document to attach. Must be a valid HTTP or HTTPS URL. */
+ 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 [Document].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .filename()
+ * .url()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Document =
+ Document(
+ checkRequired("filename", filename),
+ checkRequired("url", url),
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Document = apply {
+ if (validated) {
+ return@apply
+ }
+
+ filename()
+ url()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: PreludeInvalidDataException) {
+ 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 (filename.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 Document &&
+ filename == other.filename &&
+ url == other.url &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy { Objects.hash(filename, url, additionalProperties) }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "Document{filename=$filename, url=$url, additionalProperties=$additionalProperties}"
}
/**
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateParams.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateParams.kt
index b2b88ae7..b83074d3 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateParams.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateParams.kt
@@ -1188,7 +1188,6 @@ private constructor(
private val callbackUrl: JsonField,
private val codeSize: JsonField,
private val customCode: JsonField,
- private val integration: JsonField,
private val locale: JsonField,
private val method: JsonField,
private val preferredChannel: JsonField,
@@ -1210,9 +1209,6 @@ private constructor(
@JsonProperty("custom_code")
@ExcludeMissing
customCode: JsonField = JsonMissing.of(),
- @JsonProperty("integration")
- @ExcludeMissing
- integration: JsonField = JsonMissing.of(),
@JsonProperty("locale") @ExcludeMissing locale: JsonField = JsonMissing.of(),
@JsonProperty("method") @ExcludeMissing method: JsonField = JsonMissing.of(),
@JsonProperty("preferred_channel")
@@ -1232,7 +1228,6 @@ private constructor(
callbackUrl,
codeSize,
customCode,
- integration,
locale,
method,
preferredChannel,
@@ -1243,8 +1238,8 @@ private constructor(
)
/**
- * This allows you to automatically retrieve and fill the OTP code on mobile apps. Currently
- * only Android devices are supported.
+ * This allows automatic OTP retrieval on mobile apps and web browsers. Supported platforms
+ * are Android (SMS Retriever API) and Web (WebOTP API).
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -1280,14 +1275,6 @@ private constructor(
*/
fun customCode(): Optional = customCode.getOptional("custom_code")
- /**
- * The integration that triggered the verification.
- *
- * @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
- */
- fun integration(): Optional = integration.getOptional("integration")
-
/**
* A BCP-47 formatted locale string with the language the text message will be sent to. If
* there's no locale set, the language will be determined by the country code of the phone
@@ -1377,15 +1364,6 @@ private constructor(
@ExcludeMissing
fun _customCode(): JsonField = customCode
- /**
- * Returns the raw JSON value of [integration].
- *
- * Unlike [integration], this method doesn't throw if the JSON field has an unexpected type.
- */
- @JsonProperty("integration")
- @ExcludeMissing
- fun _integration(): JsonField = integration
-
/**
* Returns the raw JSON value of [locale].
*
@@ -1460,7 +1438,6 @@ private constructor(
private var callbackUrl: JsonField = JsonMissing.of()
private var codeSize: JsonField = JsonMissing.of()
private var customCode: JsonField = JsonMissing.of()
- private var integration: JsonField = JsonMissing.of()
private var locale: JsonField = JsonMissing.of()
private var method: JsonField = JsonMissing.of()
private var preferredChannel: JsonField = JsonMissing.of()
@@ -1475,7 +1452,6 @@ private constructor(
callbackUrl = options.callbackUrl
codeSize = options.codeSize
customCode = options.customCode
- integration = options.integration
locale = options.locale
method = options.method
preferredChannel = options.preferredChannel
@@ -1486,8 +1462,8 @@ private constructor(
}
/**
- * This allows you to automatically retrieve and fill the OTP code on mobile apps.
- * Currently only Android devices are supported.
+ * This allows automatic OTP retrieval on mobile apps and web browsers. Supported
+ * platforms are Android (SMS Retriever API) and Web (WebOTP API).
*/
fun appRealm(appRealm: AppRealm) = appRealm(JsonField.of(appRealm))
@@ -1549,20 +1525,6 @@ private constructor(
*/
fun customCode(customCode: JsonField) = apply { this.customCode = customCode }
- /** The integration that triggered the verification. */
- fun integration(integration: Integration) = integration(JsonField.of(integration))
-
- /**
- * Sets [Builder.integration] to an arbitrary JSON value.
- *
- * You should usually call [Builder.integration] with a well-typed [Integration] value
- * instead. This method is primarily for setting the field to an undocumented or not yet
- * supported value.
- */
- fun integration(integration: JsonField) = apply {
- this.integration = integration
- }
-
/**
* A BCP-47 formatted locale string with the language the text message will be sent to.
* If there's no locale set, the language will be determined by the country code of the
@@ -1684,7 +1646,6 @@ private constructor(
callbackUrl,
codeSize,
customCode,
- integration,
locale,
method,
preferredChannel,
@@ -1706,7 +1667,6 @@ private constructor(
callbackUrl()
codeSize()
customCode()
- integration().ifPresent { it.validate() }
locale()
method().ifPresent { it.validate() }
preferredChannel().ifPresent { it.validate() }
@@ -1736,7 +1696,6 @@ private constructor(
(if (callbackUrl.asKnown().isPresent) 1 else 0) +
(if (codeSize.asKnown().isPresent) 1 else 0) +
(if (customCode.asKnown().isPresent) 1 else 0) +
- (integration.asKnown().getOrNull()?.validity() ?: 0) +
(if (locale.asKnown().isPresent) 1 else 0) +
(method.asKnown().getOrNull()?.validity() ?: 0) +
(preferredChannel.asKnown().getOrNull()?.validity() ?: 0) +
@@ -1745,8 +1704,8 @@ private constructor(
(variables.asKnown().getOrNull()?.validity() ?: 0)
/**
- * This allows you to automatically retrieve and fill the OTP code on mobile apps. Currently
- * only Android devices are supported.
+ * This allows automatic OTP retrieval on mobile apps and web browsers. Supported platforms
+ * are Android (SMS Retriever API) and Web (WebOTP API).
*/
class AppRealm
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
@@ -1765,7 +1724,8 @@ private constructor(
) : this(platform, value, mutableMapOf())
/**
- * The platform the SMS will be sent to. We are currently only supporting "android".
+ * The platform for automatic OTP retrieval. Use "android" for the SMS Retriever API or
+ * "web" for the WebOTP API.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected
@@ -1774,9 +1734,11 @@ private constructor(
fun platform(): Platform = platform.getRequired("platform")
/**
- * The Android SMS Retriever API hash code that identifies your app. For more
- * information, see
- * [Google documentation](https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string).
+ * The value depends on the platform:
+ * - For Android: The SMS Retriever API hash code (11 characters). See
+ * [Google documentation](https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string).
+ * - For Web: The origin domain (e.g., "example.com" or "www.example.com"). See
+ * [WebOTP API documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API).
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected
@@ -1842,7 +1804,8 @@ private constructor(
}
/**
- * The platform the SMS will be sent to. We are currently only supporting "android".
+ * The platform for automatic OTP retrieval. Use "android" for the SMS Retriever API
+ * or "web" for the WebOTP API.
*/
fun platform(platform: Platform) = platform(JsonField.of(platform))
@@ -1856,9 +1819,11 @@ private constructor(
fun platform(platform: JsonField) = apply { this.platform = platform }
/**
- * The Android SMS Retriever API hash code that identifies your app. For more
- * information, see
- * [Google documentation](https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string).
+ * The value depends on the platform:
+ * - For Android: The SMS Retriever API hash code (11 characters). See
+ * [Google documentation](https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string).
+ * - For Web: The origin domain (e.g., "example.com" or "www.example.com"). See
+ * [WebOTP API documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API).
*/
fun value(value: String) = value(JsonField.of(value))
@@ -1945,7 +1910,10 @@ private constructor(
(platform.asKnown().getOrNull()?.validity() ?: 0) +
(if (value.asKnown().isPresent) 1 else 0)
- /** The platform the SMS will be sent to. We are currently only supporting "android". */
+ /**
+ * The platform for automatic OTP retrieval. Use "android" for the SMS Retriever API or
+ * "web" for the WebOTP API.
+ */
class Platform @JsonCreator private constructor(private val value: JsonField) :
Enum {
@@ -1963,12 +1931,15 @@ private constructor(
@JvmField val ANDROID = of("android")
+ @JvmField val WEB = of("web")
+
@JvmStatic fun of(value: String) = Platform(JsonField.of(value))
}
/** An enum containing [Platform]'s known values. */
enum class Known {
- ANDROID
+ ANDROID,
+ WEB,
}
/**
@@ -1982,6 +1953,7 @@ private constructor(
*/
enum class Value {
ANDROID,
+ WEB,
/**
* An enum member indicating that [Platform] was instantiated with an unknown
* value.
@@ -1999,6 +1971,7 @@ private constructor(
fun value(): Value =
when (this) {
ANDROID -> Value.ANDROID
+ WEB -> Value.WEB
else -> Value._UNKNOWN
}
@@ -2014,6 +1987,7 @@ private constructor(
fun known(): Known =
when (this) {
ANDROID -> Known.ANDROID
+ WEB -> Known.WEB
else -> throw PreludeInvalidDataException("Unknown Platform: $value")
}
@@ -2092,138 +2066,6 @@ private constructor(
"AppRealm{platform=$platform, value=$value, additionalProperties=$additionalProperties}"
}
- /** The integration that triggered the verification. */
- class Integration @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 AUTH0 = of("auth0")
-
- @JvmField val SUPABASE = of("supabase")
-
- @JvmStatic fun of(value: String) = Integration(JsonField.of(value))
- }
-
- /** An enum containing [Integration]'s known values. */
- enum class Known {
- AUTH0,
- SUPABASE,
- }
-
- /**
- * An enum containing [Integration]'s known values, as well as an [_UNKNOWN] member.
- *
- * An instance of [Integration] 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 {
- AUTH0,
- SUPABASE,
- /**
- * An enum member indicating that [Integration] 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) {
- AUTH0 -> Value.AUTH0
- SUPABASE -> Value.SUPABASE
- 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 PreludeInvalidDataException if this class instance's value is a not a known
- * member.
- */
- fun known(): Known =
- when (this) {
- AUTH0 -> Known.AUTH0
- SUPABASE -> Known.SUPABASE
- else -> throw PreludeInvalidDataException("Unknown Integration: $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 PreludeInvalidDataException if this class instance's value does not have the
- * expected primitive type.
- */
- fun asString(): String =
- _value().asString().orElseThrow {
- PreludeInvalidDataException("Value is not a String")
- }
-
- private var validated: Boolean = false
-
- fun validate(): Integration = apply {
- if (validated) {
- return@apply
- }
-
- known()
- validated = true
- }
-
- fun isValid(): Boolean =
- try {
- validate()
- true
- } catch (e: PreludeInvalidDataException) {
- 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 Integration && value == other.value
- }
-
- override fun hashCode() = value.hashCode()
-
- override fun toString() = value.toString()
- }
-
/**
* The method used for verifying this phone number. The 'voice' option provides an
* accessible alternative for visually impaired users by delivering the verification code
@@ -2638,7 +2480,6 @@ private constructor(
callbackUrl == other.callbackUrl &&
codeSize == other.codeSize &&
customCode == other.customCode &&
- integration == other.integration &&
locale == other.locale &&
method == other.method &&
preferredChannel == other.preferredChannel &&
@@ -2654,7 +2495,6 @@ private constructor(
callbackUrl,
codeSize,
customCode,
- integration,
locale,
method,
preferredChannel,
@@ -2668,7 +2508,7 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Options{appRealm=$appRealm, callbackUrl=$callbackUrl, codeSize=$codeSize, customCode=$customCode, integration=$integration, locale=$locale, method=$method, preferredChannel=$preferredChannel, senderId=$senderId, templateId=$templateId, variables=$variables, additionalProperties=$additionalProperties}"
+ "Options{appRealm=$appRealm, callbackUrl=$callbackUrl, codeSize=$codeSize, customCode=$customCode, locale=$locale, method=$method, preferredChannel=$preferredChannel, senderId=$senderId, templateId=$templateId, variables=$variables, additionalProperties=$additionalProperties}"
}
/**
@@ -2739,8 +2579,9 @@ private constructor(
fun appVersion(): Optional = appVersion.getOptional("app_version")
/**
- * The unique identifier for the user's device. For Android, this corresponds to the
- * `ANDROID_ID` and for iOS, this corresponds to the `identifierForVendor`.
+ * A unique ID for the user's device. You should ensure that each user device has a unique
+ * `device_id` value. Ideally, for Android, this corresponds to the `ANDROID_ID` and for
+ * iOS, this corresponds to the `identifierForVendor`.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -2765,7 +2606,10 @@ private constructor(
devicePlatform.getOptional("device_platform")
/**
- * The IP address of the user's device.
+ * The public IP v4 or v6 address of the end-user's device. You should collect this from
+ * your backend. If your backend is behind a proxy, use the `X-Forwarded-For`, `Forwarded`,
+ * `True-Client-IP`, `CF-Connecting-IP` or an equivalent header to get the actual public IP
+ * of the end-user's device.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -2773,8 +2617,8 @@ private constructor(
fun ip(): Optional = ip.getOptional("ip")
/**
- * This signal should provide a higher level of trust, indicating that the user is genuine.
- * Contact us to discuss your use case. For more details, refer to
+ * This signal should indicate a higher level of trust, explicitly stating that the user is
+ * genuine. Contact us to discuss your use case. For more details, refer to
* [Signals](/verify/v2/documentation/prevent-fraud#signals).
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
@@ -2783,9 +2627,9 @@ private constructor(
fun isTrustedUser(): Optional = isTrustedUser.getOptional("is_trusted_user")
/**
- * The JA4 fingerprint observed for the connection. Prelude will infer it automatically when
- * requests go through our client SDK (which uses Prelude's edge), but you can also provide
- * it explicitly if you terminate TLS yourself.
+ * The JA4 fingerprint observed for the end-user's connection. Prelude will infer it
+ * automatically when you use our Frontend SDKs (which use Prelude's edge network), but you
+ * can also forward the value if you terminate TLS yourself.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -2945,8 +2789,9 @@ private constructor(
fun appVersion(appVersion: JsonField) = apply { this.appVersion = appVersion }
/**
- * The unique identifier for the user's device. For Android, this corresponds to the
- * `ANDROID_ID` and for iOS, this corresponds to the `identifierForVendor`.
+ * A unique ID for the user's device. You should ensure that each user device has a
+ * unique `device_id` value. Ideally, for Android, this corresponds to the `ANDROID_ID`
+ * and for iOS, this corresponds to the `identifierForVendor`.
*/
fun deviceId(deviceId: String) = deviceId(JsonField.of(deviceId))
@@ -2988,7 +2833,12 @@ private constructor(
this.devicePlatform = devicePlatform
}
- /** The IP address of the user's device. */
+ /**
+ * The public IP v4 or v6 address of the end-user's device. You should collect this from
+ * your backend. If your backend is behind a proxy, use the `X-Forwarded-For`,
+ * `Forwarded`, `True-Client-IP`, `CF-Connecting-IP` or an equivalent header to get the
+ * actual public IP of the end-user's device.
+ */
fun ip(ip: String) = ip(JsonField.of(ip))
/**
@@ -3001,8 +2851,8 @@ private constructor(
fun ip(ip: JsonField) = apply { this.ip = ip }
/**
- * This signal should provide a higher level of trust, indicating that the user is
- * genuine. Contact us to discuss your use case. For more details, refer to
+ * This signal should indicate a higher level of trust, explicitly stating that the user
+ * is genuine. Contact us to discuss your use case. For more details, refer to
* [Signals](/verify/v2/documentation/prevent-fraud#signals).
*/
fun isTrustedUser(isTrustedUser: Boolean) = isTrustedUser(JsonField.of(isTrustedUser))
@@ -3019,9 +2869,9 @@ private constructor(
}
/**
- * The JA4 fingerprint observed for the connection. Prelude will infer it automatically
- * when requests go through our client SDK (which uses Prelude's edge), but you can also
- * provide it explicitly if you terminate TLS yourself.
+ * The JA4 fingerprint observed for the end-user's connection. Prelude will infer it
+ * automatically when you use our Frontend SDKs (which use Prelude's edge network), but
+ * you can also forward the value if you terminate TLS yourself.
*/
fun ja4Fingerprint(ja4Fingerprint: String) =
ja4Fingerprint(JsonField.of(ja4Fingerprint))
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateResponse.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateResponse.kt
index fce0bbd1..b1b13f68 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateResponse.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/VerificationCreateResponse.kt
@@ -66,6 +66,11 @@ private constructor(
/**
* The status of the verification.
+ * * `success` - A new verification window was created.
+ * * `retry` - A new attempt was created for an existing verification window.
+ * * `challenged` - The verification is suspicious and is restricted to non-SMS and non-voice
+ * channels only. This mode must be enabled for your customer account by Prelude support.
+ * * `blocked` - The verification was blocked.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
@@ -250,7 +255,15 @@ private constructor(
*/
fun method(method: JsonField) = apply { this.method = method }
- /** The status of the verification. */
+ /**
+ * The status of the verification.
+ * * `success` - A new verification window was created.
+ * * `retry` - A new attempt was created for an existing verification window.
+ * * `challenged` - The verification is suspicious and is restricted to non-SMS and
+ * non-voice channels only. This mode must be enabled for your customer account by Prelude
+ * support.
+ * * `blocked` - The verification was blocked.
+ */
fun status(status: Status) = status(JsonField.of(status))
/**
@@ -570,7 +583,14 @@ private constructor(
override fun toString() = value.toString()
}
- /** The status of the verification. */
+ /**
+ * The status of the verification.
+ * * `success` - A new verification window was created.
+ * * `retry` - A new attempt was created for an existing verification window.
+ * * `challenged` - The verification is suspicious and is restricted to non-SMS and non-voice
+ * channels only. This mode must be enabled for your customer account by Prelude support.
+ * * `blocked` - The verification was blocked.
+ */
class Status @JsonCreator private constructor(private val value: JsonField) : Enum {
/**
@@ -589,6 +609,8 @@ private constructor(
@JvmField val RETRY = of("retry")
+ @JvmField val CHALLENGED = of("challenged")
+
@JvmField val BLOCKED = of("blocked")
@JvmStatic fun of(value: String) = Status(JsonField.of(value))
@@ -598,6 +620,7 @@ private constructor(
enum class Known {
SUCCESS,
RETRY,
+ CHALLENGED,
BLOCKED,
}
@@ -613,6 +636,7 @@ private constructor(
enum class Value {
SUCCESS,
RETRY,
+ CHALLENGED,
BLOCKED,
/** An enum member indicating that [Status] was instantiated with an unknown value. */
_UNKNOWN,
@@ -629,6 +653,7 @@ private constructor(
when (this) {
SUCCESS -> Value.SUCCESS
RETRY -> Value.RETRY
+ CHALLENGED -> Value.CHALLENGED
BLOCKED -> Value.BLOCKED
else -> Value._UNKNOWN
}
@@ -646,6 +671,7 @@ private constructor(
when (this) {
SUCCESS -> Known.SUCCESS
RETRY -> Known.RETRY
+ CHALLENGED -> Known.CHALLENGED
BLOCKED -> Known.BLOCKED
else -> throw PreludeInvalidDataException("Unknown Status: $value")
}
diff --git a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/WatchPredictParams.kt b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/WatchPredictParams.kt
index 09f75807..ae6a1e37 100644
--- a/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/WatchPredictParams.kt
+++ b/prelude-java-core/src/main/kotlin/so/prelude/sdk/models/WatchPredictParams.kt
@@ -1158,8 +1158,9 @@ private constructor(
fun appVersion(): Optional = appVersion.getOptional("app_version")
/**
- * The unique identifier for the user's device. For Android, this corresponds to the
- * `ANDROID_ID` and for iOS, this corresponds to the `identifierForVendor`.
+ * A unique ID for the user's device. You should ensure that each user device has a unique
+ * `device_id` value. Ideally, for Android, this corresponds to the `ANDROID_ID` and for
+ * iOS, this corresponds to the `identifierForVendor`.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -1184,7 +1185,10 @@ private constructor(
devicePlatform.getOptional("device_platform")
/**
- * The IP address of the user's device.
+ * The public IP v4 or v6 address of the end-user's device. You should collect this from
+ * your backend. If your backend is behind a proxy, use the `X-Forwarded-For`, `Forwarded`,
+ * `True-Client-IP`, `CF-Connecting-IP` or an equivalent header to get the actual public IP
+ * of the end-user's device.
*
* @throws PreludeInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -1192,8 +1196,8 @@ private constructor(
fun ip(): Optional