diff --git a/.github/workflows/kotlin-lint.yml b/.github/workflows/kotlin-lint.yml index 053946731..911c566be 100644 --- a/.github/workflows/kotlin-lint.yml +++ b/.github/workflows/kotlin-lint.yml @@ -21,12 +21,7 @@ jobs: distribution: 'temurin' java-version: '11' - - name: Regen openapi libs - run: | - yarn - ./regen_openapi.sh - - name: Build run: | cd kotlin - ./gradlew build -x test + ./gradlew build diff --git a/ChangeLog.md b/ChangeLog.md index f2defb5e4..261312fb3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,16 @@ # Changelog +## Unreleased + +* Libs/Kotlin **(VERY IMPORTANT)**: The parameter order `appId` and `msgId` were swapped on `Message.get` and `Message.expungeContent` +* Libs/Kotlin **(Breaking)**: All uses of `ListOptions`/`PostOptions` are removed, and renamed to `{Resource}{Operation}Options`. For example in `Endpoint.List` you would now use `EndpointListOptions` +* Libs/Kotlin **(Breaking)**: In the 4 `*Patch` patch models, nullable fields are of type `MaybeUnset` instead of `T`. call `MaybeUnset.Present(val)` to initialize this value +* Libs/Kotlin **(Breaking)**: `SvixOptions` no longer has `initialRetryDelayMillis` or `numRetries` instead use `retrySchedule` +* Libs/Kotlin **(Breaking)**: All `{Resource}{Operation}Options` and model classes (`ApplicationIn`/`MessageOut`) are now data classes +* Libs/Kotlin **(Breaking)**: Deprecated functions `MessageAttempt.list` and `MessageAttempt.listAttemptsForEndpoint` are removed +* Libs/Kotlin **(Breaking)**: All uses of `java.time.OffsetDateTime` replaced with `kotlinx.datetime.Instant` +* Libs/Kotlin **(Breaking)**: All uses of `java.net.URL` in request/response models are replaced with `String` + ## Version 1.57.0 This version contains a big overhaul of the client libraries, with improved typing. @@ -9,7 +20,6 @@ This version contains a big overhaul of the client libraries, with improved typi * Libs/Go **(Breaking)**: All custom model types are now imported from `github.com/svix/svix-webhooks/go/models` instead of `github.com/svix/svix-webhooks/go` * Libs/Go **(Breaking)**: All `-WithOptions` methods are now removed. Their regular counterparts now take a pointer to an Options type which can be nil when not needed. For example in `Endpoint.RecoverWithOptions` is now `Endpoint.Recover` -* Libs/C# and Libs/Go **(Breaking)**: All uses of `ListOptions`/`PostOptions` are removed, and renamed to `{Resource}{Operation}Options`. For example in `Endpoint.List` you would now use `EndpointListOptions` * Libs/C# **(Breaking)**: All `IdempotencyKey` method parameters are removed, and are now part of `{Resource}{Operation}Options`. For example in `Message.Create`; to the use `IdempotencyKey`, simply pass it in the `MessageCreateOptions` * Libs/C# **(Breaking)**: The `Throw` parameter is removed from `SvixOptions` * Libs/C# **(Breaking)**: All redundant interfaces along with the `Svix.Abstractions` namespace are removed @@ -22,8 +32,6 @@ This version contains a big overhaul of the client libraries, with improved typi * Libs/Python **(Breaking)**: `MessageAttemptListOptions` is removed in favor of call specific `{Resource}{Operation}Options` * Libs/Python **(Breaking)**: For `Statistics` in the `aggregate_event_types` method the `task_id` parameter is removed, Please note that previously this parameter was ignored and had no affect (Both sync and async) -* Libs/Kotlin **(Breaking)**: Mark `api` field of all API resource classes as `private` (previously - only some were private, accidentally) * Libs/Kotlin **(Breaking)**: Update `recover` to return `RecoverOut` (instead of nothing) * Libs/Kotlin **(Breaking)**: Update `replayMissing` to return `ReplayOut` (instead of nothing) * Libs/Kotlin **(Breaking)**: Update `sendExample` to return `MessageOut` (instead of nothing) diff --git a/kotlin/build.gradle b/kotlin/build.gradle index 9ae49c112..5f0d1655b 100644 --- a/kotlin/build.gradle +++ b/kotlin/build.gradle @@ -1,13 +1,5 @@ -group GROUP -version VERSION_NAME - -wrapper { - gradleVersion = '8.7' - distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" -} - buildscript { - ext.kotlin_version = '1.9.23' + ext.kotlin_version = '2.1.10' ext.spotless_version = "6.25.0" repositories { @@ -21,6 +13,21 @@ buildscript { } } +plugins { + id 'org.jetbrains.kotlin.jvm' version '2.1.10' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.10' +} + +group GROUP +version VERSION_NAME + + +wrapper { + gradleVersion = '8.7' + distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" +} + + apply plugin: 'kotlin' apply plugin: 'com.diffplug.spotless' @@ -30,23 +37,29 @@ repositories { sourceSets { main.kotlin.srcDirs += 'lib/src/main/kotlin' - main.kotlin.srcDirs += 'lib/generated/openapi/src/main/kotlin' + // main.kotlin.srcDirs += 'lib/generated/openapi/src/main/kotlin' sourceSets.test.kotlin.srcDirs = ["lib/src/test/main/kotlin"] test.kotlin.srcDirs = ['lib/src/test'] } test { useJUnitPlatform() + testLogging { + events "failed" + exceptionFormat "full" + } } dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.6.2' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" - implementation "com.squareup.moshi:moshi-kotlin:1.12.0" - implementation "com.squareup.okhttp3:okhttp:4.9.1" + implementation "com.squareup.okhttp3:okhttp:4.12.0" testImplementation 'junit:junit:4.13.2' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5:1.5.21' + testImplementation "org.wiremock:wiremock:3.12.0" } jar { diff --git a/kotlin/deploy.gradle b/kotlin/deploy.gradle index 8a0d1a277..0744a0695 100644 --- a/kotlin/deploy.gradle +++ b/kotlin/deploy.gradle @@ -48,12 +48,10 @@ nexusPublishing { } task sourcesJar(type: Jar) { - classifier = "sources" from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = "javadoc" from javadoc.destinationDir } diff --git a/kotlin/gradle/wrapper/gradle-wrapper.jar b/kotlin/gradle/wrapper/gradle-wrapper.jar index 7454180f2..41d9927a4 100644 Binary files a/kotlin/gradle/wrapper/gradle-wrapper.jar and b/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/gradle/wrapper/gradle-wrapper.properties b/kotlin/gradle/wrapper/gradle-wrapper.properties index 856da69bc..d951fac2b 100644 --- a/kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/kotlin/lib/generated/openapi/.openapi-generator-ignore b/kotlin/lib/generated/openapi/.openapi-generator-ignore deleted file mode 100644 index 7484ee590..000000000 --- a/kotlin/lib/generated/openapi/.openapi-generator-ignore +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAPI Generator Ignore -# Generated by openapi-generator https://github.com/openapitools/openapi-generator - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: -#ApiClient.cs - -# You can match any string of characters against a directory, file or extension with a single asterisk (*): -#foo/*/qux -# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux - -# You can recursively match patterns against a directory, file or extension with a double asterisk (**): -#foo/**/qux -# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux - -# You can also negate patterns with an exclamation (!). -# For example, you can ignore all files in a docs folder with the file extension .md: -#docs/*.md -# Then explicitly reverse the ignore rule for a single file: -#!docs/README.md diff --git a/kotlin/lib/src/main/kotlin/Application.kt b/kotlin/lib/src/main/kotlin/Application.kt index 2d938a198..dc768cb0a 100644 --- a/kotlin/lib/src/main/kotlin/Application.kt +++ b/kotlin/lib/src/main/kotlin/Application.kt @@ -1,106 +1,106 @@ -// this file is @generated (with minor manual changes) +// this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.ApplicationApi import com.svix.kotlin.models.ApplicationIn import com.svix.kotlin.models.ApplicationOut import com.svix.kotlin.models.ApplicationPatch import com.svix.kotlin.models.ListResponseApplicationOut import com.svix.kotlin.models.Ordering +import okhttp3.Headers -class ApplicationListOptions { - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null - +data class ApplicationListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } -} + val order: Ordering? = null, +) -class Application internal constructor(token: String, options: SvixOptions) { - private val api = ApplicationApi(options.serverUrl) +data class ApplicationCreateOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +class Application(private val client: SvixHttpClient) { /** List of all the organization's applications. */ suspend fun list( options: ApplicationListOptions = ApplicationListOptions() ): ListResponseApplicationOut { - try { - return api.v1ApplicationList(options.limit, options.iterator, options.order) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** Create a new application. */ suspend fun create( applicationIn: ApplicationIn, - options: PostOptions = PostOptions(), + options: ApplicationCreateOptions = ApplicationCreateOptions(), ): ApplicationOut { - try { - return api.v1ApplicationCreate(applicationIn, null, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = applicationIn, + ) } + /** Get or create an application. */ suspend fun getOrCreate( applicationIn: ApplicationIn, - options: PostOptions = PostOptions(), + options: ApplicationCreateOptions = ApplicationCreateOptions(), ): ApplicationOut { - try { - return api.v1ApplicationCreate(applicationIn, true, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app") + .addQueryParameter("get_if_exists", "true") + var headers = Headers.Builder() + options.idempotencyKey?.let { headers = headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = applicationIn, + ) } /** Get an application. */ suspend fun get(appId: String): ApplicationOut { - try { - return api.v1ApplicationGet(appId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId") + return client.executeRequest("GET", url.build()) } /** Update an application. */ suspend fun update(appId: String, applicationIn: ApplicationIn): ApplicationOut { - try { - return api.v1ApplicationUpdate(appId, applicationIn) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId") + + return client.executeRequest( + "PUT", + url.build(), + reqBody = applicationIn, + ) } /** Delete an application. */ suspend fun delete(appId: String) { - try { - api.v1ApplicationDelete(appId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId") + client.executeRequest("DELETE", url.build()) } /** Partially update an application. */ suspend fun patch(appId: String, applicationPatch: ApplicationPatch): ApplicationOut { - try { - return api.v1ApplicationPatch(appId, applicationPatch) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId") + + return client.executeRequest( + "PATCH", + url.build(), + reqBody = applicationPatch, + ) } } diff --git a/kotlin/lib/src/main/kotlin/Authentication.kt b/kotlin/lib/src/main/kotlin/Authentication.kt index 714a418a5..2d4e0c4df 100644 --- a/kotlin/lib/src/main/kotlin/Authentication.kt +++ b/kotlin/lib/src/main/kotlin/Authentication.kt @@ -1,22 +1,21 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.AuthenticationApi import com.svix.kotlin.models.AppPortalAccessIn import com.svix.kotlin.models.AppPortalAccessOut import com.svix.kotlin.models.ApplicationTokenExpireIn import com.svix.kotlin.models.DashboardAccessOut +import okhttp3.Headers -class Authentication internal constructor(token: String, options: SvixOptions) { - private val api = AuthenticationApi(options.serverUrl) +data class AuthenticationAppPortalAccessOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class AuthenticationExpireAllOptions(val idempotencyKey: String? = null) + +data class AuthenticationDashboardAccessOptions(val idempotencyKey: String? = null) + +data class AuthenticationLogoutOptions(val idempotencyKey: String? = null) + +class Authentication(private val client: SvixHttpClient) { /** * Use this function to get magic links (and authentication codes) for connecting your users to @@ -25,30 +24,36 @@ class Authentication internal constructor(token: String, options: SvixOptions) { suspend fun appPortalAccess( appId: String, appPortalAccessIn: AppPortalAccessIn, - options: PostOptions = PostOptions(), + options: AuthenticationAppPortalAccessOptions = AuthenticationAppPortalAccessOptions(), ): AppPortalAccessOut { - try { - return api.v1AuthenticationAppPortalAccess( - appId, - appPortalAccessIn, - options.idempotencyKey, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/auth/app-portal-access/$appId") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = appPortalAccessIn, + ) } /** Expire all of the tokens associated with a specific application. */ suspend fun expireAll( appId: String, applicationTokenExpireIn: ApplicationTokenExpireIn, - options: PostOptions = PostOptions(), + options: AuthenticationExpireAllOptions = AuthenticationExpireAllOptions(), ) { - try { - api.v1AuthenticationExpireAll(appId, applicationTokenExpireIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/auth/app/$appId/expire-all") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = applicationTokenExpireIn, + ) } /** @@ -59,16 +64,19 @@ class Authentication internal constructor(token: String, options: SvixOptions) { * * @deprecated */ - @Deprecated(message = "Use appPortalAccess instead.") + @Deprecated("") suspend fun dashboardAccess( appId: String, - options: PostOptions = PostOptions(), + options: AuthenticationDashboardAccessOptions = AuthenticationDashboardAccessOptions(), ): DashboardAccessOut { - try { - return api.v1AuthenticationDashboardAccess(appId, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/auth/dashboard-access/$appId") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + ) } /** @@ -76,11 +84,10 @@ class Authentication internal constructor(token: String, options: SvixOptions) { * * Trying to log out other tokens will fail. */ - suspend fun logout(options: PostOptions = PostOptions()) { - try { - api.v1AuthenticationLogout(options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + suspend fun logout(options: AuthenticationLogoutOptions = AuthenticationLogoutOptions()) { + val url = client.newUrlBuilder().encodedPath("/api/v1/auth/logout") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + client.executeRequest("POST", url.build(), headers = headers.build()) } } diff --git a/kotlin/lib/src/main/kotlin/BackgroundTask.kt b/kotlin/lib/src/main/kotlin/BackgroundTask.kt index f6e37180e..3f13bad70 100644 --- a/kotlin/lib/src/main/kotlin/BackgroundTask.kt +++ b/kotlin/lib/src/main/kotlin/BackgroundTask.kt @@ -1,70 +1,43 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.BackgroundTasksApi import com.svix.kotlin.models.BackgroundTaskOut import com.svix.kotlin.models.BackgroundTaskStatus import com.svix.kotlin.models.BackgroundTaskType import com.svix.kotlin.models.ListResponseBackgroundTaskOut import com.svix.kotlin.models.Ordering -class BackgroundTaskListOptions { - var status: BackgroundTaskStatus? = null - var task: BackgroundTaskType? = null - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null - +data class BackgroundTaskListOptions( /** Filter the response based on the status. */ - fun status(status: BackgroundTaskStatus) = apply { this.status = status } - + val status: BackgroundTaskStatus? = null, /** Filter the response based on the type. */ - fun task(task: BackgroundTaskType) = apply { this.task = task } - + val task: BackgroundTaskType? = null, /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } -} + val order: Ordering? = null, +) -class BackgroundTask internal constructor(token: String, options: SvixOptions) { - private val api = BackgroundTasksApi(options.serverUrl) - - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +class BackgroundTask(private val client: SvixHttpClient) { /** List background tasks executed in the past 90 days. */ suspend fun list( options: BackgroundTaskListOptions = BackgroundTaskListOptions() ): ListResponseBackgroundTaskOut { - try { - return api.v1BackgroundTaskList( - options.status, - options.task, - options.limit, - options.iterator, - options.order, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/background-task") + options.status?.let { url.addQueryParameter("status", serializeQueryParam(it)) } + options.task?.let { url.addQueryParameter("task", serializeQueryParam(it)) } + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** Get a background task by ID. */ suspend fun get(taskId: String): BackgroundTaskOut { - try { - return api.v1BackgroundTaskGet(taskId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/background-task/$taskId") + return client.executeRequest("GET", url.build()) } } diff --git a/kotlin/lib/src/main/kotlin/Endpoint.kt b/kotlin/lib/src/main/kotlin/Endpoint.kt index 39bca9c36..64dfb4024 100644 --- a/kotlin/lib/src/main/kotlin/Endpoint.kt +++ b/kotlin/lib/src/main/kotlin/Endpoint.kt @@ -1,8 +1,6 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.EndpointApi import com.svix.kotlin.models.EndpointHeadersIn import com.svix.kotlin.models.EndpointHeadersOut import com.svix.kotlin.models.EndpointHeadersPatchIn @@ -23,54 +21,47 @@ import com.svix.kotlin.models.RecoverIn import com.svix.kotlin.models.RecoverOut import com.svix.kotlin.models.ReplayIn import com.svix.kotlin.models.ReplayOut -import java.time.OffsetDateTime - -class EndpointListOptions { - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null +import kotlinx.datetime.Instant +import okhttp3.Headers +data class EndpointListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } -} + val order: Ordering? = null, +) -class EndpointGetStatsOptions { - var since: OffsetDateTime? = null - var until: OffsetDateTime? = null +data class EndpointCreateOptions(val idempotencyKey: String? = null) - /** Filter the range to data starting from this date. */ - fun since(since: OffsetDateTime) = apply { this.since = since } +data class EndpointRecoverOptions(val idempotencyKey: String? = null) - /** Filter the range to data ending by this date. */ - fun until(until: OffsetDateTime) = apply { this.until = until } -} +data class EndpointReplayMissingOptions(val idempotencyKey: String? = null) -class Endpoint internal constructor(token: String, options: SvixOptions) { - private val api = EndpointApi(options.serverUrl) +data class EndpointRotateSecretOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class EndpointSendExampleOptions(val idempotencyKey: String? = null) + +data class EndpointGetStatsOptions( + /** Filter the range to data starting from this date. */ + val since: Instant? = null, + /** Filter the range to data ending by this date. */ + val until: Instant? = null, +) + +class Endpoint(private val client: SvixHttpClient) { /** List the application's endpoints. */ suspend fun list( appId: String, options: EndpointListOptions = EndpointListOptions(), ): ListResponseEndpointOut { - try { - return api.v1EndpointList(appId, options.limit, options.iterator, options.order) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -81,22 +72,24 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { suspend fun create( appId: String, endpointIn: EndpointIn, - options: PostOptions = PostOptions(), + options: EndpointCreateOptions = EndpointCreateOptions(), ): EndpointOut { - try { - return api.v1EndpointCreate(appId, endpointIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = endpointIn, + ) } /** Get an endpoint. */ - suspend fun get(endpointId: String, appId: String): EndpointOut { - try { - return api.v1EndpointGet(appId, endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + suspend fun get(appId: String, endpointId: String): EndpointOut { + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId") + return client.executeRequest("GET", url.build()) } /** Update an endpoint. */ @@ -105,20 +98,19 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, endpointUpdate: EndpointUpdate, ): EndpointOut { - try { - return api.v1EndpointUpdate(appId, endpointId, endpointUpdate) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId") + + return client.executeRequest( + "PUT", + url.build(), + reqBody = endpointUpdate, + ) } /** Delete an endpoint. */ suspend fun delete(appId: String, endpointId: String) { - try { - api.v1EndpointDelete(appId, endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId") + client.executeRequest("DELETE", url.build()) } /** Partially update an endpoint. */ @@ -127,20 +119,20 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, endpointPatch: EndpointPatch, ): EndpointOut { - try { - return api.v1EndpointPatch(appId, endpointId, endpointPatch) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId") + + return client.executeRequest( + "PATCH", + url.build(), + reqBody = endpointPatch, + ) } /** Get the additional headers to be sent with the webhook. */ suspend fun getHeaders(appId: String, endpointId: String): EndpointHeadersOut { - try { - return api.v1EndpointGetHeaders(appId, endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/headers") + return client.executeRequest("GET", url.build()) } /** Set the additional headers to be sent with the webhook. */ @@ -149,11 +141,14 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, endpointHeadersIn: EndpointHeadersIn, ) { - try { - api.v1EndpointUpdateHeaders(appId, endpointId, endpointHeadersIn) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/headers") + + client.executeRequest( + "PUT", + url.build(), + reqBody = endpointHeadersIn, + ) } /** Partially set the additional headers to be sent with the webhook. */ @@ -162,11 +157,14 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, endpointHeadersPatchIn: EndpointHeadersPatchIn, ) { - try { - api.v1EndpointPatchHeaders(appId, endpointId, endpointHeadersPatchIn) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/headers") + + client.executeRequest( + "PATCH", + url.build(), + reqBody = endpointHeadersPatchIn, + ) } /** @@ -178,13 +176,19 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { appId: String, endpointId: String, recoverIn: RecoverIn, - options: PostOptions = PostOptions(), + options: EndpointRecoverOptions = EndpointRecoverOptions(), ): RecoverOut { - try { - return api.v1EndpointRecover(appId, endpointId, recoverIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/recover") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = recoverIn, + ) } /** @@ -197,13 +201,21 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { appId: String, endpointId: String, replayIn: ReplayIn, - options: PostOptions = PostOptions(), + options: EndpointReplayMissingOptions = EndpointReplayMissingOptions(), ): ReplayOut { - try { - return api.v1EndpointReplayMissing(appId, endpointId, replayIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/endpoint/$endpointId/replay-missing") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = replayIn, + ) } /** @@ -213,11 +225,9 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { * [the consuming webhooks docs](https://docs.svix.com/consuming-webhooks/). */ suspend fun getSecret(appId: String, endpointId: String): EndpointSecretOut { - try { - return api.v1EndpointGetSecret(appId, endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/secret") + return client.executeRequest("GET", url.build()) } /** @@ -229,18 +239,21 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { appId: String, endpointId: String, endpointSecretRotateIn: EndpointSecretRotateIn, - options: PostOptions = PostOptions(), + options: EndpointRotateSecretOptions = EndpointRotateSecretOptions(), ) { - try { - api.v1EndpointRotateSecret( - appId, - endpointId, - endpointSecretRotateIn, - options.idempotencyKey, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/endpoint/$endpointId/secret/rotate") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = endpointSecretRotateIn, + ) } /** Send an example message for an event. */ @@ -248,18 +261,21 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { appId: String, endpointId: String, eventExampleIn: EventExampleIn, - options: PostOptions = PostOptions(), + options: EndpointSendExampleOptions = EndpointSendExampleOptions(), ): MessageOut { - try { - return api.v1EndpointSendExample( - appId, - endpointId, - eventExampleIn, - options.idempotencyKey, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/endpoint/$endpointId/send-example") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = eventExampleIn, + ) } /** Get basic statistics for the endpoint. */ @@ -268,20 +284,20 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, options: EndpointGetStatsOptions = EndpointGetStatsOptions(), ): EndpointStats { - try { - return api.v1EndpointGetStats(appId, endpointId, options.since, options.until) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/stats") + options.since?.let { url.addQueryParameter("since", serializeQueryParam(it)) } + options.until?.let { url.addQueryParameter("until", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** Get the transformation code associated with this endpoint. */ suspend fun transformationGet(appId: String, endpointId: String): EndpointTransformationOut { - try { - return api.v1EndpointTransformationGet(appId, endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/endpoint/$endpointId/transformation") + return client.executeRequest("GET", url.build()) } /** Set or unset the transformation code associated with this endpoint. */ @@ -290,10 +306,15 @@ class Endpoint internal constructor(token: String, options: SvixOptions) { endpointId: String, endpointTransformationIn: EndpointTransformationIn, ) { - try { - api.v1EndpointTransformationPartialUpdate(appId, endpointId, endpointTransformationIn) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/endpoint/$endpointId/transformation") + + client.executeRequest( + "PATCH", + url.build(), + reqBody = endpointTransformationIn, + ) } } diff --git a/kotlin/lib/src/main/kotlin/EventType.kt b/kotlin/lib/src/main/kotlin/EventType.kt index 9ca3e764a..c19ef07ab 100644 --- a/kotlin/lib/src/main/kotlin/EventType.kt +++ b/kotlin/lib/src/main/kotlin/EventType.kt @@ -1,8 +1,6 @@ -// this file is @generated (with minor manual changes) +// this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.EventTypeApi import com.svix.kotlin.models.EventTypeImportOpenApiIn import com.svix.kotlin.models.EventTypeImportOpenApiOut import com.svix.kotlin.models.EventTypeIn @@ -11,68 +9,48 @@ import com.svix.kotlin.models.EventTypePatch import com.svix.kotlin.models.EventTypeUpdate import com.svix.kotlin.models.ListResponseEventTypeOut import com.svix.kotlin.models.Ordering +import okhttp3.Headers -class EventTypeListOptions { - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null - var includeArchived: Boolean? = null - var withContent: Boolean? = null - +data class EventTypeListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } - + val order: Ordering? = null, /** When `true` archived (deleted but not expunged) items are included in the response. */ - fun includeArchived(includeArchived: Boolean) = apply { this.includeArchived = includeArchived } - - @Deprecated("Use the new includeArchived() method") - fun includeAchived(includeArchived: Boolean) = apply { this.includeArchived = includeArchived } - + val includeArchived: Boolean? = null, /** When `true` the full item (including the schema) is included in the response. */ - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } -} + val withContent: Boolean? = null, +) + +data class EventTypeCreateOptions(val idempotencyKey: String? = null) -class EventTypeDeleteOptions { - var expunge: Boolean? = null +data class EventTypeImportOpenapiOptions(val idempotencyKey: String? = null) +data class EventTypeDeleteOptions( /** * By default event types are archived when "deleted". Passing this to `true` deletes them * entirely. */ - fun expunge(expunge: Boolean) = apply { this.expunge = expunge } -} + val expunge: Boolean? = null +) -class EventType internal constructor(token: String, options: SvixOptions) { - private val api = EventTypeApi(options.serverUrl) - - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +class EventType(private val client: SvixHttpClient) { /** Return the list of event types. */ suspend fun list( options: EventTypeListOptions = EventTypeListOptions() ): ListResponseEventTypeOut { - try { - return api.v1EventTypeList( - options.limit, - options.iterator, - options.order, - options.includeArchived, - options.withContent, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + options.includeArchived?.let { + url.addQueryParameter("include_archived", serializeQueryParam(it)) } + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -84,13 +62,18 @@ class EventType internal constructor(token: String, options: SvixOptions) { */ suspend fun create( eventTypeIn: EventTypeIn, - options: PostOptions = PostOptions(), + options: EventTypeCreateOptions = EventTypeCreateOptions(), ): EventTypeOut { - try { - return api.v1EventTypeCreate(eventTypeIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = eventTypeIn, + ) } /** @@ -102,31 +85,35 @@ class EventType internal constructor(token: String, options: SvixOptions) { */ suspend fun importOpenapi( eventTypeImportOpenApiIn: EventTypeImportOpenApiIn, - options: PostOptions = PostOptions(), + options: EventTypeImportOpenapiOptions = EventTypeImportOpenapiOptions(), ): EventTypeImportOpenApiOut { - try { - return api.v1EventTypeImportOpenapi(eventTypeImportOpenApiIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type/import/openapi") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = eventTypeImportOpenApiIn, + ) } /** Get an event type. */ suspend fun get(eventTypeName: String): EventTypeOut { - try { - return api.v1EventTypeGet(eventTypeName) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type/$eventTypeName") + return client.executeRequest("GET", url.build()) } /** Update an event type. */ suspend fun update(eventTypeName: String, eventTypeUpdate: EventTypeUpdate): EventTypeOut { - try { - return api.v1EventTypeUpdate(eventTypeName, eventTypeUpdate) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type/$eventTypeName") + + return client.executeRequest( + "PUT", + url.build(), + reqBody = eventTypeUpdate, + ) } /** @@ -141,19 +128,19 @@ class EventType internal constructor(token: String, options: SvixOptions) { eventTypeName: String, options: EventTypeDeleteOptions = EventTypeDeleteOptions(), ) { - try { - api.v1EventTypeDelete(eventTypeName, options.expunge) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type/$eventTypeName") + options.expunge?.let { url.addQueryParameter("expunge", serializeQueryParam(it)) } + client.executeRequest("DELETE", url.build()) } /** Partially update an event type. */ suspend fun patch(eventTypeName: String, eventTypePatch: EventTypePatch): EventTypeOut { - try { - return api.v1EventTypePatch(eventTypeName, eventTypePatch) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/event-type/$eventTypeName") + + return client.executeRequest( + "PATCH", + url.build(), + reqBody = eventTypePatch, + ) } } diff --git a/kotlin/lib/src/main/kotlin/Integration.kt b/kotlin/lib/src/main/kotlin/Integration.kt index f0e8cf0c5..fecdbdd42 100644 --- a/kotlin/lib/src/main/kotlin/Integration.kt +++ b/kotlin/lib/src/main/kotlin/Integration.kt @@ -1,72 +1,63 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.IntegrationApi import com.svix.kotlin.models.IntegrationIn import com.svix.kotlin.models.IntegrationKeyOut import com.svix.kotlin.models.IntegrationOut import com.svix.kotlin.models.IntegrationUpdate import com.svix.kotlin.models.ListResponseIntegrationOut import com.svix.kotlin.models.Ordering +import okhttp3.Headers -class IntegrationListOptions { - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null - +data class IntegrationListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } -} + val order: Ordering? = null, +) -class Integration internal constructor(token: String, options: SvixOptions) { - private val api = IntegrationApi(options.serverUrl) +data class IntegrationCreateOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class IntegrationRotateKeyOptions(val idempotencyKey: String? = null) + +class Integration(private val client: SvixHttpClient) { /** List the application's integrations. */ suspend fun list( appId: String, options: IntegrationListOptions = IntegrationListOptions(), ): ListResponseIntegrationOut { - try { - return api.v1IntegrationList(appId, options.limit, options.iterator, options.order) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** Create an integration. */ suspend fun create( appId: String, integrationIn: IntegrationIn, - options: PostOptions = PostOptions(), + options: IntegrationCreateOptions = IntegrationCreateOptions(), ): IntegrationOut { - try { - return api.v1IntegrationCreate(appId, integrationIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = integrationIn, + ) } /** Get an integration. */ suspend fun get(appId: String, integId: String): IntegrationOut { - try { - return api.v1IntegrationGet(appId, integId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration/$integId") + return client.executeRequest("GET", url.build()) } /** Update an integration. */ @@ -75,20 +66,19 @@ class Integration internal constructor(token: String, options: SvixOptions) { integId: String, integrationUpdate: IntegrationUpdate, ): IntegrationOut { - try { - return api.v1IntegrationUpdate(appId, integId, integrationUpdate) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration/$integId") + + return client.executeRequest( + "PUT", + url.build(), + reqBody = integrationUpdate, + ) } /** Delete an integration. */ suspend fun delete(appId: String, integId: String) { - try { - api.v1IntegrationDelete(appId, integId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration/$integId") + client.executeRequest("DELETE", url.build()) } /** @@ -96,25 +86,26 @@ class Integration internal constructor(token: String, options: SvixOptions) { * * @deprecated */ - @Deprecated(message = "This endpoint is deprecated.") + @Deprecated("") suspend fun getKey(appId: String, integId: String): IntegrationKeyOut { - try { - return api.v1IntegrationGetKey(appId, integId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration/$integId/key") + return client.executeRequest("GET", url.build()) } /** Rotate the integration's key. The previous key will be immediately revoked. */ suspend fun rotateKey( appId: String, integId: String, - options: PostOptions = PostOptions(), + options: IntegrationRotateKeyOptions = IntegrationRotateKeyOptions(), ): IntegrationKeyOut { - try { - return api.v1IntegrationRotateKey(appId, integId, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/integration/$integId/key/rotate") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + ) } } diff --git a/kotlin/lib/src/main/kotlin/MaybeUnset.kt b/kotlin/lib/src/main/kotlin/MaybeUnset.kt new file mode 100644 index 000000000..9f45ac0c5 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/MaybeUnset.kt @@ -0,0 +1,45 @@ +package com.svix.kotlin + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable(with = MaybeUnsetSerializer::class) +sealed class MaybeUnset { + data object Null : MaybeUnset() + + data object Unset : MaybeUnset() + + data class Present(val value: T) : MaybeUnset() +} + +class MaybeUnsetSerializer(private val dataSerializer: KSerializer) : + KSerializer> { + override val descriptor: SerialDescriptor = + SerialDescriptor("com.svix.kotlin.MaybeUnsetSerializer", dataSerializer.descriptor) + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: MaybeUnset) { + when (value) { + is MaybeUnset.Unset -> + throw SerializationException("MaybeUnset.Unset should not be serialized") + + is MaybeUnset.Null -> encoder.encodeNull() + is MaybeUnset.Present -> encoder.encodeSerializableValue(dataSerializer, value.value) + else -> throw SerializationException("Unreachable") + } + } + + override fun deserialize(decoder: Decoder): MaybeUnset { + try { + val value = decoder.decodeSerializableValue(dataSerializer) + return MaybeUnset.Present(value) + } catch (e: SerializationException) { + return MaybeUnset.Null + } + } +} diff --git a/kotlin/lib/src/main/kotlin/Message.kt b/kotlin/lib/src/main/kotlin/Message.kt index 0cc0c0df4..85428c720 100644 --- a/kotlin/lib/src/main/kotlin/Message.kt +++ b/kotlin/lib/src/main/kotlin/Message.kt @@ -1,58 +1,47 @@ -// this file is @generated (with manual changes) +// this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.MessageApi import com.svix.kotlin.models.ApplicationIn import com.svix.kotlin.models.ListResponseMessageOut import com.svix.kotlin.models.MessageIn import com.svix.kotlin.models.MessageOut -import java.time.OffsetDateTime - -class MessageListOptions { - var limit: Int? = null - var iterator: String? = null - var channel: String? = null - var before: OffsetDateTime? = null - var after: OffsetDateTime? = null - var withContent: Boolean? = null - var tag: String? = null - var eventTypes: List? = null +import kotlinx.datetime.Instant +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import okhttp3.Headers +data class MessageListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** Filter response based on the channel. */ - fun channel(channel: String) = apply { this.channel = channel } - + val channel: String? = null, /** Only include items created before a certain date. */ - fun before(before: OffsetDateTime) = apply { this.before = before } - + val before: Instant? = null, /** Only include items created after a certain date. */ - fun after(after: OffsetDateTime) = apply { this.after = after } - + val after: Instant? = null, /** When `true` message payloads are included in the response. */ - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } - + val withContent: Boolean? = null, /** Filter messages matching the provided tag. */ - fun tag(tag: String) = apply { this.tag = tag } - + val tag: String? = null, /** Filter response based on the event type */ - fun eventTypes(eventTypes: List) = apply { this.eventTypes = eventTypes } -} + val eventTypes: Set? = null, +) -class Message internal constructor(token: String, options: SvixOptions) { - private val api = MessageApi(options.serverUrl) +data class MessageCreateOptions( + /** When `true`, message payloads are included in the response. */ + val withContent: Boolean? = null, + val idempotencyKey: String? = null, +) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class MessageGetOptions( + /** When `true` message payloads are included in the response. */ + val withContent: Boolean? = null +) + +class Message(private val client: SvixHttpClient) { /** * List all of the application's messages. @@ -70,21 +59,16 @@ class Message internal constructor(token: String, options: SvixOptions) { appId: String, options: MessageListOptions = MessageListOptions(), ): ListResponseMessageOut { - try { - return api.v1MessageList( - appId, - options.limit, - options.iterator, - options.channel, - options.before, - options.after, - options.withContent, - options.tag, - HashSet(options.eventTypes), - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.channel?.let { url.addQueryParameter("channel", it) } + options.before?.let { url.addQueryParameter("before", serializeQueryParam(it)) } + options.after?.let { url.addQueryParameter("after", serializeQueryParam(it)) } + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + options.tag?.let { url.addQueryParameter("tag", it) } + options.eventTypes?.let { url.addQueryParameter("event_types", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -107,22 +91,30 @@ class Message internal constructor(token: String, options: SvixOptions) { suspend fun create( appId: String, messageIn: MessageIn, - options: PostOptions = PostOptions(), + options: MessageCreateOptions = MessageCreateOptions(), ): MessageOut { - try { - return api.v1MessageCreate(appId, messageIn, null, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg") + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = messageIn, + ) } /** Get a message by its ID or eventID. */ - suspend fun get(msgId: String, appId: String): MessageOut { - try { - return api.v1MessageGet(appId, msgId, null) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + suspend fun get( + appId: String, + msgId: String, + options: MessageGetOptions = MessageGetOptions(), + ): MessageOut { + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg/$msgId") + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -131,12 +123,9 @@ class Message internal constructor(token: String, options: SvixOptions) { * Useful in cases when a message was accidentally sent with sensitive content. The message * can't be replayed or resent once its payload has been deleted or expired. */ - suspend fun expungeContent(msgId: String, appId: String) { - try { - api.v1MessageExpungeContent(appId, msgId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + suspend fun expungeContent(appId: String, msgId: String) { + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg/$msgId/content") + client.executeRequest("DELETE", url.build()) } } @@ -164,24 +153,24 @@ fun messageInRaw( payloadRetentionHours: Long? = null, payloadRetentionPeriod: Long? = 90L, tags: Set? = null, - transformationsParams: Map = mapOf(), + transformationsParams: Map = mapOf(), ): MessageIn { val transformationsParams = transformationsParams.toMutableMap() - transformationsParams.put("rawPayload", payload) + transformationsParams["rawPayload"] = JsonPrimitive(payload) if (contentType != null) { - val headers = mapOf("content-type" to contentType) - transformationsParams.put("headers", headers) + val headers = mapOf("content-type" to JsonPrimitive(contentType)) + transformationsParams["headers"] = JsonObject(headers) } return MessageIn( eventType = eventType, - payload = mapOf(), + payload = JsonObject(mapOf()), application = application, channels = channels, eventId = eventId, payloadRetentionHours = payloadRetentionHours, payloadRetentionPeriod = payloadRetentionPeriod, tags = tags, - transformationsParams = transformationsParams, + transformationsParams = JsonObject(transformationsParams), ) } diff --git a/kotlin/lib/src/main/kotlin/MessageAttempt.kt b/kotlin/lib/src/main/kotlin/MessageAttempt.kt index 7e9f283fb..dd7e99fd2 100644 --- a/kotlin/lib/src/main/kotlin/MessageAttempt.kt +++ b/kotlin/lib/src/main/kotlin/MessageAttempt.kt @@ -1,234 +1,105 @@ -// this file is @generated (with manual changes) +// this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.MessageAttemptApi import com.svix.kotlin.models.ListResponseEndpointMessageOut -import com.svix.kotlin.models.ListResponseMessageAttemptEndpointOut import com.svix.kotlin.models.ListResponseMessageAttemptOut import com.svix.kotlin.models.ListResponseMessageEndpointOut import com.svix.kotlin.models.MessageAttemptOut import com.svix.kotlin.models.MessageStatus import com.svix.kotlin.models.StatusCodeClass -import java.time.OffsetDateTime - -class MessageAttemptListByEndpointOptions { - var limit: Int? = null - var iterator: String? = null - var status: MessageStatus? = null - var statusCodeClass: StatusCodeClass? = null - var channel: String? = null - var tag: String? = null - var before: OffsetDateTime? = null - var after: OffsetDateTime? = null - var withContent: Boolean? = null - var withMsg: Boolean? = null - var eventTypes: List? = null +import kotlinx.datetime.Instant +import okhttp3.Headers +data class MessageAttemptListByEndpointOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** * Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or * Sending (3) */ - fun status(status: MessageStatus) = apply { this.status = status } - + val status: MessageStatus? = null, /** Filter response based on the HTTP status code */ - fun statusCodeClass(statusCodeClass: StatusCodeClass) = apply { - this.statusCodeClass = statusCodeClass - } - + val statusCodeClass: StatusCodeClass? = null, /** Filter response based on the channel */ - fun channel(channel: String) = apply { this.channel = channel } - + val channel: String? = null, /** Filter response based on the tag */ - fun tag(tag: String) = apply { this.tag = tag } - + val tag: String? = null, /** Only include items created before a certain date */ - fun before(before: OffsetDateTime) = apply { this.before = before } - + val before: Instant? = null, /** Only include items created after a certain date */ - fun after(after: OffsetDateTime) = apply { this.after = after } - + val after: Instant? = null, /** When `true` attempt content is included in the response */ - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } - + val withContent: Boolean? = null, /** When `true`, the message information is included in the response */ - fun withMsg(withMsg: Boolean) = apply { this.withMsg = withMsg } - + val withMsg: Boolean? = null, /** Filter response based on the event type */ - fun eventTypes(eventTypes: List) = apply { this.eventTypes = eventTypes } -} - -class MessageAttemptListByMsgOptions { - var limit: Int? = null - var iterator: String? = null - var status: MessageStatus? = null - var statusCodeClass: StatusCodeClass? = null - var channel: String? = null - var tag: String? = null - var endpointId: String? = null - var before: OffsetDateTime? = null - var after: OffsetDateTime? = null - var withContent: Boolean? = null - var eventTypes: List? = null + val eventTypes: Set? = null, +) +data class MessageAttemptListByMsgOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** * Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or * Sending (3) */ - fun status(status: MessageStatus) = apply { this.status = status } - + val status: MessageStatus? = null, /** Filter response based on the HTTP status code */ - fun statusCodeClass(statusCodeClass: StatusCodeClass) = apply { - this.statusCodeClass = statusCodeClass - } - + val statusCodeClass: StatusCodeClass? = null, /** Filter response based on the channel */ - fun channel(channel: String) = apply { this.channel = channel } - + val channel: String? = null, /** Filter response based on the tag */ - fun tag(tag: String) = apply { this.tag = tag } - + val tag: String? = null, /** Filter the attempts based on the attempted endpoint */ - fun endpointId(endpointId: String) = apply { this.endpointId = endpointId } - + val endpointId: String? = null, /** Only include items created before a certain date */ - fun before(before: OffsetDateTime) = apply { this.before = before } - + val before: Instant? = null, /** Only include items created after a certain date */ - fun after(after: OffsetDateTime) = apply { this.after = after } - + val after: Instant? = null, /** When `true` attempt content is included in the response */ - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } - + val withContent: Boolean? = null, /** Filter response based on the event type */ - fun eventTypes(eventTypes: List) = apply { this.eventTypes = eventTypes } -} - -class MessageAttemptListAttemptedMessagesOptions { - var limit: Int? = null - var iterator: String? = null - var channel: String? = null - var tag: String? = null - var status: MessageStatus? = null - var before: OffsetDateTime? = null - var after: OffsetDateTime? = null - var withContent: Boolean? = null - var eventTypes: List? = null + val eventTypes: Set? = null, +) +data class MessageAttemptListAttemptedMessagesOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** Filter response based on the channel */ - fun channel(channel: String) = apply { this.channel = channel } - + val channel: String? = null, /** Filter response based on the message tags */ - fun tag(tag: String) = apply { this.tag = tag } - + val tag: String? = null, /** * Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or * Sending (3) */ - fun status(status: MessageStatus) = apply { this.status = status } - + val status: MessageStatus? = null, /** Only include items created before a certain date */ - fun before(before: OffsetDateTime) = apply { this.before = before } - + val before: Instant? = null, /** Only include items created after a certain date */ - fun after(after: OffsetDateTime) = apply { this.after = after } - + val after: Instant? = null, /** When `true` message payloads are included in the response */ - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } - + val withContent: Boolean? = null, /** Filter response based on the event type */ - fun eventTypes(eventTypes: List) = apply { this.eventTypes = eventTypes } -} - -class MessageAttemptListAttemptedDestinationsOptions { - var limit: Int? = null - var iterator: String? = null + val eventTypes: Set? = null, +) +data class MessageAttemptListAttemptedDestinationsOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } -} - -class MessageAttemptListOptions( - var iterator: String? = null, - var limit: Int? = null, - var messageStatus: MessageStatus? = null, - var before: OffsetDateTime? = null, - var after: OffsetDateTime? = null, - var eventTypes: List? = null, - var statusCodeClass: StatusCodeClass? = null, - var channel: String? = null, - var tag: String? = null, - var endpointId: String? = null, - var withContent: Boolean? = null, - var withMsg: Boolean? = null, -) { - fun messageStatus(messageStatus: MessageStatus) = apply { this.messageStatus = messageStatus } - - fun before(before: OffsetDateTime) = apply { this.before = before } - - fun after(after: OffsetDateTime) = apply { this.after = after } - - fun statusCodeClass(statusCodeClass: StatusCodeClass) = apply { - this.statusCodeClass = statusCodeClass - } - - fun eventTypes(eventTypes: List) = apply { this.eventTypes = eventTypes } - - fun channel(channel: String) = apply { this.channel = channel } - - fun iterator(iterator: String) = apply { this.iterator = iterator } - - fun limit(limit: Int) = apply { this.limit = limit } - - fun endpointId(endpointId: String) = apply { this.endpointId = endpointId } - - fun withContent(withContent: Boolean) = apply { this.withContent = withContent } - - fun withMsg(withMsg: Boolean) = apply { this.withMsg = withMsg } - - fun tag(tag: String) = apply { this.tag = tag } -} - -class MessageAttempt internal constructor(token: String, options: SvixOptions) { - private val api = MessageAttemptApi(options.serverUrl) + val iterator: String? = null, +) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class MessageAttemptResendOptions(val idempotencyKey: String? = null) - /** @deprecated use listByMsg instead. */ - @Deprecated(message = "use listByMsg instead.") - suspend fun list( - appId: String, - msgId: String, - options: MessageAttemptListByMsgOptions = MessageAttemptListByMsgOptions(), - ): ListResponseMessageAttemptOut { - return this.listByMsg(appId, msgId, options) - } +class MessageAttempt(private val client: SvixHttpClient) { /** * List attempts by endpoint id @@ -243,25 +114,22 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { endpointId: String, options: MessageAttemptListByEndpointOptions = MessageAttemptListByEndpointOptions(), ): ListResponseMessageAttemptOut { - try { - return api.v1MessageAttemptListByEndpoint( - appId, - endpointId, - options.limit, - options.iterator, - options.status, - options.statusCodeClass, - options.channel, - options.tag, - options.before, - options.after, - options.withContent, - options.withMsg, - HashSet(options.eventTypes), - ) - } catch (e: Exception) { - throw ApiException.wrap(e) + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/attempt/endpoint/$endpointId") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.status?.let { url.addQueryParameter("status", serializeQueryParam(it)) } + options.statusCodeClass?.let { + url.addQueryParameter("status_code_class", serializeQueryParam(it)) } + options.channel?.let { url.addQueryParameter("channel", it) } + options.tag?.let { url.addQueryParameter("tag", it) } + options.before?.let { url.addQueryParameter("before", serializeQueryParam(it)) } + options.after?.let { url.addQueryParameter("after", serializeQueryParam(it)) } + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + options.withMsg?.let { url.addQueryParameter("with_msg", serializeQueryParam(it)) } + options.eventTypes?.let { url.addQueryParameter("event_types", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -277,25 +145,21 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { msgId: String, options: MessageAttemptListByMsgOptions = MessageAttemptListByMsgOptions(), ): ListResponseMessageAttemptOut { - try { - return api.v1MessageAttemptListByMsg( - appId, - msgId, - options.limit, - options.iterator, - options.status, - options.statusCodeClass, - options.channel, - options.tag, - options.endpointId, - options.before, - options.after, - options.withContent, - HashSet(options.eventTypes), - ) - } catch (e: Exception) { - throw ApiException.wrap(e) + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/attempt/msg/$msgId") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.status?.let { url.addQueryParameter("status", serializeQueryParam(it)) } + options.statusCodeClass?.let { + url.addQueryParameter("status_code_class", serializeQueryParam(it)) } + options.channel?.let { url.addQueryParameter("channel", it) } + options.tag?.let { url.addQueryParameter("tag", it) } + options.endpointId?.let { url.addQueryParameter("endpoint_id", it) } + options.before?.let { url.addQueryParameter("before", serializeQueryParam(it)) } + options.after?.let { url.addQueryParameter("after", serializeQueryParam(it)) } + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + options.eventTypes?.let { url.addQueryParameter("event_types", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** @@ -316,32 +180,24 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { options: MessageAttemptListAttemptedMessagesOptions = MessageAttemptListAttemptedMessagesOptions(), ): ListResponseEndpointMessageOut { - try { - return api.v1MessageAttemptListAttemptedMessages( - appId, - endpointId, - options.limit, - options.iterator, - options.channel, - options.tag, - options.status, - options.before, - options.after, - options.withContent, - HashSet(options.eventTypes), - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/endpoint/$endpointId/msg") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.channel?.let { url.addQueryParameter("channel", it) } + options.tag?.let { url.addQueryParameter("tag", it) } + options.status?.let { url.addQueryParameter("status", serializeQueryParam(it)) } + options.before?.let { url.addQueryParameter("before", serializeQueryParam(it)) } + options.after?.let { url.addQueryParameter("after", serializeQueryParam(it)) } + options.withContent?.let { url.addQueryParameter("with_content", serializeQueryParam(it)) } + options.eventTypes?.let { url.addQueryParameter("event_types", serializeQueryParam(it)) } + return client.executeRequest("GET", url.build()) } /** `msg_id`: Use a message id or a message `eventId` */ suspend fun get(appId: String, msgId: String, attemptId: String): MessageAttemptOut { - try { - return api.v1MessageAttemptGet(appId, msgId, attemptId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg/$msgId/attempt/$attemptId") + return client.executeRequest("GET", url.build()) } /** @@ -351,11 +207,11 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { * replayed or resent once its payload has been deleted or expired. */ suspend fun expungeContent(appId: String, msgId: String, attemptId: String) { - try { - api.v1MessageAttemptExpungeContent(appId, msgId, attemptId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/msg/$msgId/attempt/$attemptId/content") + client.executeRequest("DELETE", url.build()) } /** @@ -370,37 +226,10 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { options: MessageAttemptListAttemptedDestinationsOptions = MessageAttemptListAttemptedDestinationsOptions(), ): ListResponseMessageEndpointOut { - try { - return api.v1MessageAttemptListAttemptedDestinations( - appId, - msgId, - options.limit, - options.iterator, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } - } - - @Deprecated(message = "use listByMsg instead, passing the endpoint ID through options.") - suspend fun listAttemptsForEndpoint( - appId: String, - endpointId: String, - msgId: String, - options: MessageAttemptListOptions = MessageAttemptListOptions(), - ): ListResponseMessageAttemptOut { - val listByMsgOptions = MessageAttemptListByMsgOptions() - listByMsgOptions.limit = options.limit - listByMsgOptions.iterator = options.iterator - listByMsgOptions.channel = options.channel - listByMsgOptions.tag = options.tag - listByMsgOptions.status = options.messageStatus - listByMsgOptions.before = options.before - listByMsgOptions.after = options.after - listByMsgOptions.eventTypes = options.eventTypes - listByMsgOptions.endpointId = endpointId - - return listByMsg(appId, msgId, listByMsgOptions) + val url = client.newUrlBuilder().encodedPath("/api/v1/app/$appId/msg/$msgId/endpoint") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + return client.executeRequest("GET", url.build()) } /** Resend a message to the specified endpoint. */ @@ -408,12 +237,14 @@ class MessageAttempt internal constructor(token: String, options: SvixOptions) { appId: String, msgId: String, endpointId: String, - options: PostOptions = PostOptions(), + options: MessageAttemptResendOptions = MessageAttemptResendOptions(), ) { - try { - api.v1MessageAttemptResend(appId, msgId, endpointId, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/app/$appId/msg/$msgId/endpoint/$endpointId/resend") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + client.executeRequest("POST", url.build(), headers = headers.build()) } } diff --git a/kotlin/lib/src/main/kotlin/OperationalWebhookEndpoint.kt b/kotlin/lib/src/main/kotlin/OperationalWebhookEndpoint.kt index 8234b3d1c..8a38de1ae 100644 --- a/kotlin/lib/src/main/kotlin/OperationalWebhookEndpoint.kt +++ b/kotlin/lib/src/main/kotlin/OperationalWebhookEndpoint.kt @@ -1,8 +1,6 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.WebhookEndpointApi as OperationalWebhookEndpointApi import com.svix.kotlin.models.ListResponseOperationalWebhookEndpointOut import com.svix.kotlin.models.OperationalWebhookEndpointHeadersIn import com.svix.kotlin.models.OperationalWebhookEndpointHeadersOut @@ -12,69 +10,59 @@ import com.svix.kotlin.models.OperationalWebhookEndpointSecretIn import com.svix.kotlin.models.OperationalWebhookEndpointSecretOut import com.svix.kotlin.models.OperationalWebhookEndpointUpdate import com.svix.kotlin.models.Ordering +import okhttp3.Headers -class OperationalWebhookEndpointListOptions { - var limit: Int? = null - var iterator: String? = null - var order: Ordering? = null - +data class OperationalWebhookEndpointListOptions( /** Limit the number of returned items */ - fun limit(limit: Int) = apply { this.limit = limit } - + val limit: ULong? = null, /** The iterator returned from a prior invocation */ - fun iterator(iterator: String) = apply { this.iterator = iterator } - + val iterator: String? = null, /** The sorting order of the returned items */ - fun order(order: Ordering) = apply { this.order = order } -} + val order: Ordering? = null, +) -class OperationalWebhookEndpoint internal constructor(token: String, options: SvixOptions) { - private val api = OperationalWebhookEndpointApi(options.serverUrl) +data class OperationalWebhookEndpointCreateOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +data class OperationalWebhookEndpointRotateSecretOptions(val idempotencyKey: String? = null) + +class OperationalWebhookEndpoint(private val client: SvixHttpClient) { /** List operational webhook endpoints. */ suspend fun list( options: OperationalWebhookEndpointListOptions = OperationalWebhookEndpointListOptions() ): ListResponseOperationalWebhookEndpointOut { - try { - return api.v1OperationalWebhookEndpointList( - options.limit, - options.iterator, - options.order, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/operational-webhook/endpoint") + options.limit?.let { url.addQueryParameter("limit", serializeQueryParam(it)) } + options.iterator?.let { url.addQueryParameter("iterator", it) } + options.order?.let { url.addQueryParameter("order", serializeQueryParam(it)) } + return client.executeRequest( + "GET", + url.build(), + ) } /** Create an operational webhook endpoint. */ suspend fun create( operationalWebhookEndpointIn: OperationalWebhookEndpointIn, - options: PostOptions = PostOptions(), + options: OperationalWebhookEndpointCreateOptions = OperationalWebhookEndpointCreateOptions(), ): OperationalWebhookEndpointOut { - try { - return api.v1OperationalWebhookEndpointCreate( - operationalWebhookEndpointIn, - options.idempotencyKey, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/operational-webhook/endpoint") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = operationalWebhookEndpointIn, + ) } /** Get an operational webhook endpoint. */ suspend fun get(endpointId: String): OperationalWebhookEndpointOut { - try { - return api.v1OperationalWebhookEndpointGet(endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/operational-webhook/endpoint/$endpointId") + return client.executeRequest("GET", url.build()) } /** Update an operational webhook endpoint. */ @@ -82,32 +70,33 @@ class OperationalWebhookEndpoint internal constructor(token: String, options: Sv endpointId: String, operationalWebhookEndpointUpdate: OperationalWebhookEndpointUpdate, ): OperationalWebhookEndpointOut { - try { - return api.v1OperationalWebhookEndpointUpdate( - endpointId, - operationalWebhookEndpointUpdate, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/operational-webhook/endpoint/$endpointId") + + return client.executeRequest< + OperationalWebhookEndpointUpdate, + OperationalWebhookEndpointOut, + >( + "PUT", + url.build(), + reqBody = operationalWebhookEndpointUpdate, + ) } /** Delete an operational webhook endpoint. */ suspend fun delete(endpointId: String) { - try { - api.v1OperationalWebhookEndpointDelete(endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client.newUrlBuilder().encodedPath("/api/v1/operational-webhook/endpoint/$endpointId") + client.executeRequest("DELETE", url.build()) } /** Get the additional headers to be sent with the operational webhook. */ suspend fun getHeaders(endpointId: String): OperationalWebhookEndpointHeadersOut { - try { - return api.v1OperationalWebhookEndpointGetHeaders(endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/operational-webhook/endpoint/$endpointId/headers") + return client.executeRequest("GET", url.build()) } /** Set the additional headers to be sent with the operational webhook. */ @@ -115,14 +104,16 @@ class OperationalWebhookEndpoint internal constructor(token: String, options: Sv endpointId: String, operationalWebhookEndpointHeadersIn: OperationalWebhookEndpointHeadersIn, ) { - try { - api.v1OperationalWebhookEndpointUpdateHeaders( - endpointId, - operationalWebhookEndpointHeadersIn, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/operational-webhook/endpoint/$endpointId/headers") + + client.executeRequest( + "PUT", + url.build(), + reqBody = operationalWebhookEndpointHeadersIn, + ) } /** @@ -132,11 +123,11 @@ class OperationalWebhookEndpoint internal constructor(token: String, options: Sv * [the consuming webhooks docs](https://docs.svix.com/consuming-webhooks/). */ suspend fun getSecret(endpointId: String): OperationalWebhookEndpointSecretOut { - try { - return api.v1OperationalWebhookEndpointGetSecret(endpointId) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/operational-webhook/endpoint/$endpointId/secret") + return client.executeRequest("GET", url.build()) } /** @@ -147,16 +138,21 @@ class OperationalWebhookEndpoint internal constructor(token: String, options: Sv suspend fun rotateSecret( endpointId: String, operationalWebhookEndpointSecretIn: OperationalWebhookEndpointSecretIn, - options: PostOptions = PostOptions(), + options: OperationalWebhookEndpointRotateSecretOptions = + OperationalWebhookEndpointRotateSecretOptions(), ) { - try { - api.v1OperationalWebhookEndpointRotateSecret( - endpointId, - operationalWebhookEndpointSecretIn, - options.idempotencyKey, - ) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = + client + .newUrlBuilder() + .encodedPath("/api/v1/operational-webhook/endpoint/$endpointId/secret/rotate") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = operationalWebhookEndpointSecretIn, + ) } } diff --git a/kotlin/lib/src/main/kotlin/Statistics.kt b/kotlin/lib/src/main/kotlin/Statistics.kt index 79ca3e2a6..e578a1925 100644 --- a/kotlin/lib/src/main/kotlin/Statistics.kt +++ b/kotlin/lib/src/main/kotlin/Statistics.kt @@ -1,21 +1,14 @@ // this file is @generated package com.svix.kotlin -import com.svix.kotlin.exceptions.ApiException -import com.svix.kotlin.internal.apis.StatisticsApi import com.svix.kotlin.models.AggregateEventTypesOut import com.svix.kotlin.models.AppUsageStatsIn import com.svix.kotlin.models.AppUsageStatsOut +import okhttp3.Headers -class Statistics internal constructor(token: String, options: SvixOptions) { - private val api = StatisticsApi(options.serverUrl) +data class StatisticsAggregateAppStatsOptions(val idempotencyKey: String? = null) - init { - api.accessToken = token - api.userAgent = options.getUA() - options.initialRetryDelayMillis?.let { api.initialRetryDelayMillis = it } - options.numRetries?.let { api.numRetries = it } - } +class Statistics(private val client: SvixHttpClient) { /** * Creates a background task to calculate the message destinations for all applications in the @@ -26,13 +19,18 @@ class Statistics internal constructor(token: String, options: SvixOptions) { */ suspend fun aggregateAppStats( appUsageStatsIn: AppUsageStatsIn, - options: PostOptions = PostOptions(), + options: StatisticsAggregateAppStatsOptions = StatisticsAggregateAppStatsOptions(), ): AppUsageStatsOut { - try { - return api.v1StatisticsAggregateAppStats(appUsageStatsIn, options.idempotencyKey) - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/stats/usage/app") + val headers = Headers.Builder() + options.idempotencyKey?.let { headers.add("idempotency-key", it) } + + return client.executeRequest( + "POST", + url.build(), + headers = headers.build(), + reqBody = appUsageStatsIn, + ) } /** @@ -43,10 +41,7 @@ class Statistics internal constructor(token: String, options: SvixOptions) { * endpoint to retrieve the results of the operation. */ suspend fun aggregateEventTypes(): AggregateEventTypesOut { - try { - return api.v1StatisticsAggregateEventTypes() - } catch (e: Exception) { - throw ApiException.wrap(e) - } + val url = client.newUrlBuilder().encodedPath("/api/v1/stats/usage/event-types") + return client.executeRequest("PUT", url.build()) } } diff --git a/kotlin/lib/src/main/kotlin/StringAnyMapSerializer.kt b/kotlin/lib/src/main/kotlin/StringAnyMapSerializer.kt new file mode 100644 index 000000000..0e9bc6515 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/StringAnyMapSerializer.kt @@ -0,0 +1,138 @@ +package com.svix.kotlin + +import kotlinx.serialization.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive + +@OptIn(ExperimentalSerializationApi::class) +object StringAnyMapSerializer : KSerializer> { + override val descriptor: SerialDescriptor = JsonObject.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Map) { + val jsonEncoder = + encoder as? JsonEncoder + ?: throw SerializationException("This serializer can only be used with JSON") + val jsonObject = value.toJsonElement() + jsonEncoder.encodeJsonElement(jsonObject) + } + + override fun deserialize(decoder: Decoder): Map { + val jsonDecoder = + decoder as? JsonDecoder + ?: throw SerializationException("This serializer can only be used with JSON") + val element = jsonDecoder.decodeJsonElement() + + if (element !is JsonObject) throw SerializationException("Expected JsonObject") + + return element.mapValues { (_, value) -> + deserializeJsonElement(value) ?: throw SerializationException("Null values not allowed") + } + } + + private fun deserializeJsonElement(element: JsonElement): Any? { + return when (element) { + is JsonPrimitive -> + when { + element.isString -> element.content + element.intOrNull != null -> element.int + element.longOrNull != null -> element.long + element.doubleOrNull != null -> element.double + element.booleanOrNull != null -> element.boolean + else -> element.content + } + + is JsonArray -> + element.map { + deserializeJsonElement(it) + ?: throw SerializationException("Null values not allowed in arrays") + } + + is JsonObject -> + element.mapValues { (_, value) -> + deserializeJsonElement(value) + ?: throw SerializationException("Null values not allowed in objects") + } + + JsonNull -> null + } + } +} + +@OptIn(ExperimentalSerializationApi::class) +object MaybeUnsetStringAnyMapSerializer : KSerializer>> { + override val descriptor: SerialDescriptor = JsonObject.serializer().descriptor + + override fun serialize(encoder: Encoder, value: MaybeUnset>) { + val jsonEncoder = + encoder as? JsonEncoder + ?: throw SerializationException("This serializer can only be used with JSON") + + when (value) { + is MaybeUnset.Unset -> + throw SerializationException("MaybeUnset.Unset should not be serialized") + is MaybeUnset.Null -> jsonEncoder.encodeJsonElement(JsonNull) + is MaybeUnset.Present -> StringAnyMapSerializer.serialize(encoder, value.value) + } + } + + override fun deserialize(decoder: Decoder): MaybeUnset> { + val jsonDecoder = + decoder as? JsonDecoder + ?: throw SerializationException("This serializer can only be used with JSON") + val element = jsonDecoder.decodeJsonElement() + + return when (element) { + JsonNull -> MaybeUnset.Null + is JsonObject -> MaybeUnset.Present(StringAnyMapSerializer.deserialize(decoder)) + else -> throw SerializationException("Expected JsonObject or null") + } + } +} + +fun Any?.toJsonElement(): JsonElement = + when (this) { + null -> JsonNull + is Map<*, *> -> toJsonElement() + is Collection<*> -> toJsonElement() + is ByteArray -> this.toList().toJsonElement() + is CharArray -> this.toList().toJsonElement() + is ShortArray -> this.toList().toJsonElement() + is IntArray -> this.toList().toJsonElement() + is LongArray -> this.toList().toJsonElement() + is FloatArray -> this.toList().toJsonElement() + is DoubleArray -> this.toList().toJsonElement() + is BooleanArray -> this.toList().toJsonElement() + is Array<*> -> toJsonElement() + is Boolean -> JsonPrimitive(this) + is Number -> JsonPrimitive(this) + is String -> JsonPrimitive(this) + is Enum<*> -> JsonPrimitive(this.toString()) + else -> { + throw IllegalStateException("Can't serialize unknown type: $this") + } + } + +fun Map<*, *>.toJsonElement(): JsonElement { + val map = mutableMapOf() + this.forEach { key, value -> + key as String + map[key] = value.toJsonElement() + } + return JsonObject(map) +} + +fun Collection<*>.toJsonElement(): JsonElement { + return JsonArray(this.map { it.toJsonElement() }) +} + +fun Array<*>.toJsonElement(): JsonElement { + return JsonArray(this.map { it.toJsonElement() }) +} diff --git a/kotlin/lib/src/main/kotlin/Svix.kt b/kotlin/lib/src/main/kotlin/Svix.kt index a7e798926..867e4328b 100644 --- a/kotlin/lib/src/main/kotlin/Svix.kt +++ b/kotlin/lib/src/main/kotlin/Svix.kt @@ -1,27 +1,53 @@ package com.svix.kotlin +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + class Svix(token: String, options: SvixOptions = SvixOptions()) { + + val application: Application + val authentication: Authentication + val endpoint: Endpoint + val eventType: EventType + val integration: Integration + val message: Message + val messageAttempt: MessageAttempt + val statistics: Statistics + val operationalWebhookEndpoint: OperationalWebhookEndpoint + init { val tokenParts = token.split(".") - if (options.wantedServerUrl == null) { + if (options.baseUrl == null) { val region = tokenParts.last() - if (region == "us") { - options.serverUrl = "https://api.us.svix.com" - } else if (region == "eu") { - options.serverUrl = "https://api.eu.svix.com" - } else if (region == "in") { - options.serverUrl = "https://api.in.svix.com" + when (region) { + "us" -> options.baseUrl = "https://api.us.svix.com" + "eu" -> options.baseUrl = "https://api.eu.svix.com" + "in" -> options.baseUrl = "https://api.in.svix.com" + else -> options.baseUrl = "https://api.svix.com" } } + val parsedUrl = options.baseUrl?.toHttpUrlOrNull() ?: throw Exception("Invalid base url") + val defaultHeaders = + mapOf("User-Agent" to "svix-libs/${Version}/kotlin", "Authorization" to "Bearer $token") + val httpClient = SvixHttpClient(parsedUrl, defaultHeaders, options.retrySchedule) + application = Application(httpClient) + authentication = Authentication(httpClient) + endpoint = Endpoint(httpClient) + eventType = EventType(httpClient) + integration = Integration(httpClient) + message = Message(httpClient) + messageAttempt = MessageAttempt(httpClient) + statistics = Statistics(httpClient) + operationalWebhookEndpoint = OperationalWebhookEndpoint(httpClient) } +} - val application = Application(token, options) - val authentication = Authentication(token, options) - val endpoint = Endpoint(token, options) - val eventType = EventType(token, options) - val integration = Integration(token, options) - val message = Message(token, options) - val messageAttempt = MessageAttempt(token, options) - val statistics = Statistics(token, options) - val operationalWebhookEndpoint = OperationalWebhookEndpoint(token, options) +data class SvixOptions( + var baseUrl: String? = null, + val retrySchedule: List = listOf(50, 100, 200), +) { + init { + if (retrySchedule.count() > 5) { + throw IllegalArgumentException("number of retries must not exceed 5") + } + } } diff --git a/kotlin/lib/src/main/kotlin/SvixHttpClient.kt b/kotlin/lib/src/main/kotlin/SvixHttpClient.kt new file mode 100644 index 000000000..e5f717b5a --- /dev/null +++ b/kotlin/lib/src/main/kotlin/SvixHttpClient.kt @@ -0,0 +1,82 @@ +package com.svix.kotlin + +import com.svix.kotlin.exceptions.ApiException +import kotlin.random.Random +import kotlin.random.nextULong +import kotlinx.coroutines.delay +import kotlinx.serialization.json.Json +import okhttp3.* +import okhttp3.RequestBody.Companion.toRequestBody + +open class SvixHttpClient +internal constructor( + private val baseUrl: HttpUrl, + private val defaultHeaders: Map, + private val retrySchedule: List, +) { + private val client: OkHttpClient = OkHttpClient() + + fun newUrlBuilder(): HttpUrl.Builder { + return HttpUrl.Builder().scheme(baseUrl.scheme).host(baseUrl.host).port(baseUrl.port) + } + + internal suspend inline fun executeRequest( + method: String, + url: HttpUrl, + headers: Headers? = null, + reqBody: Req? = null, + ): Res { + val reqBuilder = Request.Builder().url(url) + if (reqBody != null) { + reqBuilder.method(method, Json.encodeToString(reqBody).toRequestBody()) + } else { + reqBuilder.method(method, null) + } + + for ((k, v) in defaultHeaders) { + reqBuilder.addHeader(k, v) + } + if (headers != null) { + for ((k, v) in headers) { + reqBuilder.addHeader(k, v) + } + } + reqBuilder.addHeader("svix-req-id", Random.nextULong().toString()) + + val request = reqBuilder.build() + val res = executeRequestWithRetry(request) + + // if body is null panic + if (res.body == null) { + throw ApiException("Body is null", res.code) + } + val bodyString = res.body!!.string() + if (res.code == 204) { + return Json.decodeFromString("true") + } + if (res.code in 200..299) { + return Json.decodeFromString(bodyString) + } + throw ApiException("None 200 status code", res.code, bodyString) + } + + suspend fun executeRequestWithRetry(request: Request): Response { + + var res = client.newCall(request).execute() + + if (res.code >= 500) { + retrySchedule.forEachIndexed { index, sleepTime -> + run { + delay(sleepTime) + val newReq = + request + .newBuilder() + .header("svix-retry-count", (index + 1).toString()) + .build() + res = client.newCall(newReq).execute() + } + } + } + return res + } +} diff --git a/kotlin/lib/src/main/kotlin/SvixOptions.kt b/kotlin/lib/src/main/kotlin/SvixOptions.kt deleted file mode 100644 index 1ad987368..000000000 --- a/kotlin/lib/src/main/kotlin/SvixOptions.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.svix.kotlin - -data class SvixOptions( - internal var wantedServerUrl: String? = null, - val initialRetryDelayMillis: Long? = null, - val numRetries: Int? = null, -) { - private val version = "1.21.0" - - var serverUrl: String - get() = this.wantedServerUrl ?: DEFAULT_URL - set(value) { - this.wantedServerUrl = value - } - - companion object { - const val DEFAULT_URL = "https://api.svix.com" - } - - internal fun getUA(): String { - return "svix-libs/$version/kotlin" - } -} diff --git a/kotlin/lib/src/main/kotlin/Utils.kt b/kotlin/lib/src/main/kotlin/Utils.kt new file mode 100644 index 000000000..79085f2aa --- /dev/null +++ b/kotlin/lib/src/main/kotlin/Utils.kt @@ -0,0 +1,39 @@ +package com.svix.kotlin + +import kotlin.reflect.full.isSubclassOf + +private fun isEnum(v: Any?): Boolean { + return v != null && v::class.isSubclassOf(Enum::class) +} + +private fun isList(v: Any?): Boolean { + return v != null && v::class.isSubclassOf(List::class) +} + +private fun isSet(v: Any?): Boolean { + return v != null && v::class.isSubclassOf(Set::class) +} + +internal fun serializeQueryParam(v: Any): String { + return if (isEnum(v)) { + (v as ToQueryParam).toQueryParam() + } else if (isList(v)) { + // sort before comma separating the values (helps me assert when testing) + (v as List).map { serializeQueryParam(it) }.sorted().joinToString(",") + } else if (isSet(v)) { + (v as Set) + .asSequence() + .map { serializeQueryParam(it) } + .toList() + .sorted() + .joinToString(",") + } else { + v.toString() + } +} + +internal interface ToQueryParam { + // Used to get the enums correct representation as a query param + // does not url encode the returned string + fun toQueryParam(): String +} diff --git a/kotlin/lib/src/main/kotlin/Version.kt b/kotlin/lib/src/main/kotlin/Version.kt new file mode 100644 index 000000000..51b5c4661 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/Version.kt @@ -0,0 +1,3 @@ +package com.svix.kotlin + +const val Version = "1.56.0" diff --git a/kotlin/lib/src/main/kotlin/exceptions/ApiException.kt b/kotlin/lib/src/main/kotlin/exceptions/ApiException.kt index f196badde..3908ad3f2 100644 --- a/kotlin/lib/src/main/kotlin/exceptions/ApiException.kt +++ b/kotlin/lib/src/main/kotlin/exceptions/ApiException.kt @@ -1,39 +1,4 @@ package com.svix.kotlin.exceptions -import com.svix.kotlin.internal.infrastructure.ClientError -import com.svix.kotlin.internal.infrastructure.ClientException -import com.svix.kotlin.internal.infrastructure.Response -import com.svix.kotlin.internal.infrastructure.ServerError -import com.svix.kotlin.internal.infrastructure.ServerException - -class ApiException -internal constructor(message: String? = null, val statusCode: Int = -1, val body: String? = null) : - RuntimeException(message) { - companion object { - private fun extractBody(response: Response?): String? { - return when (response) { - is ClientError<*> -> { - val body = response.body - return if (body is String) body else null - } - is ServerError<*> -> { - val body = response.body - return if (body is String) body else null - } - else -> null - } - } - - internal fun wrap(e: Exception): Exception { - return when (e) { - is ServerException -> { - ApiException(e.message, e.statusCode, extractBody(e.response)) - } - is ClientException -> { - ApiException(e.message, e.statusCode, extractBody(e.response)) - } - else -> e - } - } - } -} +class ApiException(message: String? = null, val statusCode: Int = -1, val body: String? = null) : + RuntimeException(message) {} diff --git a/kotlin/lib/src/main/kotlin/models/AggregateEventTypesOut.kt b/kotlin/lib/src/main/kotlin/models/AggregateEventTypesOut.kt new file mode 100644 index 000000000..d98f25ea8 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/AggregateEventTypesOut.kt @@ -0,0 +1,11 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class AggregateEventTypesOut( + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, +) diff --git a/kotlin/lib/src/main/kotlin/models/AppPortalAccessIn.kt b/kotlin/lib/src/main/kotlin/models/AppPortalAccessIn.kt new file mode 100644 index 000000000..b62bafa9a --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/AppPortalAccessIn.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class AppPortalAccessIn( + val application: ApplicationIn? = null, + val expiry: ULong? = null, + val featureFlags: Set? = null, + val readOnly: Boolean? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/AppPortalAccessOut.kt b/kotlin/lib/src/main/kotlin/models/AppPortalAccessOut.kt new file mode 100644 index 000000000..42357c3f6 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/AppPortalAccessOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class AppPortalAccessOut(val token: String, val url: String) diff --git a/kotlin/lib/src/main/kotlin/models/AppUsageStatsIn.kt b/kotlin/lib/src/main/kotlin/models/AppUsageStatsIn.kt new file mode 100644 index 000000000..37b3407d4 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/AppUsageStatsIn.kt @@ -0,0 +1,8 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class AppUsageStatsIn(val appIds: Set? = null, val since: Instant, val until: Instant) diff --git a/kotlin/lib/src/main/kotlin/models/AppUsageStatsOut.kt b/kotlin/lib/src/main/kotlin/models/AppUsageStatsOut.kt new file mode 100644 index 000000000..a282bf878 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/AppUsageStatsOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class AppUsageStatsOut( + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, + val unresolvedAppIds: Set, +) diff --git a/kotlin/lib/src/main/kotlin/models/ApplicationIn.kt b/kotlin/lib/src/main/kotlin/models/ApplicationIn.kt new file mode 100644 index 000000000..028f0b72b --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ApplicationIn.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ApplicationIn( + val metadata: Map? = null, + val name: String, + val rateLimit: UShort? = null, + val uid: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ApplicationOut.kt b/kotlin/lib/src/main/kotlin/models/ApplicationOut.kt new file mode 100644 index 000000000..c7f7a230f --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ApplicationOut.kt @@ -0,0 +1,16 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class ApplicationOut( + val createdAt: Instant, + val id: String, + val metadata: Map, + val name: String, + val rateLimit: UShort? = null, + val uid: String? = null, + val updatedAt: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/ApplicationPatch.kt b/kotlin/lib/src/main/kotlin/models/ApplicationPatch.kt new file mode 100644 index 000000000..d97e9e75c --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ApplicationPatch.kt @@ -0,0 +1,13 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.MaybeUnset +import kotlinx.serialization.Serializable + +@Serializable +data class ApplicationPatch( + val metadata: Map? = null, + val name: String? = null, + val rateLimit: MaybeUnset = MaybeUnset.Unset, + val uid: MaybeUnset = MaybeUnset.Unset, +) diff --git a/kotlin/lib/src/main/kotlin/models/ApplicationTokenExpireIn.kt b/kotlin/lib/src/main/kotlin/models/ApplicationTokenExpireIn.kt new file mode 100644 index 000000000..12ddf3f97 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ApplicationTokenExpireIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class ApplicationTokenExpireIn(val expiry: Long? = null) diff --git a/kotlin/lib/src/main/kotlin/models/BackgroundTaskOut.kt b/kotlin/lib/src/main/kotlin/models/BackgroundTaskOut.kt new file mode 100644 index 000000000..83be0130c --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/BackgroundTaskOut.kt @@ -0,0 +1,13 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class BackgroundTaskOut( + @Serializable(with = StringAnyMapSerializer::class) val data: Map, + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, +) diff --git a/kotlin/lib/src/main/kotlin/models/BackgroundTaskStatus.kt b/kotlin/lib/src/main/kotlin/models/BackgroundTaskStatus.kt new file mode 100644 index 000000000..b62f862ed --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/BackgroundTaskStatus.kt @@ -0,0 +1,18 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +enum class BackgroundTaskStatus : ToQueryParam { + @SerialName("running") RUNNING, + @SerialName("finished") FINISHED, + @SerialName("failed") FAILED; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} diff --git a/kotlin/lib/src/main/kotlin/models/BackgroundTaskType.kt b/kotlin/lib/src/main/kotlin/models/BackgroundTaskType.kt new file mode 100644 index 000000000..2f9a2b652 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/BackgroundTaskType.kt @@ -0,0 +1,22 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +enum class BackgroundTaskType : ToQueryParam { + @SerialName("endpoint.replay") ENDPOINT_REPLAY, + @SerialName("endpoint.recover") ENDPOINT_RECOVER, + @SerialName("application.stats") APPLICATION_STATS, + @SerialName("message.broadcast") MESSAGE_BROADCAST, + @SerialName("sdk.generate") SDK_GENERATE, + @SerialName("event-type.aggregate") EVENT_TYPE_AGGREGATE, + @SerialName("application.purge_content") APPLICATION_PURGE_CONTENT; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} diff --git a/kotlin/lib/src/main/kotlin/models/ConnectorIn.kt b/kotlin/lib/src/main/kotlin/models/ConnectorIn.kt new file mode 100644 index 000000000..07c35f089 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ConnectorIn.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ConnectorIn( + val description: String? = null, + val featureFlag: String? = null, + val filterTypes: Set? = null, + val instructions: String? = null, + val instructionsLink: String? = null, + val kind: ConnectorKind? = null, + val logo: String, + val name: String, + val transformation: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/ConnectorKind.kt b/kotlin/lib/src/main/kotlin/models/ConnectorKind.kt new file mode 100644 index 000000000..11ef1cfd9 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ConnectorKind.kt @@ -0,0 +1,27 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +enum class ConnectorKind : ToQueryParam { + @SerialName("Custom") CUSTOM, + @SerialName("CustomerIO") CUSTOMER_IO, + @SerialName("Discord") DISCORD, + @SerialName("Hubspot") HUBSPOT, + @SerialName("Inngest") INNGEST, + @SerialName("Salesforce") SALESFORCE, + @SerialName("Segment") SEGMENT, + @SerialName("Slack") SLACK, + @SerialName("Teams") TEAMS, + @SerialName("TriggerDev") TRIGGER_DEV, + @SerialName("Windmill") WINDMILL, + @SerialName("Zapier") ZAPIER; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} diff --git a/kotlin/lib/src/main/kotlin/models/DashboardAccessOut.kt b/kotlin/lib/src/main/kotlin/models/DashboardAccessOut.kt new file mode 100644 index 000000000..030538c1b --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/DashboardAccessOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class DashboardAccessOut(val token: String, val url: String) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointHeadersIn.kt b/kotlin/lib/src/main/kotlin/models/EndpointHeadersIn.kt new file mode 100644 index 000000000..438913d73 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointHeadersIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EndpointHeadersIn(val headers: Map) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointHeadersOut.kt b/kotlin/lib/src/main/kotlin/models/EndpointHeadersOut.kt new file mode 100644 index 000000000..9dbe190a9 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointHeadersOut.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointHeadersOut(val headers: Map, val sensitive: Set) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointHeadersPatchIn.kt b/kotlin/lib/src/main/kotlin/models/EndpointHeadersPatchIn.kt new file mode 100644 index 000000000..8f3d74e5a --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointHeadersPatchIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EndpointHeadersPatchIn(val headers: Map) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointIn.kt b/kotlin/lib/src/main/kotlin/models/EndpointIn.kt new file mode 100644 index 000000000..52a81f681 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointIn.kt @@ -0,0 +1,18 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointIn( + val channels: Set? = null, + val description: String? = null, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val metadata: Map? = null, + val rateLimit: UShort? = null, + val secret: String? = null, + val uid: String? = null, + val url: String, + val version: UShort? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointMessageOut.kt b/kotlin/lib/src/main/kotlin/models/EndpointMessageOut.kt new file mode 100644 index 000000000..57967aec9 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointMessageOut.kt @@ -0,0 +1,19 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointMessageOut( + val channels: Set? = null, + val eventId: String? = null, + val eventType: String, + val id: String, + val nextAttempt: Instant? = null, + @Serializable(with = StringAnyMapSerializer::class) val payload: Map, + val status: MessageStatus, + val tags: Set? = null, + val timestamp: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointOut.kt b/kotlin/lib/src/main/kotlin/models/EndpointOut.kt new file mode 100644 index 000000000..3baeee3b7 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointOut.kt @@ -0,0 +1,21 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointOut( + val channels: Set? = null, + val createdAt: Instant, + val description: String, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val id: String, + val metadata: Map, + val rateLimit: UShort? = null, + val uid: String? = null, + val updatedAt: Instant, + val url: String, + val version: Int, +) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointPatch.kt b/kotlin/lib/src/main/kotlin/models/EndpointPatch.kt new file mode 100644 index 000000000..142240ef8 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointPatch.kt @@ -0,0 +1,19 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.MaybeUnset +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointPatch( + val channels: MaybeUnset> = MaybeUnset.Unset, + val description: String? = null, + val disabled: Boolean? = null, + val filterTypes: MaybeUnset> = MaybeUnset.Unset, + val metadata: Map? = null, + val rateLimit: MaybeUnset = MaybeUnset.Unset, + val secret: MaybeUnset = MaybeUnset.Unset, + val uid: MaybeUnset = MaybeUnset.Unset, + val url: String? = null, + val version: UShort? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointSecretOut.kt b/kotlin/lib/src/main/kotlin/models/EndpointSecretOut.kt new file mode 100644 index 000000000..3b153042c --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointSecretOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EndpointSecretOut(val key: String) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointSecretRotateIn.kt b/kotlin/lib/src/main/kotlin/models/EndpointSecretRotateIn.kt new file mode 100644 index 000000000..d6a6f4a88 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointSecretRotateIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EndpointSecretRotateIn(val key: String? = null) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointStats.kt b/kotlin/lib/src/main/kotlin/models/EndpointStats.kt new file mode 100644 index 000000000..a4318b881 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointStats.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointStats(val fail: Long, val pending: Long, val sending: Long, val success: Long) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointTransformationIn.kt b/kotlin/lib/src/main/kotlin/models/EndpointTransformationIn.kt new file mode 100644 index 000000000..2e41bc598 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointTransformationIn.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointTransformationIn(val code: String? = null, val enabled: Boolean? = null) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointTransformationOut.kt b/kotlin/lib/src/main/kotlin/models/EndpointTransformationOut.kt new file mode 100644 index 000000000..ea53091c1 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointTransformationOut.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointTransformationOut(val code: String? = null, val enabled: Boolean? = null) diff --git a/kotlin/lib/src/main/kotlin/models/EndpointUpdate.kt b/kotlin/lib/src/main/kotlin/models/EndpointUpdate.kt new file mode 100644 index 000000000..cd6a44f69 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EndpointUpdate.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class EndpointUpdate( + val channels: Set? = null, + val description: String? = null, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val metadata: Map? = null, + val rateLimit: UShort? = null, + val uid: String? = null, + val url: String, + val version: UShort? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EnvironmentIn.kt b/kotlin/lib/src/main/kotlin/models/EnvironmentIn.kt new file mode 100644 index 000000000..e0a2bee72 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EnvironmentIn.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EnvironmentIn( + val eventTypes: List? = null, + @Serializable(with = StringAnyMapSerializer::class) val settings: Map? = null, + val transformationTemplates: List? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EnvironmentOut.kt b/kotlin/lib/src/main/kotlin/models/EnvironmentOut.kt new file mode 100644 index 000000000..81b64f842 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EnvironmentOut.kt @@ -0,0 +1,15 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class EnvironmentOut( + val createdAt: Instant, + val eventTypes: List, + @Serializable(with = StringAnyMapSerializer::class) val settings: Map? = null, + val transformationTemplates: List, + val version: Long? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventExampleIn.kt b/kotlin/lib/src/main/kotlin/models/EventExampleIn.kt new file mode 100644 index 000000000..2288bbe98 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventExampleIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EventExampleIn(val eventType: String, val exampleIndex: ULong? = null) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeFromOpenApi.kt b/kotlin/lib/src/main/kotlin/models/EventTypeFromOpenApi.kt new file mode 100644 index 000000000..8e74b5fe6 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeFromOpenApi.kt @@ -0,0 +1,15 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeFromOpenApi( + val deprecated: Boolean, + val description: String, + val featureFlag: String? = null, + val groupName: String? = null, + val name: String, + @Serializable(with = StringAnyMapSerializer::class) val schemas: Map? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiIn.kt b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiIn.kt new file mode 100644 index 000000000..344224837 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiIn.kt @@ -0,0 +1,13 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeImportOpenApiIn( + val dryRun: Boolean? = null, + val replaceAll: Boolean? = null, + @Serializable(with = StringAnyMapSerializer::class) val spec: Map? = null, + val specRaw: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOut.kt b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOut.kt new file mode 100644 index 000000000..2c8dcafd5 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class EventTypeImportOpenApiOut(val data: EventTypeImportOpenApiOutData) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOutData.kt b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOutData.kt new file mode 100644 index 000000000..e9cf83e02 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeImportOpenApiOutData.kt @@ -0,0 +1,11 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeImportOpenApiOutData( + val modified: List, + @SerialName("to_modify") val toModify: List? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeIn.kt b/kotlin/lib/src/main/kotlin/models/EventTypeIn.kt new file mode 100644 index 000000000..ef5f368d0 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeIn.kt @@ -0,0 +1,16 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeIn( + val archived: Boolean? = null, + val deprecated: Boolean? = null, + val description: String, + val featureFlag: String? = null, + val groupName: String? = null, + val name: String, + @Serializable(with = StringAnyMapSerializer::class) val schemas: Map? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeOut.kt b/kotlin/lib/src/main/kotlin/models/EventTypeOut.kt new file mode 100644 index 000000000..a91b555f3 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeOut.kt @@ -0,0 +1,19 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeOut( + val archived: Boolean? = null, + val createdAt: Instant, + val deprecated: Boolean, + val description: String, + val featureFlag: String? = null, + val groupName: String? = null, + val name: String, + @Serializable(with = StringAnyMapSerializer::class) val schemas: Map? = null, + val updatedAt: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypePatch.kt b/kotlin/lib/src/main/kotlin/models/EventTypePatch.kt new file mode 100644 index 000000000..473bac896 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypePatch.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.MaybeUnset +import com.svix.kotlin.MaybeUnsetStringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypePatch( + val archived: Boolean? = null, + val deprecated: Boolean? = null, + val description: String? = null, + val featureFlag: MaybeUnset = MaybeUnset.Unset, + val groupName: MaybeUnset = MaybeUnset.Unset, + @Serializable(with = MaybeUnsetStringAnyMapSerializer::class) + val schemas: MaybeUnset> = MaybeUnset.Unset, +) diff --git a/kotlin/lib/src/main/kotlin/models/EventTypeUpdate.kt b/kotlin/lib/src/main/kotlin/models/EventTypeUpdate.kt new file mode 100644 index 000000000..edf7ed91d --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/EventTypeUpdate.kt @@ -0,0 +1,15 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class EventTypeUpdate( + val archived: Boolean? = null, + val deprecated: Boolean? = null, + val description: String, + val featureFlag: String? = null, + val groupName: String? = null, + @Serializable(with = StringAnyMapSerializer::class) val schemas: Map? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ExpungAllContentsOut.kt b/kotlin/lib/src/main/kotlin/models/ExpungAllContentsOut.kt new file mode 100644 index 000000000..0e3c0c30d --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ExpungAllContentsOut.kt @@ -0,0 +1,11 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ExpungAllContentsOut( + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, +) diff --git a/kotlin/lib/src/main/kotlin/models/IntegrationIn.kt b/kotlin/lib/src/main/kotlin/models/IntegrationIn.kt new file mode 100644 index 000000000..7d8ff5e1c --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/IntegrationIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class IntegrationIn(val featureFlags: Set? = null, val name: String) diff --git a/kotlin/lib/src/main/kotlin/models/IntegrationKeyOut.kt b/kotlin/lib/src/main/kotlin/models/IntegrationKeyOut.kt new file mode 100644 index 000000000..c65d62177 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/IntegrationKeyOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class IntegrationKeyOut(val key: String) diff --git a/kotlin/lib/src/main/kotlin/models/IntegrationOut.kt b/kotlin/lib/src/main/kotlin/models/IntegrationOut.kt new file mode 100644 index 000000000..1cf590c6d --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/IntegrationOut.kt @@ -0,0 +1,14 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class IntegrationOut( + val createdAt: Instant, + val featureFlags: Set? = null, + val id: String, + val name: String, + val updatedAt: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/IntegrationUpdate.kt b/kotlin/lib/src/main/kotlin/models/IntegrationUpdate.kt new file mode 100644 index 000000000..0efe711cc --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/IntegrationUpdate.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class IntegrationUpdate(val featureFlags: Set? = null, val name: String) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseApplicationOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseApplicationOut.kt new file mode 100644 index 000000000..ce1bf7b36 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseApplicationOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseApplicationOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseBackgroundTaskOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseBackgroundTaskOut.kt new file mode 100644 index 000000000..57aa7bbfd --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseBackgroundTaskOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseBackgroundTaskOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseEndpointMessageOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseEndpointMessageOut.kt new file mode 100644 index 000000000..58c230cd7 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseEndpointMessageOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseEndpointMessageOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseEndpointOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseEndpointOut.kt new file mode 100644 index 000000000..b28e38c44 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseEndpointOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseEndpointOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseEventTypeOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseEventTypeOut.kt new file mode 100644 index 000000000..329708c16 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseEventTypeOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseEventTypeOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseIntegrationOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseIntegrationOut.kt new file mode 100644 index 000000000..3aa5c95b6 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseIntegrationOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseIntegrationOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseMessageAttemptOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseMessageAttemptOut.kt new file mode 100644 index 000000000..c5c2063fe --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseMessageAttemptOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseMessageAttemptOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseMessageEndpointOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseMessageEndpointOut.kt new file mode 100644 index 000000000..aeb5c5872 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseMessageEndpointOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseMessageEndpointOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseMessageOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseMessageOut.kt new file mode 100644 index 000000000..68f9e9485 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseMessageOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseMessageOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/ListResponseOperationalWebhookEndpointOut.kt b/kotlin/lib/src/main/kotlin/models/ListResponseOperationalWebhookEndpointOut.kt new file mode 100644 index 000000000..9d1c46991 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ListResponseOperationalWebhookEndpointOut.kt @@ -0,0 +1,12 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ListResponseOperationalWebhookEndpointOut( + val data: List, + val done: Boolean, + val iterator: String? = null, + val prevIterator: String? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/MessageAttemptOut.kt b/kotlin/lib/src/main/kotlin/models/MessageAttemptOut.kt new file mode 100644 index 000000000..9afda1b23 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageAttemptOut.kt @@ -0,0 +1,20 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class MessageAttemptOut( + val endpointId: String, + val id: String, + val msg: MessageOut? = null, + val msgId: String, + val response: String, + val responseDurationMs: Long, + val responseStatusCode: Short, + val status: MessageStatus, + val timestamp: Instant, + val triggerType: MessageAttemptTriggerType, + val url: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/MessageAttemptTriggerType.kt b/kotlin/lib/src/main/kotlin/models/MessageAttemptTriggerType.kt new file mode 100644 index 000000000..f4863fc9f --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageAttemptTriggerType.kt @@ -0,0 +1,49 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable(with = MessageAttemptTriggerTypeSerializer::class) +enum class MessageAttemptTriggerType : ToQueryParam { + SCHEDULED, + MANUAL; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} + +object MessageAttemptTriggerTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor( + "com.svix.kotlin.models.MessageAttemptTriggerTypeSerializer", + PrimitiveKind.LONG, + ) + + override fun serialize(encoder: Encoder, value: MessageAttemptTriggerType) { + val vAsLong = + when (value) { + MessageAttemptTriggerType.SCHEDULED -> 0L + MessageAttemptTriggerType.MANUAL -> 1L + } + encoder.encodeLong(vAsLong) + } + + override fun deserialize(decoder: Decoder): MessageAttemptTriggerType { + return when (val vAsLong = decoder.decodeLong()) { + 0L -> MessageAttemptTriggerType.SCHEDULED + 1L -> MessageAttemptTriggerType.MANUAL + else -> { + throw SerializationException( + "$vAsLong is not a valid value for MessageAttemptTriggerType" + ) + } + } + } +} diff --git a/kotlin/lib/src/main/kotlin/models/MessageEndpointOut.kt b/kotlin/lib/src/main/kotlin/models/MessageEndpointOut.kt new file mode 100644 index 000000000..937c788c8 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageEndpointOut.kt @@ -0,0 +1,22 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class MessageEndpointOut( + val channels: Set? = null, + val createdAt: Instant, + val description: String, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val id: String, + val nextAttempt: Instant? = null, + val rateLimit: UShort? = null, + val status: MessageStatus, + val uid: String? = null, + val updatedAt: Instant, + val url: String, + val version: Int, +) diff --git a/kotlin/lib/src/main/kotlin/models/MessageIn.kt b/kotlin/lib/src/main/kotlin/models/MessageIn.kt new file mode 100644 index 000000000..5575f2a82 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageIn.kt @@ -0,0 +1,19 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.serialization.Serializable + +@Serializable +data class MessageIn( + val application: ApplicationIn? = null, + val channels: Set? = null, + val eventId: String? = null, + val eventType: String, + @Serializable(with = StringAnyMapSerializer::class) val payload: Map, + val payloadRetentionHours: Long? = null, + val payloadRetentionPeriod: Long? = null, + val tags: Set? = null, + @Serializable(with = StringAnyMapSerializer::class) + val transformationsParams: Map? = null, +) diff --git a/kotlin/lib/src/main/kotlin/models/MessageOut.kt b/kotlin/lib/src/main/kotlin/models/MessageOut.kt new file mode 100644 index 000000000..5e572e506 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageOut.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.StringAnyMapSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class MessageOut( + val channels: Set? = null, + val eventId: String? = null, + val eventType: String, + val id: String, + @Serializable(with = StringAnyMapSerializer::class) val payload: Map, + val tags: Set? = null, + val timestamp: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/MessageStatus.kt b/kotlin/lib/src/main/kotlin/models/MessageStatus.kt new file mode 100644 index 000000000..6ae9a7115 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/MessageStatus.kt @@ -0,0 +1,53 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable(with = MessageStatusSerializer::class) +enum class MessageStatus : ToQueryParam { + SUCCESS, + PENDING, + FAIL, + SENDING; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} + +object MessageStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor( + "com.svix.kotlin.models.MessageStatusSerializer", + PrimitiveKind.LONG, + ) + + override fun serialize(encoder: Encoder, value: MessageStatus) { + val vAsLong = + when (value) { + MessageStatus.SUCCESS -> 0L + MessageStatus.PENDING -> 1L + MessageStatus.FAIL -> 2L + MessageStatus.SENDING -> 3L + } + encoder.encodeLong(vAsLong) + } + + override fun deserialize(decoder: Decoder): MessageStatus { + return when (val vAsLong = decoder.decodeLong()) { + 0L -> MessageStatus.SUCCESS + 1L -> MessageStatus.PENDING + 2L -> MessageStatus.FAIL + 3L -> MessageStatus.SENDING + else -> { + throw SerializationException("$vAsLong is not a valid value for MessageStatus") + } + } + } +} diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersIn.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersIn.kt new file mode 100644 index 000000000..275a13102 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class OperationalWebhookEndpointHeadersIn(val headers: Map) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersOut.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersOut.kt new file mode 100644 index 000000000..41afe4171 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointHeadersOut.kt @@ -0,0 +1,10 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class OperationalWebhookEndpointHeadersOut( + val headers: Map, + val sensitive: Set, +) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointIn.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointIn.kt new file mode 100644 index 000000000..31deb534f --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointIn.kt @@ -0,0 +1,16 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class OperationalWebhookEndpointIn( + val description: String? = null, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val metadata: Map? = null, + val rateLimit: UShort? = null, + val secret: String? = null, + val uid: String? = null, + val url: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointOut.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointOut.kt new file mode 100644 index 000000000..30d4bd941 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointOut.kt @@ -0,0 +1,19 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class OperationalWebhookEndpointOut( + val createdAt: Instant, + val description: String, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val id: String, + val metadata: Map, + val rateLimit: UShort? = null, + val uid: String? = null, + val updatedAt: Instant, + val url: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretIn.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretIn.kt new file mode 100644 index 000000000..e3f941e95 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretIn.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class OperationalWebhookEndpointSecretIn(val key: String? = null) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretOut.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretOut.kt new file mode 100644 index 000000000..9e05139cb --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointSecretOut.kt @@ -0,0 +1,6 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable data class OperationalWebhookEndpointSecretOut(val key: String) diff --git a/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointUpdate.kt b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointUpdate.kt new file mode 100644 index 000000000..66d0ffd41 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/OperationalWebhookEndpointUpdate.kt @@ -0,0 +1,15 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class OperationalWebhookEndpointUpdate( + val description: String? = null, + val disabled: Boolean? = null, + val filterTypes: Set? = null, + val metadata: Map? = null, + val rateLimit: UShort? = null, + val uid: String? = null, + val url: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/Ordering.kt b/kotlin/lib/src/main/kotlin/models/Ordering.kt new file mode 100644 index 000000000..e9e4c3bd2 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/Ordering.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +enum class Ordering : ToQueryParam { + @SerialName("ascending") ASCENDING, + @SerialName("descending") DESCENDING; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} diff --git a/kotlin/lib/src/main/kotlin/models/RecoverIn.kt b/kotlin/lib/src/main/kotlin/models/RecoverIn.kt new file mode 100644 index 000000000..430071701 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/RecoverIn.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable data class RecoverIn(val since: Instant, val until: Instant? = null) diff --git a/kotlin/lib/src/main/kotlin/models/RecoverOut.kt b/kotlin/lib/src/main/kotlin/models/RecoverOut.kt new file mode 100644 index 000000000..3da31f813 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/RecoverOut.kt @@ -0,0 +1,11 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class RecoverOut( + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, +) diff --git a/kotlin/lib/src/main/kotlin/models/ReplayIn.kt b/kotlin/lib/src/main/kotlin/models/ReplayIn.kt new file mode 100644 index 000000000..eb8d6092d --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ReplayIn.kt @@ -0,0 +1,7 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable data class ReplayIn(val since: Instant, val until: Instant? = null) diff --git a/kotlin/lib/src/main/kotlin/models/ReplayOut.kt b/kotlin/lib/src/main/kotlin/models/ReplayOut.kt new file mode 100644 index 000000000..44a8cc7e1 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/ReplayOut.kt @@ -0,0 +1,11 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ReplayOut( + val id: String, + val status: BackgroundTaskStatus, + val task: BackgroundTaskType, +) diff --git a/kotlin/lib/src/main/kotlin/models/StatusCodeClass.kt b/kotlin/lib/src/main/kotlin/models/StatusCodeClass.kt new file mode 100644 index 000000000..a14a63ec6 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/StatusCodeClass.kt @@ -0,0 +1,59 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable(with = StatusCodeClassSerializer::class) +enum class StatusCodeClass : ToQueryParam { + CODE_NONE, + CODE1XX, + CODE2XX, + CODE3XX, + CODE4XX, + CODE5XX; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} + +object StatusCodeClassSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor( + "com.svix.kotlin.models.StatusCodeClassSerializer", + PrimitiveKind.LONG, + ) + + override fun serialize(encoder: Encoder, value: StatusCodeClass) { + val vAsLong = + when (value) { + StatusCodeClass.CODE_NONE -> 0L + StatusCodeClass.CODE1XX -> 100L + StatusCodeClass.CODE2XX -> 200L + StatusCodeClass.CODE3XX -> 300L + StatusCodeClass.CODE4XX -> 400L + StatusCodeClass.CODE5XX -> 500L + } + encoder.encodeLong(vAsLong) + } + + override fun deserialize(decoder: Decoder): StatusCodeClass { + return when (val vAsLong = decoder.decodeLong()) { + 0L -> StatusCodeClass.CODE_NONE + 100L -> StatusCodeClass.CODE1XX + 200L -> StatusCodeClass.CODE2XX + 300L -> StatusCodeClass.CODE3XX + 400L -> StatusCodeClass.CODE4XX + 500L -> StatusCodeClass.CODE5XX + else -> { + throw SerializationException("$vAsLong is not a valid value for StatusCodeClass") + } + } + } +} diff --git a/kotlin/lib/src/main/kotlin/models/TemplateIn.kt b/kotlin/lib/src/main/kotlin/models/TemplateIn.kt new file mode 100644 index 000000000..c2ed29764 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/TemplateIn.kt @@ -0,0 +1,17 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.serialization.Serializable + +@Serializable +data class TemplateIn( + val description: String? = null, + val featureFlag: String? = null, + val filterTypes: Set? = null, + val instructions: String? = null, + val instructionsLink: String? = null, + val kind: TransformationTemplateKind? = null, + val logo: String, + val name: String, + val transformation: String, +) diff --git a/kotlin/lib/src/main/kotlin/models/TemplateOut.kt b/kotlin/lib/src/main/kotlin/models/TemplateOut.kt new file mode 100644 index 000000000..910e3937a --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/TemplateOut.kt @@ -0,0 +1,22 @@ +// This file is @generated +package com.svix.kotlin.models + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class TemplateOut( + val createdAt: Instant, + val description: String, + val featureFlag: String? = null, + val filterTypes: Set? = null, + val id: String, + val instructions: String, + val instructionsLink: String? = null, + val kind: TransformationTemplateKind, + val logo: String, + val name: String, + val orgId: String, + val transformation: String, + val updatedAt: Instant, +) diff --git a/kotlin/lib/src/main/kotlin/models/TransformationTemplateKind.kt b/kotlin/lib/src/main/kotlin/models/TransformationTemplateKind.kt new file mode 100644 index 000000000..0994b4287 --- /dev/null +++ b/kotlin/lib/src/main/kotlin/models/TransformationTemplateKind.kt @@ -0,0 +1,27 @@ +// This file is @generated +package com.svix.kotlin.models + +import com.svix.kotlin.ToQueryParam +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +enum class TransformationTemplateKind : ToQueryParam { + @SerialName("Custom") CUSTOM, + @SerialName("CustomerIO") CUSTOMER_IO, + @SerialName("Discord") DISCORD, + @SerialName("Hubspot") HUBSPOT, + @SerialName("Inngest") INNGEST, + @SerialName("Salesforce") SALESFORCE, + @SerialName("Segment") SEGMENT, + @SerialName("Slack") SLACK, + @SerialName("Teams") TEAMS, + @SerialName("TriggerDev") TRIGGER_DEV, + @SerialName("Windmill") WINDMILL, + @SerialName("Zapier") ZAPIER; + + override fun toQueryParam() = Json.encodeToJsonElement(this).jsonPrimitive.content +} diff --git a/kotlin/lib/src/test/com/svix/kotlin/BasicTest.kt b/kotlin/lib/src/test/com/svix/kotlin/BasicTest.kt index 0b2956f63..89a285e12 100644 --- a/kotlin/lib/src/test/com/svix/kotlin/BasicTest.kt +++ b/kotlin/lib/src/test/com/svix/kotlin/BasicTest.kt @@ -6,7 +6,6 @@ import com.svix.kotlin.models.EndpointIn import com.svix.kotlin.models.EndpointPatch import com.svix.kotlin.models.EventTypeIn import com.svix.kotlin.models.MessageIn -import java.net.URI import kotlin.test.Test import kotlin.test.assertEquals import kotlinx.coroutines.runBlocking @@ -80,10 +79,7 @@ class BasicTest { val epOut = svix.endpoint.create( appOut.id, - EndpointIn( - url = URI("https://example.svix.com"), - channels = setOf("ch0", "ch1"), - ), + EndpointIn(url = "https://example.svix.com", channels = setOf("ch0", "ch1")), ) assertEquals(setOf("ch0", "ch1"), epOut.channels, "initial ep should have 2 channels") assertEquals(0, epOut.filterTypes?.size ?: 0, "initial ep should have 0 filter types") @@ -91,7 +87,9 @@ class BasicTest { svix.endpoint.patch( appOut.id, epOut.id, - EndpointPatch(filterTypes = setOf("event.started", "event.ended")), + EndpointPatch( + filterTypes = MaybeUnset.Present(setOf("event.started", "event.ended")) + ), ) assertEquals( setOf("ch0", "ch1"), @@ -103,6 +101,7 @@ class BasicTest { epPatched.filterTypes, "patched ep should have 2 filter types", ) + svix.application.delete(appOut.id) } } } diff --git a/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt b/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt new file mode 100644 index 000000000..9e098ae7e --- /dev/null +++ b/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt @@ -0,0 +1,383 @@ +package com.svix.kotlin + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options +import com.svix.kotlin.exceptions.ApiException +import com.svix.kotlin.models.AppUsageStatsIn +import com.svix.kotlin.models.EndpointPatch +import com.svix.kotlin.models.EventTypeIn +import com.svix.kotlin.models.EventTypePatch +import com.svix.kotlin.models.MessageIn +import com.svix.kotlin.models.Ordering +import kotlin.test.assertEquals +import kotlinx.coroutines.runBlocking +import kotlinx.datetime.Instant +import org.junit.jupiter.api.* + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class WiremockTests { + + private val wireMockServer = + WireMockServer(options().port(0).withRootDirectory("lib/src/test/resources")) + + @BeforeAll + fun beforeAll() { + wireMockServer.start() + } + + @AfterAll + fun afterAll() { + wireMockServer.stop() + } + + @BeforeEach + fun beforeEach() { + wireMockServer.resetAll() + } + + private fun testClient(): Svix { + return Svix("testtk_asd12.eu", SvixOptions(this.wireMockServer.baseUrl())) + } + + @Test + fun testEnumQueryParamEncodedCorrectly() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app?.*")) + .willReturn(WireMock.ok().withBodyFile("ListResponseApplicationOut.json")) + ) + runBlocking { + svx.application.list( + ApplicationListOptions( + limit = 5UL, + order = Ordering.ASCENDING, + iterator = "cool-string-!@#$%^", + ) + ) + } + wireMockServer.verify( + 1, + getRequestedFor( + urlEqualTo( + "/api/v1/app?limit=5&iterator=cool-string-%21%40%23%24%25%5E&order=ascending" + ) + ), + ) + } + + @Test + fun testSetQueryParamEncodedCorrectly() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app/app_asd123/msg.*")) + .willReturn(WireMock.ok().withBodyFile("ListResponseMessageOut.json")) + ) + runBlocking { + svx.message.list( + "app_asd123", + MessageListOptions(eventTypes = setOf("key3", "key4", "key1")), + ) + } + wireMockServer.verify( + 1, + getRequestedFor(urlEqualTo("/api/v1/app/app_asd123/msg?event_types=key1%2Ckey3%2Ckey4")), + ) + } + + @Test + fun testInstantAndBoolQueryParamEncodedCorrectly() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app/app_asd123/msg.*")) + .willReturn(WireMock.ok().withBodyFile("ListResponseMessageOut.json")) + ) + runBlocking { + svx.message.list( + "app_asd123", + MessageListOptions( + before = Instant.fromEpochSeconds(1739399072, 864755000), + withContent = true, + ), + ) + } + wireMockServer.verify( + 1, + getRequestedFor( + urlEqualTo( + "/api/v1/app/app_asd123/msg?before=2025-02-12T22%3A24%3A32.864755Z&with_content=true" + ) + ), + ) + } + + @Test + fun maybeUnsetIsCorrectlySerialized() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.patch(urlMatching("/api/v1/app/ap/endpoint/endp")) + .willReturn(WireMock.ok().withBodyFile("EndpointOut.json")) + ) + runBlocking { + // MaybeUnset.Present + svx.endpoint.patch( + "ap", + "endp", + EndpointPatch(filterTypes = MaybeUnset.Present(setOf("ft1", "ft2"))), + ) + // MaybeUnset.Null + svx.endpoint.patch("ap", "endp", EndpointPatch(filterTypes = MaybeUnset.Null)) + // MaybeUnset.Unset + svx.endpoint.patch( + "ap", + "endp", + EndpointPatch(filterTypes = MaybeUnset.Unset, version = 42u), + ) + } + // MaybeUnset.Present + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/app/ap/endpoint/endp")) + .withRequestBody(equalTo("""{"filterTypes":["ft1","ft2"]}""")), + ) + // MaybeUnset.Null + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/app/ap/endpoint/endp")) + .withRequestBody(equalTo("""{"filterTypes":null}""")), + ) + // MaybeUnset.Unset + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/app/ap/endpoint/endp")) + .withRequestBody(equalTo("""{"version":42}""")), + ) + } + + @Test + fun deeplyNestedMapStringAnyIsCorrectlySerialized() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.post(urlMatching("/api/v1/event-type")) + .willReturn(WireMock.ok().withBodyFile("EventTypeOut.json")) + ) + val nestedList = listOf("l1", listOf("l2", listOf("l3"))) + val sampleMap = + mapOf( + "str_key" to "val", + "int_key" to 1, + "bool_key" to false, + "list_key" to listOf("val1", "val2", nestedList), + "map_key" to mapOf("key" to "val", "list" to nestedList), + ) + val nestedMap = + mapOf( + "l1" to mapOf("l2" to mapOf("l3" to sampleMap, "more_list" to nestedList)), + "m2" to sampleMap, + ) + + runBlocking { + svx.eventType.create( + EventTypeIn(description = "", name = "", schemas = mapOf("nestedMap" to nestedMap)) + ) + } + wireMockServer.verify( + 1, + postRequestedFor(urlEqualTo("/api/v1/event-type")) + .withRequestBody( + equalToJson( + wireMockServer.options.stores.filesBlobStore + .get("VeryNestedMap.json") + .get() + .toString(Charsets.UTF_8) + ) + ), + ) + } + + @Test + fun maybeUnsetOnMapStringAnyIsCorrectlySerialized() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.patch(urlMatching("/api/v1/event-type/event")) + .willReturn(WireMock.ok().withBodyFile("EventTypeOut.json")) + ) + runBlocking { + // MaybeUnset.Present + svx.eventType.patch( + "event", + EventTypePatch( + schemas = + MaybeUnset.Present( + mapOf( + "str_key" to "val", + "int_key" to 1, + "bool_key" to false, + "list_key" to listOf("val1", "val2"), + "map_key" to mapOf("key" to "val"), + ) + ) + ), + ) + // MaybeUnset.Null + svx.eventType.patch("event", EventTypePatch(schemas = MaybeUnset.Null)) + // MaybeUnset.Unset + svx.eventType.patch( + "event", + EventTypePatch(description = "42", schemas = MaybeUnset.Unset), + ) + } + // MaybeUnset.Present + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/event-type/event")) + .withRequestBody( + equalTo( + """{"schemas":{"str_key":"val","int_key":1,"bool_key":false,"list_key":["val1","val2"],"map_key":{"key":"val"}}}""" + ) + ), + ) + // MaybeUnset.Null + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/event-type/event")) + .withRequestBody(equalTo("""{"schemas":null}""")), + ) + // MaybeUnset.Unset + wireMockServer.verify( + 1, + patchRequestedFor(urlEqualTo("/api/v1/event-type/event")) + .withRequestBody(equalTo("""{"description":"42"}""")), + ) + } + + @Test + fun optionsHeadersAreSent() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.post(urlMatching("/api/v1/app/ap/msg")) + .willReturn(WireMock.ok().withBodyFile("MessageOut.json")) + ) + runBlocking { + svx.message.create( + "ap", + MessageIn(eventType = "event.test", payload = mapOf("key" to "val")), + MessageCreateOptions(idempotencyKey = "key123"), + ) + } + wireMockServer.verify( + 1, + postRequestedFor(urlEqualTo("/api/v1/app/ap/msg")) + .withHeader("idempotency-key", equalTo("key123")), + ) + } + + @Test + fun userAgentHeaderIsSent() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app/ap/msg")) + .willReturn(WireMock.ok().withBodyFile("ListResponseMessageOut.json")) + ) + runBlocking { svx.message.list("ap") } + wireMockServer.verify( + 1, + getRequestedFor(urlEqualTo("/api/v1/app/ap/msg")) + .withHeader("User-Agent", matching("svix-libs/.*/kotlin")), + ) + } + + @Test + fun defaultRetryStatusCode500() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app/ap/msg")) + .willReturn(WireMock.status(500).withBodyFile("ListResponseMessageOut.json")) + ) + runBlocking { + try { + svx.message.list("ap") + } catch (e: ApiException) { + assertEquals(500, e.statusCode) + } + } + + wireMockServer.verify( + 1, + getRequestedFor(urlEqualTo("/api/v1/app/ap/msg")) + // first request does not have `svix-retry-count` header + .withHeader("svix-retry-count", absent()), + ) + + // check the svix-retry-count are set correctly + for (retryCount in 1..3) { + wireMockServer.verify( + 1, + getRequestedFor(urlEqualTo("/api/v1/app/ap/msg")) + .withHeader("svix-retry-count", equalTo("$retryCount")), + ) + } + } + + @Test + fun instantSerializedCorrectly() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app/ap/msg/msg_asd123")) + // this file includes a string timestamp `2025-02-12T22:24:32.864755Z` + .willReturn(WireMock.ok().withBodyFile("MessageOut.json")) + ) + runBlocking { + val res = svx.message.get("ap", "msg_asd123") + assertEquals(Instant.fromEpochSeconds(1739399072, 864755000), res.timestamp) + } + + wireMockServer.verify(1, getRequestedFor(urlEqualTo("/api/v1/app/ap/msg/msg_asd123"))) + } + + @Test + fun instantDeserializedCorrectly() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.post(urlMatching("/api/v1/stats/usage/app")) + .willReturn( + WireMock.ok() + .withBody( + """{"unresolvedAppIds":["unique-identifier"],"id":"qtask_1srOrx2ZWZBpBUvZwXKQmoEYga2","status":"running","task":"endpoint.replay"}""" + ) + ) + ) + runBlocking { + svx.statistics.aggregateAppStats( + AppUsageStatsIn( + since = Instant.fromEpochSeconds(1739399072, 864755000), + until = Instant.fromEpochSeconds(1739399072, 864755000), + ) + ) + } + + wireMockServer.verify( + 1, + postRequestedFor(urlEqualTo("/api/v1/stats/usage/app")) + .withRequestBody( + equalTo( + """{"since":"2025-02-12T22:24:32.864755Z","until":"2025-02-12T22:24:32.864755Z"}""" + ) + ), + ) + } + + @Test + fun listResponseOutCorrectlyDeserialized() { + val svx = testClient() + wireMockServer.stubFor( + WireMock.get(urlMatching("/api/v1/app")) + .willReturn( + WireMock.ok() + .withBody("""{"data":[],"iterator":null,"prevIterator":null,"done":true}""") + ) + ) + runBlocking { svx.application.list() } + } +} diff --git a/kotlin/lib/src/test/resources/__files/EndpointOut.json b/kotlin/lib/src/test/resources/__files/EndpointOut.json new file mode 100644 index 000000000..50ef7b1e6 --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/EndpointOut.json @@ -0,0 +1 @@ +{"id":"ep_1srOrx2ZWZBpBUvZwXKQmoEYga2","metadata":{"property1":"string","property2":"string"},"description":"string","rateLimit":0,"uid":"unique-identifier","url":"https://example.com/webhook/","version":1,"disabled":false,"filterTypes":["user.signup","user.deleted"],"channels":["project_123","group_2"],"createdAt":"2019-08-24T14:15:22Z","updatedAt":"2019-08-24T14:15:22Z" } \ No newline at end of file diff --git a/kotlin/lib/src/test/resources/__files/EventTypeOut.json b/kotlin/lib/src/test/resources/__files/EventTypeOut.json new file mode 100644 index 000000000..6342c8a20 --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/EventTypeOut.json @@ -0,0 +1 @@ +{"name":"user.signup","description":"A user has signed up","archived":false,"deprecated":true,"schemas":{"1":{"description":"An invoice was paid by a user","properties":{"invoiceId":{"description":"The invoice id","type":"string"},"userId":{"description":"The user id","type":"string"}},"required":["invoiceId","userId"],"title":"Invoice Paid Event","type":"object"}},"createdAt":"2019-08-24T14:15:22Z","updatedAt":"2019-08-24T14:15:22Z","featureFlag":"cool-new-feature","groupName":"user" } \ No newline at end of file diff --git a/kotlin/lib/src/test/resources/__files/ListResponseApplicationOut.json b/kotlin/lib/src/test/resources/__files/ListResponseApplicationOut.json new file mode 100644 index 000000000..256207b5b --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/ListResponseApplicationOut.json @@ -0,0 +1 @@ +{"data":[{"uid":"unique-identifier","name":"My first application","rateLimit":0,"id":"app_1srOrx2ZWZBpBUvZwXKQmoEYga2","createdAt":"2019-08-24T14:15:22Z","updatedAt":"2019-08-24T14:15:22Z","metadata":{"property1":"string","property2":"string"}}],"iterator":"iterator","prevIterator":"-iterator","done":true} \ No newline at end of file diff --git a/kotlin/lib/src/test/resources/__files/ListResponseMessageOut.json b/kotlin/lib/src/test/resources/__files/ListResponseMessageOut.json new file mode 100644 index 000000000..4e92f640f --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/ListResponseMessageOut.json @@ -0,0 +1 @@ +{"data":[{"eventId":"unique-identifier","eventType":"user.signup","payload":{"email":"test@example.com","type":"user.created","username":"test_user"},"channels":["project_123","group_2"],"id":"msg_1srOrx2ZWZBpBUvZwXKQmoEYga2","timestamp":"2019-08-24T14:15:22Z","tags":["project_1337"]}],"iterator":"iterator","prevIterator":"-iterator","done":true} \ No newline at end of file diff --git a/kotlin/lib/src/test/resources/__files/MessageOut.json b/kotlin/lib/src/test/resources/__files/MessageOut.json new file mode 100644 index 000000000..018de1bb0 --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/MessageOut.json @@ -0,0 +1 @@ +{"eventId":"unique-identifier","eventType":"user.signup","payload":{"email":"test@example.com","type":"user.created","username":"test_user"},"channels":["project_123","group_2"],"id":"msg_1srOrx2ZWZBpBUvZwXKQmoEYga2","timestamp":"2025-02-12T22:24:32.864755Z","tags":["project_1337"]} \ No newline at end of file diff --git a/kotlin/lib/src/test/resources/__files/VeryNestedMap.json b/kotlin/lib/src/test/resources/__files/VeryNestedMap.json new file mode 100644 index 000000000..428395fc3 --- /dev/null +++ b/kotlin/lib/src/test/resources/__files/VeryNestedMap.json @@ -0,0 +1,81 @@ +{ + "description": "", + "name": "", + "schemas": { + "nestedMap": { + "l1": { + "l2": { + "l3": { + "str_key": "val", + "int_key": 1, + "bool_key": false, + "list_key": [ + "val1", + "val2", + [ + "l1", + [ + "l2", + [ + "l3" + ] + ] + ] + ], + "map_key": { + "key": "val", + "list": [ + "l1", + [ + "l2", + [ + "l3" + ] + ] + ] + } + }, + "more_list": [ + "l1", + [ + "l2", + [ + "l3" + ] + ] + ] + } + }, + "m2": { + "str_key": "val", + "int_key": 1, + "bool_key": false, + "list_key": [ + "val1", + "val2", + [ + "l1", + [ + "l2", + [ + "l3" + ] + ] + ] + ], + "map_key": { + "key": "val", + "list": [ + "l1", + [ + "l2", + [ + "l3" + ] + ] + ] + } + } + } + } +} \ No newline at end of file diff --git a/kotlin/openapi-generator-config.json b/kotlin/openapi-generator-config.json deleted file mode 100644 index d92146d86..000000000 --- a/kotlin/openapi-generator-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "packageName": "com.svix.kotlin.internal", - "modelPackage": "com.svix.kotlin.models", - "useCoroutines": true -} diff --git a/kotlin/templates/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache b/kotlin/templates/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache deleted file mode 100644 index e3c4729f2..000000000 --- a/kotlin/templates/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache +++ /dev/null @@ -1,474 +0,0 @@ -package {{packageName}}.infrastructure - -{{#supportAndroidApiLevel25AndBelow}} -import android.os.Build -{{/supportAndroidApiLevel25AndBelow}} -import okhttp3.OkHttpClient -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.FormBody -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.ResponseBody -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.Request -import okhttp3.Headers -import okhttp3.Headers.Companion.toHeaders -import okhttp3.MultipartBody -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Response -import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter -import java.io.IOException -import java.net.URLConnection -{{^threetenbp}} -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.OffsetTime -{{/threetenbp}} -import java.util.Locale -import java.util.regex.Pattern -{{#useCoroutines}} -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine -{{/useCoroutines}} -{{#kotlinx_serialization}} -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -{{/kotlinx_serialization}} -{{#threetenbp}} -import org.threeten.bp.LocalDate -import org.threeten.bp.LocalDateTime -import org.threeten.bp.LocalTime -import org.threeten.bp.OffsetDateTime -import org.threeten.bp.OffsetTime -{{/threetenbp}} -{{#gson}} -import com.google.gson.reflect.TypeToken -{{/gson}} -{{#jackson}} -import com.fasterxml.jackson.core.type.TypeReference -{{/jackson}} -{{#moshi}} -import com.squareup.moshi.adapter -{{/moshi}} -import kotlinx.coroutines.delay -import kotlin.random.Random - -{{#nonPublicApi}}internal {{/nonPublicApi}}val EMPTY_REQUEST: RequestBody = ByteArray(0).toRequestBody() - -{{#nonPublicApi}}internal {{/nonPublicApi}}open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClient) { - {{#nonPublicApi}}internal {{/nonPublicApi}}companion object { - protected const val ContentType = "Content-Type" - protected const val Accept = "Accept" - protected const val Authorization = "Authorization" - protected const val JsonMediaType = "application/json" - protected const val FormDataMediaType = "multipart/form-data" - protected const val FormUrlEncMediaType = "application/x-www-form-urlencoded" - protected const val XmlMediaType = "application/xml" - protected const val UserAgent = "User-Agent" - protected const val OctetMediaType = "application/octet-stream" - - val apiKey: MutableMap = mutableMapOf() - val apiKeyPrefix: MutableMap = mutableMapOf() - var username: String? = null - var password: String? = null - const val baseUrlKey = "{{packageName}}.baseUrl" - - @JvmStatic - val defaultClient: OkHttpClient by lazy { - builder.build() - } - - @JvmStatic - val builder: OkHttpClient.Builder = OkHttpClient.Builder() - } - - var accessToken: String? = null - var userAgent: String? = null - var initialRetryDelayMillis = 50L - var numRetries: Int = 3 - - /** - * Guess Content-Type header from the given file (defaults to "application/octet-stream"). - * - * @param file The given file - * @return The guessed Content-Type - */ - protected fun guessContentTypeFromFile(file: File): String { - val contentType = URLConnection.guessContentTypeFromName(file.name) - return contentType ?: "application/octet-stream" - } - - protected inline fun requestBody(content: T, mediaType: String?): RequestBody = - when { - content is File -> content.asRequestBody((mediaType ?: guessContentTypeFromFile(content)).toMediaTypeOrNull()) - mediaType == FormDataMediaType -> - MultipartBody.Builder() - .setType(MultipartBody.FORM) - .apply { - // content's type *must* be Map> - @Suppress("UNCHECKED_CAST") - (content as Map>).forEach { (name, part) -> - if (part.body is File) { - val partHeaders = part.headers.toMutableMap() + - ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${part.body.name}\"") - val fileMediaType = guessContentTypeFromFile(part.body).toMediaTypeOrNull() - addPart( - partHeaders.toHeaders(), - part.body.asRequestBody(fileMediaType) - ) - } else { - val partHeaders = part.headers.toMutableMap() + - ("Content-Disposition" to "form-data; name=\"$name\"") - addPart( - partHeaders.toHeaders(), - parameterToString(part.body).toRequestBody(null) - ) - } - } - }.build() - mediaType == FormUrlEncMediaType -> { - FormBody.Builder().apply { - // content's type *must* be Map> - @Suppress("UNCHECKED_CAST") - (content as Map>).forEach { (name, part) -> - add(name, parameterToString(part.body)) - } - }.build() - } - mediaType == null || mediaType.startsWith("application/") && mediaType.endsWith("json") -> - if (content == null) { - // FIXME(onelson): double check that this is where we land on DELETE requests - // We initially had to patch the client for responses without a body. - // Ref: https://github.com/svix/svix-webhooks/pull/1124 - EMPTY_REQUEST - } else { - {{#moshi}} - Serializer.moshi.adapter(T::class.java).toJson(content) - {{/moshi}} - {{#gson}} - Serializer.gson.toJson(content, T::class.java) - {{/gson}} - {{#jackson}} - Serializer.jacksonObjectMapper.writeValueAsString(content) - {{/jackson}} - {{#kotlinx_serialization}} - Serializer.kotlinxSerializationJson.encodeToString(content) - {{/kotlinx_serialization}} - .toRequestBody((mediaType ?: JsonMediaType).toMediaTypeOrNull()) - } - mediaType == XmlMediaType -> throw UnsupportedOperationException("xml not currently supported.") - mediaType == OctetMediaType && content is ByteArray -> - content.toRequestBody(OctetMediaType.toMediaTypeOrNull()) - // TODO: this should be extended with other serializers - else -> throw UnsupportedOperationException("requestBody currently only supports JSON body, byte body and File body.") - } - - {{#moshi}} - @OptIn(ExperimentalStdlibApi::class) - {{/moshi}} - protected inline fun responseBody(response: Response, mediaType: String? = JsonMediaType): T? { - val body = response.body - if(body == null) { - return null - } else if (T::class.java == Unit::class.java) { - // No need to parse the body when we're not interested in the body - // Useful when API is returning other Content-Type - return null - } else if (T::class.java == File::class.java) { - // return tempFile - val contentDisposition = response.header("Content-Disposition") - - val fileName = if (contentDisposition != null) { - // Get filename from the Content-Disposition header. - val pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?") - val matcher = pattern.matcher(contentDisposition) - if (matcher.find()) { - matcher.group(1) - ?.replace(".*[/\\\\]", "") - ?.replace(";", "") - } else { - null - } - } else { - null - } - - var prefix: String? - val suffix: String? - if (fileName == null) { - prefix = "download" - suffix = "" - } else { - val pos = fileName.lastIndexOf(".") - if (pos == -1) { - prefix = fileName - suffix = null - } else { - prefix = fileName.substring(0, pos) - suffix = fileName.substring(pos) - } - // Files.createTempFile requires the prefix to be at least three characters long - if (prefix.length < 3) { - prefix = "download" - } - } - - {{^supportAndroidApiLevel25AndBelow}} - // Attention: if you are developing an android app that supports API Level 25 and bellow, please check flag supportAndroidApiLevel25AndBelow in https://openapi-generator.tech/docs/generators/kotlin#config-options - val tempFile = java.nio.file.Files.createTempFile(prefix, suffix).toFile() - {{/supportAndroidApiLevel25AndBelow}} - {{#supportAndroidApiLevel25AndBelow}} - val tempFile = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - java.nio.file.Files.createTempFile(prefix, suffix).toFile() - } else { - @Suppress("DEPRECATION") - createTempFile(prefix, suffix) - } - {{/supportAndroidApiLevel25AndBelow}} - tempFile.deleteOnExit() - body.byteStream().use { inputStream -> - tempFile.outputStream().use { tempFileOutputStream -> - inputStream.copyTo(tempFileOutputStream) - } - } - return tempFile as T - } - - return when { - mediaType == null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) -> { - val bodyContent = body.string() - if (bodyContent.isEmpty()) { - return null - } - {{#moshi}}Serializer.moshi.adapter().fromJson(bodyContent){{/moshi}}{{! - }}{{#gson}}Serializer.gson.fromJson(bodyContent, (object: TypeToken(){}).getType()){{/gson}}{{! - }}{{#jackson}}Serializer.jacksonObjectMapper.readValue(bodyContent, object: TypeReference() {}){{/jackson}}{{! - }}{{#kotlinx_serialization}}Serializer.kotlinxSerializationJson.decodeFromString(bodyContent){{/kotlinx_serialization}} - } - mediaType == OctetMediaType -> body.bytes() as? T - else -> throw UnsupportedOperationException("responseBody currently only supports JSON body.") - } - } - - {{#hasAuthMethods}} - protected fun updateAuthParams(requestConfig: RequestConfig) { - {{#authMethods}} - {{#isApiKey}} - {{#isKeyInHeader}} - if (requestConfig.headers["{{keyParamName}}"].isNullOrEmpty()) { - {{/isKeyInHeader}} - {{#isKeyInQuery}} - if (requestConfig.query["{{keyParamName}}"].isNullOrEmpty()) { - {{/isKeyInQuery}} - if (apiKey["{{keyParamName}}"] != null) { - if (apiKeyPrefix["{{keyParamName}}"] != null) { - {{#isKeyInHeader}} - requestConfig.headers["{{keyParamName}}"] = apiKeyPrefix["{{keyParamName}}"]!! + " " + apiKey["{{keyParamName}}"]!! - {{/isKeyInHeader}} - {{#isKeyInQuery}} - requestConfig.query["{{keyParamName}}"] = listOf(apiKeyPrefix["{{keyParamName}}"]!! + " " + apiKey["{{keyParamName}}"]!!) - {{/isKeyInQuery}} - } else { - {{#isKeyInHeader}} - requestConfig.headers["{{keyParamName}}"] = apiKey["{{keyParamName}}"]!! - {{/isKeyInHeader}} - {{#isKeyInQuery}} - requestConfig.query["{{keyParamName}}"] = listOf(apiKey["{{keyParamName}}"]!!) - {{/isKeyInQuery}} - } - } - {{#isKeyInQuery}} - } - {{/isKeyInQuery}} - {{#isKeyInHeader}} - } - {{/isKeyInHeader}} - {{/isApiKey}} - {{#isBasic}} - {{#isBasicBasic}} - if (requestConfig.headers[Authorization].isNullOrEmpty()) { - username?.let { username -> - password?.let { password -> - requestConfig.headers[Authorization] = okhttp3.Credentials.basic(username, password) - } - } - } - {{/isBasicBasic}} - {{#isBasicBearer}} - if (requestConfig.headers[Authorization].isNullOrEmpty()) { - accessToken?.let { accessToken -> - requestConfig.headers[Authorization] = "Bearer $accessToken" - } - } - {{/isBasicBearer}} - {{/isBasic}} - {{#isOAuth}} - if (requestConfig.headers[Authorization].isNullOrEmpty()) { - accessToken?.let { accessToken -> - requestConfig.headers[Authorization] = "Bearer $accessToken " - } - } - {{/isOAuth}} - {{/authMethods}} - } - {{/hasAuthMethods}} - - protected {{#useCoroutines}}suspend {{/useCoroutines}}inline fun request(requestConfig: RequestConfig): ApiResponse { - val httpUrl = baseUrl.toHttpUrlOrNull() ?: throw IllegalStateException("baseUrl is invalid.") - {{#hasAuthMethods}} - - // take authMethod from operation - updateAuthParams(requestConfig) - {{/hasAuthMethods}} - - // add user agent - if (requestConfig.headers[UserAgent].isNullOrEmpty()) { - userAgent?.let { userAgent -> - requestConfig.headers[UserAgent] = userAgent - } - } - - val url = httpUrl.newBuilder() - .addEncodedPathSegments(requestConfig.path.trimStart('/')) - .apply { - requestConfig.query.forEach { query -> - query.value.forEach { queryValue -> - addQueryParameter(query.key, queryValue) - } - } - }.build() - - // take content-type/accept from spec or set to default (application/json) if not defined - if (requestConfig.body != null && requestConfig.headers[ContentType].isNullOrEmpty()) { - requestConfig.headers[ContentType] = JsonMediaType - } - if (requestConfig.headers["svix-req-id"].isNullOrEmpty()) { - requestConfig.headers["svix-req-id"] = Math.abs(Random.nextBits(32)).toString() - } - if (requestConfig.headers[Accept].isNullOrEmpty()) { - requestConfig.headers[Accept] = JsonMediaType - } - val headers = requestConfig.headers - - if (headers[Accept].isNullOrEmpty()) { - throw kotlin.IllegalStateException("Missing Accept header. This is required.") - } - - val contentType = if (headers[ContentType] != null) { - // TODO: support multiple contentType options here. - (headers[ContentType] as String).substringBefore(";").lowercase(Locale.US) - } else { - null - } - - val request = when (requestConfig.method) { - RequestMethod.DELETE -> Request.Builder().url(url).delete(requestBody(requestConfig.body, contentType)) - RequestMethod.GET -> Request.Builder().url(url) - RequestMethod.HEAD -> Request.Builder().url(url).head() - RequestMethod.PATCH -> Request.Builder().url(url).patch(requestBody(requestConfig.body, contentType)) - RequestMethod.PUT -> Request.Builder().url(url).put(requestBody(requestConfig.body, contentType)) - RequestMethod.POST -> Request.Builder().url(url).post(requestBody(requestConfig.body, contentType)) - RequestMethod.OPTIONS -> Request.Builder().url(url).method("OPTIONS", null) - }.apply { - headers.forEach { header -> addHeader(header.key, header.value) } - }.build() - - - // FIXME(onelson): the upstream generator template has the below block for the useCoroutines=false case. - // More reading up on kotlin required before it's clear how to do a retry loop over a suspendable. - // For now, we'll do the non-coroutine thing. - // Ref: https://github.com/OpenAPITools/openapi-generator/blob/4145000dfebe7a9edea4555c8515383da7602458/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache#L363-L376 - var response = client.newCall(request).execute() - - var sleepTime = initialRetryDelayMillis - var retryCount = 0 - for (i in 0 until numRetries-1) { - if (response.isSuccessful || response.code < 500) { - break - } - response.close() - retryCount = retryCount.inc() - delay(sleepTime) - sleepTime = sleepTime * 2 - var newRequest = request.newBuilder().header("svix-retry-count", retryCount.toString()).build() - response = client.newCall(newRequest).execute() - } - - val accept = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.US) - - // TODO: handle specific mapping types. e.g. Map> - @Suppress("UNNECESSARY_SAFE_CALL") - return response.use { - when { - it.isRedirect -> Redirection( - it.code, - it.headers.toMultimap() - ) - it.isInformational -> Informational( - it.message, - it.code, - it.headers.toMultimap() - ) - it.isSuccessful -> Success( - responseBody(it, accept), - it.code, - it.headers.toMultimap() - ) - it.isClientError -> ClientError( - it.message, - it.body?.string(), - it.code, - it.headers.toMultimap() - ) - else -> ServerError( - it.message, - it.body?.string(), - it.code, - it.headers.toMultimap() - ) - } - } - } - - protected fun parameterToString(value: Any?): String = when (value) { - null -> "" - is Array<*> -> toMultiValue(value, "csv").toString() - is Iterable<*> -> toMultiValue(value, "csv").toString() - is OffsetDateTime, is OffsetTime, is LocalDateTime, is LocalDate, is LocalTime -> - parseDateToQueryString(value) - else -> value.toString() - } - - protected inline fun parseDateToQueryString(value : T): String { - {{#toJson}} - /* - .replace("\"", "") converts the json object string to an actual string for the query parameter. - The moshi or gson adapter allows a more generic solution instead of trying to use a native - formatter. It also easily allows to provide a simple way to define a custom date format pattern - inside a gson/moshi adapter. - */ - {{#moshi}} - return Serializer.moshi.adapter(T::class.java).toJson(value).replace("\"", "") - {{/moshi}} - {{#gson}} - return Serializer.gson.toJson(value, T::class.java).replace("\"", "") - {{/gson}} - {{#jackson}} - return Serializer.jacksonObjectMapper.writeValueAsString(value).replace("\"", "") - {{/jackson}} - {{#kotlinx_serialization}} - return Serializer.kotlinxSerializationJson.encodeToString(value).replace("\"", "") - {{/kotlinx_serialization}} - {{/toJson}} - {{^toJson}} - return value.toString() - {{/toJson}} - } -} diff --git a/regen_openapi.sh b/regen_openapi.sh index ad9138670..f21fbf827 100755 --- a/regen_openapi.sh +++ b/regen_openapi.sh @@ -57,8 +57,6 @@ yarn openapi-generator-cli generate -i .codegen-tmp/openapi.json -g typescript - yarn openapi-generator-cli generate -i .codegen-tmp/openapi.json -g java -o java/lib/generated/openapi -c java/openapi-generator-config.json -t java/templates -yarn openapi-generator-cli generate -i .codegen-tmp/openapi.json -g kotlin -o kotlin/lib/generated/openapi -c kotlin/openapi-generator-config.json -t kotlin/templates - yarn openapi-generator-cli generate -i .codegen-tmp/openapi.json -g ruby -o ruby -c ruby/openapi-generator-config.json -t ruby/templates rm -rf .codegen-tmp diff --git a/tools/bump_version.js b/tools/bump_version.js index e41514536..b0e7ec10f 100755 --- a/tools/bump_version.js +++ b/tools/bump_version.js @@ -30,7 +30,7 @@ const filesPaths = [ // Kotlin "kotlin/gradle.properties", "kotlin/README.md", - "kotlin/lib/src/main/kotlin/SvixOptions.kt", + "kotlin/lib/src/main/kotlin/Version.kt", // Python "python/svix/__init__.py", // Ruby