diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f668ddf3..d0b5e4c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: build: timeout-minutes: 15 name: build + permissions: + contents: read + id-token: write runs-on: ${{ github.repository == 'stainless-sdks/courier-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork @@ -61,6 +64,21 @@ jobs: - name: Build SDK run: ./scripts/build + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/courier-java' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Build and upload Maven artifacts + if: github.repository == 'stainless-sdks/courier-java' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + PROJECT: courier-java + run: ./scripts/upload-artifacts test: timeout-minutes: 15 name: test diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1eec10e9..f1a48d37 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.7.0" + ".": "4.7.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 647de165..92aa6915 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 78 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier%2Fcourier-b79e0eb1ab06f4076c48fa519e2b2ad792a0c483a5d017e43c938ca4c4be6988.yml -openapi_spec_hash: cb3cc2c1145503e5737a880326857aa4 -config_hash: ff903e824043dc81aca51a0ce21896d6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier%2Fcourier-e3e54d99e2a73fd87519270f2685131050d342e86a4e96130247b854deae5c20.yml +openapi_spec_hash: 897a3fbee24f24d021d6af0df480220c +config_hash: 66a5c28bb74d78454456d9ce7d1c0a0c diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b1c3f1a..20855c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 4.7.1 (2026-01-14) + +Full Changelog: [v4.7.0...v4.7.1](https://github.com/trycourier/courier-java/compare/v4.7.0...v4.7.1) + +### Chores + +* **internal:** regenerate SDK with no functional changes ([227578d](https://github.com/trycourier/courier-java/commit/227578dc96d7114cd01e4cac20567f46008364ec)) +* **internal:** regenerate SDK with no functional changes ([49599e7](https://github.com/trycourier/courier-java/commit/49599e729b1ed313141734486e484ca4b88cd8d9)) +* **internal:** support uploading Maven repo artifacts to stainless package server ([4a21584](https://github.com/trycourier/courier-java/commit/4a21584ee2daff5c18cf9ca259cce3a9920005ba)) + ## 4.7.0 (2026-01-12) Full Changelog: [v4.6.0...v4.7.0](https://github.com/trycourier/courier-java/compare/v4.6.0...v4.7.0) diff --git a/README.md b/README.md index c8a8b3ed..47bcf43a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.courier/courier-java)](https://central.sonatype.com/artifact/com.courier/courier-java/4.7.0) -[![javadoc](https://javadoc.io/badge2/com.courier/courier-java/4.7.0/javadoc.svg)](https://javadoc.io/doc/com.courier/courier-java/4.7.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.courier/courier-java)](https://central.sonatype.com/artifact/com.courier/courier-java/4.7.1) +[![javadoc](https://javadoc.io/badge2/com.courier/courier-java/4.7.1/javadoc.svg)](https://javadoc.io/doc/com.courier/courier-java/4.7.1) @@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/). -The REST API documentation can be found on [www.courier.com](https://www.courier.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.courier/courier-java/4.7.0). +The REST API documentation can be found on [www.courier.com](https://www.courier.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.courier/courier-java/4.7.1). @@ -24,7 +24,7 @@ The REST API documentation can be found on [www.courier.com](https://www.courier ### Gradle ```kotlin -implementation("com.courier:courier-java:4.7.0") +implementation("com.courier:courier-java:4.7.1") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.courier:courier-java:4.7.0") com.courier courier-java - 4.7.0 + 4.7.1 ``` diff --git a/build.gradle.kts b/build.gradle.kts index d206be55..885270a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.courier" - version = "4.7.0" // x-release-please-version + version = "4.7.1" // x-release-please-version } subprojects { diff --git a/buildSrc/src/main/kotlin/courier.publish.gradle.kts b/buildSrc/src/main/kotlin/courier.publish.gradle.kts index e70f24d9..50c167b8 100644 --- a/buildSrc/src/main/kotlin/courier.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/courier.publish.gradle.kts @@ -7,6 +7,17 @@ plugins { id("com.vanniktech.maven.publish") } +publishing { + repositories { + if (project.hasProperty("publishLocal")) { + maven { + name = "LocalFileSystem" + url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo") + } + } + } +} + repositories { gradlePluginPortal() mavenCentral() @@ -17,8 +28,10 @@ extra["signingInMemoryKeyId"] = System.getenv("GPG_SIGNING_KEY_ID") extra["signingInMemoryKeyPassword"] = System.getenv("GPG_SIGNING_PASSWORD") configure { - signAllPublications() - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + if (!project.hasProperty("publishLocal")) { + signAllPublications() + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + } coordinates(project.group.toString(), project.name, project.version.toString()) configure( diff --git a/courier-java-core/src/main/kotlin/com/courier/models/AudienceFilterConfig.kt b/courier-java-core/src/main/kotlin/com/courier/models/AudienceFilterConfig.kt new file mode 100644 index 00000000..a93282aa --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/AudienceFilterConfig.kt @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models + +import com.courier.core.ExcludeMissing +import com.courier.core.JsonField +import com.courier.core.JsonMissing +import com.courier.core.JsonValue +import com.courier.core.checkKnown +import com.courier.core.checkRequired +import com.courier.core.toImmutable +import com.courier.errors.CourierInvalidDataException +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import java.util.Collections +import java.util.Objects +import kotlin.jvm.optionals.getOrNull + +/** Filter configuration for audience membership containing an array of filter rules */ +class AudienceFilterConfig +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val filters: JsonField>, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("filters") + @ExcludeMissing + filters: JsonField> = JsonMissing.of() + ) : this(filters, mutableMapOf()) + + /** + * Array of filter rules (single conditions or nested groups) + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun filters(): List = filters.getRequired("filters") + + /** + * Returns the raw JSON value of [filters]. + * + * Unlike [filters], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("filters") @ExcludeMissing fun _filters(): JsonField> = filters + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [AudienceFilterConfig]. + * + * The following fields are required: + * ```java + * .filters() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [AudienceFilterConfig]. */ + class Builder internal constructor() { + + private var filters: JsonField>? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(audienceFilterConfig: AudienceFilterConfig) = apply { + filters = audienceFilterConfig.filters.map { it.toMutableList() } + additionalProperties = audienceFilterConfig.additionalProperties.toMutableMap() + } + + /** Array of filter rules (single conditions or nested groups) */ + fun filters(filters: List) = filters(JsonField.of(filters)) + + /** + * Sets [Builder.filters] to an arbitrary JSON value. + * + * You should usually call [Builder.filters] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun filters(filters: JsonField>) = apply { + this.filters = filters.map { it.toMutableList() } + } + + /** + * Adds a single [FilterConfig] to [filters]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addFilter(filter: FilterConfig) = apply { + filters = + (filters ?: JsonField.of(mutableListOf())).also { + checkKnown("filters", it).add(filter) + } + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [AudienceFilterConfig]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .filters() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): AudienceFilterConfig = + AudienceFilterConfig( + checkRequired("filters", filters).map { it.toImmutable() }, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): AudienceFilterConfig = apply { + if (validated) { + return@apply + } + + filters().forEach { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (filters.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is AudienceFilterConfig && + filters == other.filters && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(filters, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "AudienceFilterConfig{filters=$filters, additionalProperties=$additionalProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNode.kt b/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNode.kt index 1b848899..6b481f4c 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNode.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNode.kt @@ -7,7 +7,6 @@ import com.courier.core.JsonField import com.courier.core.JsonMissing import com.courier.core.JsonValue import com.courier.core.checkKnown -import com.courier.core.checkRequired import com.courier.core.toImmutable import com.courier.errors.CourierInvalidDataException import com.fasterxml.jackson.annotation.JsonAnyGetter @@ -85,10 +84,10 @@ private constructor( * The channel the contents of this element should be applied to. Can be `email`, `push`, * `direct_message`, `sms` or a provider such as slack * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). */ - fun channel(): String = channel.getRequired("channel") + fun channel(): Optional = channel.getOptional("channel") /** * Raw data to apply to the channel. If `elements` has not been specified, `raw` is required. @@ -154,14 +153,7 @@ private constructor( companion object { - /** - * Returns a mutable builder for constructing an instance of [ElementalChannelNode]. - * - * The following fields are required: - * ```java - * .channel() - * ``` - */ + /** Returns a mutable builder for constructing an instance of [ElementalChannelNode]. */ @JvmStatic fun builder() = Builder() } @@ -172,7 +164,7 @@ private constructor( private var if_: JsonField = JsonMissing.of() private var loop: JsonField = JsonMissing.of() private var ref: JsonField = JsonMissing.of() - private var channel: JsonField? = null + private var channel: JsonField = JsonMissing.of() private var raw: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @@ -308,13 +300,6 @@ private constructor( * Returns an immutable instance of [ElementalChannelNode]. * * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .channel() - * ``` - * - * @throws IllegalStateException if any required field is unset. */ fun build(): ElementalChannelNode = ElementalChannelNode( @@ -322,7 +307,7 @@ private constructor( if_, loop, ref, - checkRequired("channel", channel), + channel, raw, additionalProperties.toMutableMap(), ) diff --git a/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNodeWithType.kt b/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNodeWithType.kt index ad6031e7..6db19a3f 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNodeWithType.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/ElementalChannelNodeWithType.kt @@ -8,7 +8,6 @@ import com.courier.core.JsonField import com.courier.core.JsonMissing import com.courier.core.JsonValue import com.courier.core.checkKnown -import com.courier.core.checkRequired import com.courier.core.toImmutable import com.courier.errors.CourierInvalidDataException import com.fasterxml.jackson.annotation.JsonAnyGetter @@ -97,10 +96,10 @@ private constructor( * The channel the contents of this element should be applied to. Can be `email`, `push`, * `direct_message`, `sms` or a provider such as slack * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). */ - fun channel(): String = channel.getRequired("channel") + fun channel(): Optional = channel.getOptional("channel") /** * Raw data to apply to the channel. If `elements` has not been specified, `raw` is required. @@ -181,11 +180,6 @@ private constructor( /** * Returns a mutable builder for constructing an instance of [ElementalChannelNodeWithType]. - * - * The following fields are required: - * ```java - * .channel() - * ``` */ @JvmStatic fun builder() = Builder() } @@ -197,7 +191,7 @@ private constructor( private var if_: JsonField = JsonMissing.of() private var loop: JsonField = JsonMissing.of() private var ref: JsonField = JsonMissing.of() - private var channel: JsonField? = null + private var channel: JsonField = JsonMissing.of() private var raw: JsonField = JsonMissing.of() private var type: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @@ -346,13 +340,6 @@ private constructor( * Returns an immutable instance of [ElementalChannelNodeWithType]. * * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .channel() - * ``` - * - * @throws IllegalStateException if any required field is unset. */ fun build(): ElementalChannelNodeWithType = ElementalChannelNodeWithType( @@ -360,7 +347,7 @@ private constructor( if_, loop, ref, - checkRequired("channel", channel), + channel, raw, type, additionalProperties.toMutableMap(), diff --git a/courier-java-core/src/main/kotlin/com/courier/models/FilterConfig.kt b/courier-java-core/src/main/kotlin/com/courier/models/FilterConfig.kt new file mode 100644 index 00000000..6d0d0b29 --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/FilterConfig.kt @@ -0,0 +1,323 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models + +import com.courier.core.ExcludeMissing +import com.courier.core.JsonField +import com.courier.core.JsonMissing +import com.courier.core.JsonValue +import com.courier.core.checkKnown +import com.courier.core.checkRequired +import com.courier.core.toImmutable +import com.courier.errors.CourierInvalidDataException +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * A filter rule that can be either a single condition (with path/value) or a nested group (with + * filters array). Use comparison operators (EQ, GT, etc.) for single conditions, and logical + * operators (AND, OR) for nested groups. + */ +class FilterConfig +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val operator: JsonField, + private val filters: JsonField>, + private val path: JsonField, + private val value: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("operator") @ExcludeMissing operator: JsonField = JsonMissing.of(), + @JsonProperty("filters") + @ExcludeMissing + filters: JsonField> = JsonMissing.of(), + @JsonProperty("path") @ExcludeMissing path: JsonField = JsonMissing.of(), + @JsonProperty("value") @ExcludeMissing value: JsonField = JsonMissing.of(), + ) : this(operator, filters, path, value, mutableMapOf()) + + /** + * The operator for this filter. Use comparison operators (EQ, GT, LT, GTE, LTE, NEQ, EXISTS, + * INCLUDES, STARTS_WITH, ENDS_WITH, IS_BEFORE, IS_AFTER, OMIT) for single conditions, or + * logical operators (AND, OR) for nested filter groups. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun operator(): String = operator.getRequired("operator") + + /** + * Nested filter rules to combine with AND/OR. Required for nested filter groups, not used for + * single filter conditions. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun filters(): Optional> = filters.getOptional("filters") + + /** + * The attribute path from the user profile to filter on. Required for single filter conditions, + * not used for nested filter groups. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun path(): Optional = path.getOptional("path") + + /** + * The value to compare against. Required for single filter conditions, not used for nested + * filter groups. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun value(): Optional = value.getOptional("value") + + /** + * Returns the raw JSON value of [operator]. + * + * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("operator") @ExcludeMissing fun _operator(): JsonField = operator + + /** + * Returns the raw JSON value of [filters]. + * + * Unlike [filters], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("filters") @ExcludeMissing fun _filters(): JsonField> = filters + + /** + * Returns the raw JSON value of [path]. + * + * Unlike [path], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("path") @ExcludeMissing fun _path(): JsonField = path + + /** + * Returns the raw JSON value of [value]. + * + * Unlike [value], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("value") @ExcludeMissing fun _value(): JsonField = value + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [FilterConfig]. + * + * The following fields are required: + * ```java + * .operator() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [FilterConfig]. */ + class Builder internal constructor() { + + private var operator: JsonField? = null + private var filters: JsonField>? = null + private var path: JsonField = JsonMissing.of() + private var value: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(filterConfig: FilterConfig) = apply { + operator = filterConfig.operator + filters = filterConfig.filters.map { it.toMutableList() } + path = filterConfig.path + value = filterConfig.value + additionalProperties = filterConfig.additionalProperties.toMutableMap() + } + + /** + * The operator for this filter. Use comparison operators (EQ, GT, LT, GTE, LTE, NEQ, + * EXISTS, INCLUDES, STARTS_WITH, ENDS_WITH, IS_BEFORE, IS_AFTER, OMIT) for single + * conditions, or logical operators (AND, OR) for nested filter groups. + */ + fun operator(operator: String) = operator(JsonField.of(operator)) + + /** + * Sets [Builder.operator] to an arbitrary JSON value. + * + * You should usually call [Builder.operator] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun operator(operator: JsonField) = apply { this.operator = operator } + + /** + * Nested filter rules to combine with AND/OR. Required for nested filter groups, not used + * for single filter conditions. + */ + fun filters(filters: List) = filters(JsonField.of(filters)) + + /** + * Sets [Builder.filters] to an arbitrary JSON value. + * + * You should usually call [Builder.filters] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun filters(filters: JsonField>) = apply { + this.filters = filters.map { it.toMutableList() } + } + + /** + * Adds a single [FilterConfig] to [filters]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addFilter(filter: FilterConfig) = apply { + filters = + (filters ?: JsonField.of(mutableListOf())).also { + checkKnown("filters", it).add(filter) + } + } + + /** + * The attribute path from the user profile to filter on. Required for single filter + * conditions, not used for nested filter groups. + */ + fun path(path: String) = path(JsonField.of(path)) + + /** + * Sets [Builder.path] to an arbitrary JSON value. + * + * You should usually call [Builder.path] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun path(path: JsonField) = apply { this.path = path } + + /** + * The value to compare against. Required for single filter conditions, not used for nested + * filter groups. + */ + fun value(value: String) = value(JsonField.of(value)) + + /** + * Sets [Builder.value] to an arbitrary JSON value. + * + * You should usually call [Builder.value] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun value(value: JsonField) = apply { this.value = value } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [FilterConfig]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .operator() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): FilterConfig = + FilterConfig( + checkRequired("operator", operator), + (filters ?: JsonMissing.of()).map { it.toImmutable() }, + path, + value, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): FilterConfig = apply { + if (validated) { + return@apply + } + + operator() + filters().ifPresent { it.forEach { it.validate() } } + path() + value() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (operator.asKnown().isPresent) 1 else 0) + + (filters.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + (if (path.asKnown().isPresent) 1 else 0) + + (if (value.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is FilterConfig && + operator == other.operator && + filters == other.filters && + path == other.path && + value == other.value && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(operator, filters, path, value, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "FilterConfig{operator=$operator, filters=$filters, path=$path, value=$value, additionalProperties=$additionalProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/audiences/Audience.kt b/courier-java-core/src/main/kotlin/com/courier/models/audiences/Audience.kt index 4cdf514a..a40c978a 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/audiences/Audience.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/audiences/Audience.kt @@ -2,18 +2,21 @@ package com.courier.models.audiences +import com.courier.core.Enum import com.courier.core.ExcludeMissing import com.courier.core.JsonField import com.courier.core.JsonMissing import com.courier.core.JsonValue import com.courier.core.checkRequired import com.courier.errors.CourierInvalidDataException +import com.courier.models.AudienceFilterConfig import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty import java.util.Collections import java.util.Objects +import java.util.Optional import kotlin.jvm.optionals.getOrNull class Audience @@ -22,9 +25,10 @@ private constructor( private val id: JsonField, private val createdAt: JsonField, private val description: JsonField, - private val filter: JsonField, private val name: JsonField, private val updatedAt: JsonField, + private val filter: JsonField, + private val operator: JsonField, private val additionalProperties: MutableMap, ) { @@ -35,10 +39,13 @@ private constructor( @JsonProperty("description") @ExcludeMissing description: JsonField = JsonMissing.of(), - @JsonProperty("filter") @ExcludeMissing filter: JsonField = JsonMissing.of(), @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), @JsonProperty("updated_at") @ExcludeMissing updatedAt: JsonField = JsonMissing.of(), - ) : this(id, createdAt, description, filter, name, updatedAt, mutableMapOf()) + @JsonProperty("filter") + @ExcludeMissing + filter: JsonField = JsonMissing.of(), + @JsonProperty("operator") @ExcludeMissing operator: JsonField = JsonMissing.of(), + ) : this(id, createdAt, description, name, updatedAt, filter, operator, mutableMapOf()) /** * A unique identifier representing the audience_id @@ -62,14 +69,6 @@ private constructor( */ fun description(): String = description.getRequired("description") - /** - * A single filter to use for filtering - * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun filter(): Filter = filter.getRequired("filter") - /** * The name of the audience * @@ -84,6 +83,22 @@ private constructor( */ fun updatedAt(): String = updatedAt.getRequired("updated_at") + /** + * Filter configuration for audience membership containing an array of filter rules + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun filter(): Optional = filter.getOptional("filter") + + /** + * The logical operator (AND/OR) for the top-level filter + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun operator(): Optional = operator.getOptional("operator") + /** * Returns the raw JSON value of [id]. * @@ -105,13 +120,6 @@ private constructor( */ @JsonProperty("description") @ExcludeMissing fun _description(): JsonField = description - /** - * Returns the raw JSON value of [filter]. - * - * Unlike [filter], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("filter") @ExcludeMissing fun _filter(): JsonField = filter - /** * Returns the raw JSON value of [name]. * @@ -126,6 +134,20 @@ private constructor( */ @JsonProperty("updated_at") @ExcludeMissing fun _updatedAt(): JsonField = updatedAt + /** + * Returns the raw JSON value of [filter]. + * + * Unlike [filter], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("filter") @ExcludeMissing fun _filter(): JsonField = filter + + /** + * Returns the raw JSON value of [operator]. + * + * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("operator") @ExcludeMissing fun _operator(): JsonField = operator + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -148,7 +170,6 @@ private constructor( * .id() * .createdAt() * .description() - * .filter() * .name() * .updatedAt() * ``` @@ -162,9 +183,10 @@ private constructor( private var id: JsonField? = null private var createdAt: JsonField? = null private var description: JsonField? = null - private var filter: JsonField? = null private var name: JsonField? = null private var updatedAt: JsonField? = null + private var filter: JsonField = JsonMissing.of() + private var operator: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic @@ -172,9 +194,10 @@ private constructor( id = audience.id createdAt = audience.createdAt description = audience.description - filter = audience.filter name = audience.name updatedAt = audience.updatedAt + filter = audience.filter + operator = audience.operator additionalProperties = audience.additionalProperties.toMutableMap() } @@ -212,25 +235,6 @@ private constructor( */ fun description(description: JsonField) = apply { this.description = description } - /** A single filter to use for filtering */ - fun filter(filter: Filter) = filter(JsonField.of(filter)) - - /** - * Sets [Builder.filter] to an arbitrary JSON value. - * - * You should usually call [Builder.filter] with a well-typed [Filter] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. - */ - fun filter(filter: JsonField) = apply { this.filter = filter } - - /** Alias for calling [filter] with `Filter.ofSingleFilterConfig(singleFilterConfig)`. */ - fun filter(singleFilterConfig: SingleFilterConfig) = - filter(Filter.ofSingleFilterConfig(singleFilterConfig)) - - /** Alias for calling [filter] with `Filter.ofNestedFilterConfig(nestedFilterConfig)`. */ - fun filter(nestedFilterConfig: NestedFilterConfig) = - filter(Filter.ofNestedFilterConfig(nestedFilterConfig)) - /** The name of the audience */ fun name(name: String) = name(JsonField.of(name)) @@ -253,6 +257,33 @@ private constructor( */ fun updatedAt(updatedAt: JsonField) = apply { this.updatedAt = updatedAt } + /** Filter configuration for audience membership containing an array of filter rules */ + fun filter(filter: AudienceFilterConfig?) = filter(JsonField.ofNullable(filter)) + + /** Alias for calling [Builder.filter] with `filter.orElse(null)`. */ + fun filter(filter: Optional) = filter(filter.getOrNull()) + + /** + * Sets [Builder.filter] to an arbitrary JSON value. + * + * You should usually call [Builder.filter] with a well-typed [AudienceFilterConfig] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun filter(filter: JsonField) = apply { this.filter = filter } + + /** The logical operator (AND/OR) for the top-level filter */ + fun operator(operator: Operator) = operator(JsonField.of(operator)) + + /** + * Sets [Builder.operator] to an arbitrary JSON value. + * + * You should usually call [Builder.operator] with a well-typed [Operator] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun operator(operator: JsonField) = apply { this.operator = operator } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -282,7 +313,6 @@ private constructor( * .id() * .createdAt() * .description() - * .filter() * .name() * .updatedAt() * ``` @@ -294,9 +324,10 @@ private constructor( checkRequired("id", id), checkRequired("createdAt", createdAt), checkRequired("description", description), - checkRequired("filter", filter), checkRequired("name", name), checkRequired("updatedAt", updatedAt), + filter, + operator, additionalProperties.toMutableMap(), ) } @@ -311,9 +342,10 @@ private constructor( id() createdAt() description() - filter().validate() name() updatedAt() + filter().ifPresent { it.validate() } + operator().ifPresent { it.validate() } validated = true } @@ -335,9 +367,136 @@ private constructor( (if (id.asKnown().isPresent) 1 else 0) + (if (createdAt.asKnown().isPresent) 1 else 0) + (if (description.asKnown().isPresent) 1 else 0) + - (filter.asKnown().getOrNull()?.validity() ?: 0) + (if (name.asKnown().isPresent) 1 else 0) + - (if (updatedAt.asKnown().isPresent) 1 else 0) + (if (updatedAt.asKnown().isPresent) 1 else 0) + + (filter.asKnown().getOrNull()?.validity() ?: 0) + + (operator.asKnown().getOrNull()?.validity() ?: 0) + + /** The logical operator (AND/OR) for the top-level filter */ + class Operator @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val AND = of("AND") + + @JvmField val OR = of("OR") + + @JvmStatic fun of(value: String) = Operator(JsonField.of(value)) + } + + /** An enum containing [Operator]'s known values. */ + enum class Known { + AND, + OR, + } + + /** + * An enum containing [Operator]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Operator] can contain an unknown value in a couple of cases: + * - It was deserialized from data that doesn't match any known member. For example, if the + * SDK is on an older version than the API, then the API may respond with new members that + * the SDK is unaware of. + * - It was constructed with an arbitrary value using the [of] method. + */ + enum class Value { + AND, + OR, + /** An enum member indicating that [Operator] was instantiated with an unknown value. */ + _UNKNOWN, + } + + /** + * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] + * if the class was instantiated with an unknown value. + * + * Use the [known] method instead if you're certain the value is always known or if you want + * to throw for the unknown case. + */ + fun value(): Value = + when (this) { + AND -> Value.AND + OR -> Value.OR + else -> Value._UNKNOWN + } + + /** + * Returns an enum member corresponding to this class instance's value. + * + * Use the [value] method instead if you're uncertain the value is always known and don't + * want to throw for the unknown case. + * + * @throws CourierInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + AND -> Known.AND + OR -> Known.OR + else -> throw CourierInvalidDataException("Unknown Operator: $value") + } + + /** + * Returns this class instance's primitive wire representation. + * + * This differs from the [toString] method because that method is primarily for debugging + * and generally doesn't throw. + * + * @throws CourierInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { CourierInvalidDataException("Value is not a String") } + + private var validated: Boolean = false + + fun validate(): Operator = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Operator && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -348,18 +507,28 @@ private constructor( id == other.id && createdAt == other.createdAt && description == other.description && - filter == other.filter && name == other.name && updatedAt == other.updatedAt && + filter == other.filter && + operator == other.operator && additionalProperties == other.additionalProperties } private val hashCode: Int by lazy { - Objects.hash(id, createdAt, description, filter, name, updatedAt, additionalProperties) + Objects.hash( + id, + createdAt, + description, + name, + updatedAt, + filter, + operator, + additionalProperties, + ) } override fun hashCode(): Int = hashCode override fun toString() = - "Audience{id=$id, createdAt=$createdAt, description=$description, filter=$filter, name=$name, updatedAt=$updatedAt, additionalProperties=$additionalProperties}" + "Audience{id=$id, createdAt=$createdAt, description=$description, name=$name, updatedAt=$updatedAt, filter=$filter, operator=$operator, additionalProperties=$additionalProperties}" } diff --git a/courier-java-core/src/main/kotlin/com/courier/models/audiences/AudienceUpdateParams.kt b/courier-java-core/src/main/kotlin/com/courier/models/audiences/AudienceUpdateParams.kt index 415505bb..3344d3a4 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/audiences/AudienceUpdateParams.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/audiences/AudienceUpdateParams.kt @@ -2,6 +2,7 @@ package com.courier.models.audiences +import com.courier.core.Enum import com.courier.core.ExcludeMissing import com.courier.core.JsonField import com.courier.core.JsonMissing @@ -10,6 +11,7 @@ import com.courier.core.Params import com.courier.core.http.Headers import com.courier.core.http.QueryParams import com.courier.errors.CourierInvalidDataException +import com.courier.models.AudienceFilterConfig import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator @@ -39,12 +41,12 @@ private constructor( fun description(): Optional = body.description() /** - * A single filter to use for filtering + * Filter configuration for audience membership containing an array of filter rules * * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun filter(): Optional = body.filter() + fun filter(): Optional = body.filter() /** * The name of the audience @@ -54,6 +56,14 @@ private constructor( */ fun name(): Optional = body.name() + /** + * The logical operator (AND/OR) for the top-level filter + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun operator(): Optional = body.operator() + /** * Returns the raw JSON value of [description]. * @@ -66,7 +76,7 @@ private constructor( * * Unlike [filter], this method doesn't throw if the JSON field has an unexpected type. */ - fun _filter(): JsonField = body._filter() + fun _filter(): JsonField = body._filter() /** * Returns the raw JSON value of [name]. @@ -75,6 +85,13 @@ private constructor( */ fun _name(): JsonField = body._name() + /** + * Returns the raw JSON value of [operator]. + * + * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _operator(): JsonField = body._operator() + fun _additionalBodyProperties(): Map = body._additionalProperties() /** Additional headers to send with the request. */ @@ -122,6 +139,7 @@ private constructor( * - [description] * - [filter] * - [name] + * - [operator] */ fun body(body: Body) = apply { this.body = body.toBuilder() } @@ -140,29 +158,20 @@ private constructor( */ fun description(description: JsonField) = apply { body.description(description) } - /** A single filter to use for filtering */ - fun filter(filter: Filter?) = apply { body.filter(filter) } + /** Filter configuration for audience membership containing an array of filter rules */ + fun filter(filter: AudienceFilterConfig?) = apply { body.filter(filter) } /** Alias for calling [Builder.filter] with `filter.orElse(null)`. */ - fun filter(filter: Optional) = filter(filter.getOrNull()) + fun filter(filter: Optional) = filter(filter.getOrNull()) /** * Sets [Builder.filter] to an arbitrary JSON value. * - * You should usually call [Builder.filter] with a well-typed [Filter] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. + * You should usually call [Builder.filter] with a well-typed [AudienceFilterConfig] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. */ - fun filter(filter: JsonField) = apply { body.filter(filter) } - - /** Alias for calling [filter] with `Filter.ofSingleFilterConfig(singleFilterConfig)`. */ - fun filter(singleFilterConfig: SingleFilterConfig) = apply { - body.filter(singleFilterConfig) - } - - /** Alias for calling [filter] with `Filter.ofNestedFilterConfig(nestedFilterConfig)`. */ - fun filter(nestedFilterConfig: NestedFilterConfig) = apply { - body.filter(nestedFilterConfig) - } + fun filter(filter: JsonField) = apply { body.filter(filter) } /** The name of the audience */ fun name(name: String?) = apply { body.name(name) } @@ -178,6 +187,21 @@ private constructor( */ fun name(name: JsonField) = apply { body.name(name) } + /** The logical operator (AND/OR) for the top-level filter */ + fun operator(operator: Operator?) = apply { body.operator(operator) } + + /** Alias for calling [Builder.operator] with `operator.orElse(null)`. */ + fun operator(operator: Optional) = operator(operator.getOrNull()) + + /** + * Sets [Builder.operator] to an arbitrary JSON value. + * + * You should usually call [Builder.operator] with a well-typed [Operator] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun operator(operator: JsonField) = apply { body.operator(operator) } + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { body.additionalProperties(additionalBodyProperties) } @@ -325,8 +349,9 @@ private constructor( @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val description: JsonField, - private val filter: JsonField, + private val filter: JsonField, private val name: JsonField, + private val operator: JsonField, private val additionalProperties: MutableMap, ) { @@ -335,9 +360,14 @@ private constructor( @JsonProperty("description") @ExcludeMissing description: JsonField = JsonMissing.of(), - @JsonProperty("filter") @ExcludeMissing filter: JsonField = JsonMissing.of(), + @JsonProperty("filter") + @ExcludeMissing + filter: JsonField = JsonMissing.of(), @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), - ) : this(description, filter, name, mutableMapOf()) + @JsonProperty("operator") + @ExcludeMissing + operator: JsonField = JsonMissing.of(), + ) : this(description, filter, name, operator, mutableMapOf()) /** * A description of the audience @@ -348,12 +378,12 @@ private constructor( fun description(): Optional = description.getOptional("description") /** - * A single filter to use for filtering + * Filter configuration for audience membership containing an array of filter rules * * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun filter(): Optional = filter.getOptional("filter") + fun filter(): Optional = filter.getOptional("filter") /** * The name of the audience @@ -363,6 +393,14 @@ private constructor( */ fun name(): Optional = name.getOptional("name") + /** + * The logical operator (AND/OR) for the top-level filter + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun operator(): Optional = operator.getOptional("operator") + /** * Returns the raw JSON value of [description]. * @@ -377,7 +415,9 @@ private constructor( * * Unlike [filter], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("filter") @ExcludeMissing fun _filter(): JsonField = filter + @JsonProperty("filter") + @ExcludeMissing + fun _filter(): JsonField = filter /** * Returns the raw JSON value of [name]. @@ -386,6 +426,13 @@ private constructor( */ @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + /** + * Returns the raw JSON value of [operator]. + * + * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("operator") @ExcludeMissing fun _operator(): JsonField = operator + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -408,8 +455,9 @@ private constructor( class Builder internal constructor() { private var description: JsonField = JsonMissing.of() - private var filter: JsonField = JsonMissing.of() + private var filter: JsonField = JsonMissing.of() private var name: JsonField = JsonMissing.of() + private var operator: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic @@ -417,6 +465,7 @@ private constructor( description = body.description filter = body.filter name = body.name + operator = body.operator additionalProperties = body.additionalProperties.toMutableMap() } @@ -437,32 +486,20 @@ private constructor( this.description = description } - /** A single filter to use for filtering */ - fun filter(filter: Filter?) = filter(JsonField.ofNullable(filter)) + /** Filter configuration for audience membership containing an array of filter rules */ + fun filter(filter: AudienceFilterConfig?) = filter(JsonField.ofNullable(filter)) /** Alias for calling [Builder.filter] with `filter.orElse(null)`. */ - fun filter(filter: Optional) = filter(filter.getOrNull()) + fun filter(filter: Optional) = filter(filter.getOrNull()) /** * Sets [Builder.filter] to an arbitrary JSON value. * - * You should usually call [Builder.filter] with a well-typed [Filter] value instead. - * This method is primarily for setting the field to an undocumented or not yet - * supported value. + * You should usually call [Builder.filter] with a well-typed [AudienceFilterConfig] + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. */ - fun filter(filter: JsonField) = apply { this.filter = filter } - - /** - * Alias for calling [filter] with `Filter.ofSingleFilterConfig(singleFilterConfig)`. - */ - fun filter(singleFilterConfig: SingleFilterConfig) = - filter(Filter.ofSingleFilterConfig(singleFilterConfig)) - - /** - * Alias for calling [filter] with `Filter.ofNestedFilterConfig(nestedFilterConfig)`. - */ - fun filter(nestedFilterConfig: NestedFilterConfig) = - filter(Filter.ofNestedFilterConfig(nestedFilterConfig)) + fun filter(filter: JsonField) = apply { this.filter = filter } /** The name of the audience */ fun name(name: String?) = name(JsonField.ofNullable(name)) @@ -479,6 +516,21 @@ private constructor( */ fun name(name: JsonField) = apply { this.name = name } + /** The logical operator (AND/OR) for the top-level filter */ + fun operator(operator: Operator?) = operator(JsonField.ofNullable(operator)) + + /** Alias for calling [Builder.operator] with `operator.orElse(null)`. */ + fun operator(operator: Optional) = operator(operator.getOrNull()) + + /** + * Sets [Builder.operator] to an arbitrary JSON value. + * + * You should usually call [Builder.operator] with a well-typed [Operator] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun operator(operator: JsonField) = apply { this.operator = operator } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -503,7 +555,8 @@ private constructor( * * Further updates to this [Builder] will not mutate the returned instance. */ - fun build(): Body = Body(description, filter, name, additionalProperties.toMutableMap()) + fun build(): Body = + Body(description, filter, name, operator, additionalProperties.toMutableMap()) } private var validated: Boolean = false @@ -516,6 +569,7 @@ private constructor( description() filter().ifPresent { it.validate() } name() + operator().ifPresent { it.validate() } validated = true } @@ -537,7 +591,8 @@ private constructor( internal fun validity(): Int = (if (description.asKnown().isPresent) 1 else 0) + (filter.asKnown().getOrNull()?.validity() ?: 0) + - (if (name.asKnown().isPresent) 1 else 0) + (if (name.asKnown().isPresent) 1 else 0) + + (operator.asKnown().getOrNull()?.validity() ?: 0) override fun equals(other: Any?): Boolean { if (this === other) { @@ -548,17 +603,144 @@ private constructor( description == other.description && filter == other.filter && name == other.name && + operator == other.operator && additionalProperties == other.additionalProperties } private val hashCode: Int by lazy { - Objects.hash(description, filter, name, additionalProperties) + Objects.hash(description, filter, name, operator, additionalProperties) } override fun hashCode(): Int = hashCode override fun toString() = - "Body{description=$description, filter=$filter, name=$name, additionalProperties=$additionalProperties}" + "Body{description=$description, filter=$filter, name=$name, operator=$operator, additionalProperties=$additionalProperties}" + } + + /** The logical operator (AND/OR) for the top-level filter */ + class Operator @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val AND = of("AND") + + @JvmField val OR = of("OR") + + @JvmStatic fun of(value: String) = Operator(JsonField.of(value)) + } + + /** An enum containing [Operator]'s known values. */ + enum class Known { + AND, + OR, + } + + /** + * An enum containing [Operator]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Operator] can contain an unknown value in a couple of cases: + * - It was deserialized from data that doesn't match any known member. For example, if the + * SDK is on an older version than the API, then the API may respond with new members that + * the SDK is unaware of. + * - It was constructed with an arbitrary value using the [of] method. + */ + enum class Value { + AND, + OR, + /** An enum member indicating that [Operator] was instantiated with an unknown value. */ + _UNKNOWN, + } + + /** + * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] + * if the class was instantiated with an unknown value. + * + * Use the [known] method instead if you're certain the value is always known or if you want + * to throw for the unknown case. + */ + fun value(): Value = + when (this) { + AND -> Value.AND + OR -> Value.OR + else -> Value._UNKNOWN + } + + /** + * Returns an enum member corresponding to this class instance's value. + * + * Use the [value] method instead if you're uncertain the value is always known and don't + * want to throw for the unknown case. + * + * @throws CourierInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + AND -> Known.AND + OR -> Known.OR + else -> throw CourierInvalidDataException("Unknown Operator: $value") + } + + /** + * Returns this class instance's primitive wire representation. + * + * This differs from the [toString] method because that method is primarily for debugging + * and generally doesn't throw. + * + * @throws CourierInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { CourierInvalidDataException("Value is not a String") } + + private var validated: Boolean = false + + fun validate(): Operator = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Operator && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() } override fun equals(other: Any?): Boolean { diff --git a/courier-java-core/src/main/kotlin/com/courier/models/audiences/Filter.kt b/courier-java-core/src/main/kotlin/com/courier/models/audiences/Filter.kt deleted file mode 100644 index 14dab282..00000000 --- a/courier-java-core/src/main/kotlin/com/courier/models/audiences/Filter.kt +++ /dev/null @@ -1,206 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.BaseDeserializer -import com.courier.core.BaseSerializer -import com.courier.core.JsonValue -import com.courier.core.allMaxBy -import com.courier.core.getOrThrow -import com.courier.errors.CourierInvalidDataException -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.ObjectCodec -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import java.util.Objects -import java.util.Optional - -/** A single filter to use for filtering */ -@JsonDeserialize(using = Filter.Deserializer::class) -@JsonSerialize(using = Filter.Serializer::class) -class Filter -private constructor( - private val singleFilterConfig: SingleFilterConfig? = null, - private val nestedFilterConfig: NestedFilterConfig? = null, - private val _json: JsonValue? = null, -) { - - /** A single filter to use for filtering */ - fun singleFilterConfig(): Optional = Optional.ofNullable(singleFilterConfig) - - /** The operator to use for filtering */ - fun nestedFilterConfig(): Optional = Optional.ofNullable(nestedFilterConfig) - - fun isSingleFilterConfig(): Boolean = singleFilterConfig != null - - fun isNestedFilterConfig(): Boolean = nestedFilterConfig != null - - /** A single filter to use for filtering */ - fun asSingleFilterConfig(): SingleFilterConfig = - singleFilterConfig.getOrThrow("singleFilterConfig") - - /** The operator to use for filtering */ - fun asNestedFilterConfig(): NestedFilterConfig = - nestedFilterConfig.getOrThrow("nestedFilterConfig") - - fun _json(): Optional = Optional.ofNullable(_json) - - fun accept(visitor: Visitor): T = - when { - singleFilterConfig != null -> visitor.visitSingleFilterConfig(singleFilterConfig) - nestedFilterConfig != null -> visitor.visitNestedFilterConfig(nestedFilterConfig) - else -> visitor.unknown(_json) - } - - private var validated: Boolean = false - - fun validate(): Filter = apply { - if (validated) { - return@apply - } - - accept( - object : Visitor { - override fun visitSingleFilterConfig(singleFilterConfig: SingleFilterConfig) { - singleFilterConfig.validate() - } - - override fun visitNestedFilterConfig(nestedFilterConfig: NestedFilterConfig) { - nestedFilterConfig.validate() - } - } - ) - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: CourierInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic - internal fun validity(): Int = - accept( - object : Visitor { - override fun visitSingleFilterConfig(singleFilterConfig: SingleFilterConfig) = - singleFilterConfig.validity() - - override fun visitNestedFilterConfig(nestedFilterConfig: NestedFilterConfig) = - nestedFilterConfig.validity() - - override fun unknown(json: JsonValue?) = 0 - } - ) - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is Filter && - singleFilterConfig == other.singleFilterConfig && - nestedFilterConfig == other.nestedFilterConfig - } - - override fun hashCode(): Int = Objects.hash(singleFilterConfig, nestedFilterConfig) - - override fun toString(): String = - when { - singleFilterConfig != null -> "Filter{singleFilterConfig=$singleFilterConfig}" - nestedFilterConfig != null -> "Filter{nestedFilterConfig=$nestedFilterConfig}" - _json != null -> "Filter{_unknown=$_json}" - else -> throw IllegalStateException("Invalid Filter") - } - - companion object { - - /** A single filter to use for filtering */ - @JvmStatic - fun ofSingleFilterConfig(singleFilterConfig: SingleFilterConfig) = - Filter(singleFilterConfig = singleFilterConfig) - - /** The operator to use for filtering */ - @JvmStatic - fun ofNestedFilterConfig(nestedFilterConfig: NestedFilterConfig) = - Filter(nestedFilterConfig = nestedFilterConfig) - } - - /** An interface that defines how to map each variant of [Filter] to a value of type [T]. */ - interface Visitor { - - /** A single filter to use for filtering */ - fun visitSingleFilterConfig(singleFilterConfig: SingleFilterConfig): T - - /** The operator to use for filtering */ - fun visitNestedFilterConfig(nestedFilterConfig: NestedFilterConfig): T - - /** - * Maps an unknown variant of [Filter] to a value of type [T]. - * - * An instance of [Filter] can contain an unknown variant if it was deserialized from data - * that doesn't match any known variant. For example, if the SDK is on an older version than - * the API, then the API may respond with new variants that the SDK is unaware of. - * - * @throws CourierInvalidDataException in the default implementation. - */ - fun unknown(json: JsonValue?): T { - throw CourierInvalidDataException("Unknown Filter: $json") - } - } - - internal class Deserializer : BaseDeserializer(Filter::class) { - - override fun ObjectCodec.deserialize(node: JsonNode): Filter { - val json = JsonValue.fromJsonNode(node) - - val bestMatches = - sequenceOf( - tryDeserialize(node, jacksonTypeRef())?.let { - Filter(singleFilterConfig = it, _json = json) - }, - tryDeserialize(node, jacksonTypeRef())?.let { - Filter(nestedFilterConfig = it, _json = json) - }, - ) - .filterNotNull() - .allMaxBy { it.validity() } - .toList() - return when (bestMatches.size) { - // This can happen if what we're deserializing is completely incompatible with all - // the possible variants (e.g. deserializing from boolean). - 0 -> Filter(_json = json) - 1 -> bestMatches.single() - // If there's more than one match with the highest validity, then use the first - // completely valid match, or simply the first match if none are completely valid. - else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first() - } - } - } - - internal class Serializer : BaseSerializer(Filter::class) { - - override fun serialize( - value: Filter, - generator: JsonGenerator, - provider: SerializerProvider, - ) { - when { - value.singleFilterConfig != null -> generator.writeObject(value.singleFilterConfig) - value.nestedFilterConfig != null -> generator.writeObject(value.nestedFilterConfig) - value._json != null -> generator.writeObject(value._json) - else -> throw IllegalStateException("Invalid Filter") - } - } - } -} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/audiences/NestedFilterConfig.kt b/courier-java-core/src/main/kotlin/com/courier/models/audiences/NestedFilterConfig.kt deleted file mode 100644 index e4518b44..00000000 --- a/courier-java-core/src/main/kotlin/com/courier/models/audiences/NestedFilterConfig.kt +++ /dev/null @@ -1,438 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.Enum -import com.courier.core.ExcludeMissing -import com.courier.core.JsonField -import com.courier.core.JsonMissing -import com.courier.core.JsonValue -import com.courier.core.checkKnown -import com.courier.core.checkRequired -import com.courier.core.toImmutable -import com.courier.errors.CourierInvalidDataException -import com.fasterxml.jackson.annotation.JsonAnyGetter -import com.fasterxml.jackson.annotation.JsonAnySetter -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import java.util.Collections -import java.util.Objects -import kotlin.jvm.optionals.getOrNull - -class NestedFilterConfig -@JsonCreator(mode = JsonCreator.Mode.DISABLED) -private constructor( - private val operator: JsonField, - private val rules: JsonField>, - private val additionalProperties: MutableMap, -) { - - @JsonCreator - private constructor( - @JsonProperty("operator") @ExcludeMissing operator: JsonField = JsonMissing.of(), - @JsonProperty("rules") @ExcludeMissing rules: JsonField> = JsonMissing.of(), - ) : this(operator, rules, mutableMapOf()) - - /** - * The operator to use for filtering - * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun operator(): Operator = operator.getRequired("operator") - - /** - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun rules(): List = rules.getRequired("rules") - - /** - * Returns the raw JSON value of [operator]. - * - * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("operator") @ExcludeMissing fun _operator(): JsonField = operator - - /** - * Returns the raw JSON value of [rules]. - * - * Unlike [rules], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("rules") @ExcludeMissing fun _rules(): JsonField> = rules - - @JsonAnySetter - private fun putAdditionalProperty(key: String, value: JsonValue) { - additionalProperties.put(key, value) - } - - @JsonAnyGetter - @ExcludeMissing - fun _additionalProperties(): Map = - Collections.unmodifiableMap(additionalProperties) - - fun toBuilder() = Builder().from(this) - - companion object { - - /** - * Returns a mutable builder for constructing an instance of [NestedFilterConfig]. - * - * The following fields are required: - * ```java - * .operator() - * .rules() - * ``` - */ - @JvmStatic fun builder() = Builder() - } - - /** A builder for [NestedFilterConfig]. */ - class Builder internal constructor() { - - private var operator: JsonField? = null - private var rules: JsonField>? = null - private var additionalProperties: MutableMap = mutableMapOf() - - @JvmSynthetic - internal fun from(nestedFilterConfig: NestedFilterConfig) = apply { - operator = nestedFilterConfig.operator - rules = nestedFilterConfig.rules.map { it.toMutableList() } - additionalProperties = nestedFilterConfig.additionalProperties.toMutableMap() - } - - /** The operator to use for filtering */ - fun operator(operator: Operator) = operator(JsonField.of(operator)) - - /** - * Sets [Builder.operator] to an arbitrary JSON value. - * - * You should usually call [Builder.operator] with a well-typed [Operator] value instead. - * This method is primarily for setting the field to an undocumented or not yet supported - * value. - */ - fun operator(operator: JsonField) = apply { this.operator = operator } - - fun rules(rules: List) = rules(JsonField.of(rules)) - - /** - * Sets [Builder.rules] to an arbitrary JSON value. - * - * You should usually call [Builder.rules] with a well-typed `List` value instead. - * This method is primarily for setting the field to an undocumented or not yet supported - * value. - */ - fun rules(rules: JsonField>) = apply { - this.rules = rules.map { it.toMutableList() } - } - - /** - * Adds a single [Filter] to [rules]. - * - * @throws IllegalStateException if the field was previously set to a non-list. - */ - fun addRule(rule: Filter) = apply { - rules = - (rules ?: JsonField.of(mutableListOf())).also { checkKnown("rules", it).add(rule) } - } - - /** Alias for calling [addRule] with `Filter.ofSingleFilterConfig(singleFilterConfig)`. */ - fun addRule(singleFilterConfig: SingleFilterConfig) = - addRule(Filter.ofSingleFilterConfig(singleFilterConfig)) - - /** Alias for calling [addRule] with `Filter.ofNestedFilterConfig(nestedFilterConfig)`. */ - fun addRule(nestedFilterConfig: NestedFilterConfig) = - addRule(Filter.ofNestedFilterConfig(nestedFilterConfig)) - - fun additionalProperties(additionalProperties: Map) = apply { - this.additionalProperties.clear() - putAllAdditionalProperties(additionalProperties) - } - - fun putAdditionalProperty(key: String, value: JsonValue) = apply { - additionalProperties.put(key, value) - } - - fun putAllAdditionalProperties(additionalProperties: Map) = apply { - this.additionalProperties.putAll(additionalProperties) - } - - fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } - - fun removeAllAdditionalProperties(keys: Set) = apply { - keys.forEach(::removeAdditionalProperty) - } - - /** - * Returns an immutable instance of [NestedFilterConfig]. - * - * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .operator() - * .rules() - * ``` - * - * @throws IllegalStateException if any required field is unset. - */ - fun build(): NestedFilterConfig = - NestedFilterConfig( - checkRequired("operator", operator), - checkRequired("rules", rules).map { it.toImmutable() }, - additionalProperties.toMutableMap(), - ) - } - - private var validated: Boolean = false - - fun validate(): NestedFilterConfig = apply { - if (validated) { - return@apply - } - - operator().validate() - rules().forEach { it.validate() } - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: CourierInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic - internal fun validity(): Int = - (operator.asKnown().getOrNull()?.validity() ?: 0) + - (rules.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) - - /** The operator to use for filtering */ - class Operator @JsonCreator private constructor(private val value: JsonField) : Enum { - - /** - * Returns this class instance's raw value. - * - * This is usually only useful if this instance was deserialized from data that doesn't - * match any known member, and you want to know that value. For example, if the SDK is on an - * older version than the API, then the API may respond with new members that the SDK is - * unaware of. - */ - @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value - - companion object { - - @JvmField val ENDS_WITH = of("ENDS_WITH") - - @JvmField val EQ = of("EQ") - - @JvmField val EXISTS = of("EXISTS") - - @JvmField val GT = of("GT") - - @JvmField val GTE = of("GTE") - - @JvmField val INCLUDES = of("INCLUDES") - - @JvmField val IS_AFTER = of("IS_AFTER") - - @JvmField val IS_BEFORE = of("IS_BEFORE") - - @JvmField val LT = of("LT") - - @JvmField val LTE = of("LTE") - - @JvmField val NEQ = of("NEQ") - - @JvmField val OMIT = of("OMIT") - - @JvmField val STARTS_WITH = of("STARTS_WITH") - - @JvmField val AND = of("AND") - - @JvmField val OR = of("OR") - - @JvmStatic fun of(value: String) = Operator(JsonField.of(value)) - } - - /** An enum containing [Operator]'s known values. */ - enum class Known { - ENDS_WITH, - EQ, - EXISTS, - GT, - GTE, - INCLUDES, - IS_AFTER, - IS_BEFORE, - LT, - LTE, - NEQ, - OMIT, - STARTS_WITH, - AND, - OR, - } - - /** - * An enum containing [Operator]'s known values, as well as an [_UNKNOWN] member. - * - * An instance of [Operator] can contain an unknown value in a couple of cases: - * - It was deserialized from data that doesn't match any known member. For example, if the - * SDK is on an older version than the API, then the API may respond with new members that - * the SDK is unaware of. - * - It was constructed with an arbitrary value using the [of] method. - */ - enum class Value { - ENDS_WITH, - EQ, - EXISTS, - GT, - GTE, - INCLUDES, - IS_AFTER, - IS_BEFORE, - LT, - LTE, - NEQ, - OMIT, - STARTS_WITH, - AND, - OR, - /** An enum member indicating that [Operator] was instantiated with an unknown value. */ - _UNKNOWN, - } - - /** - * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] - * if the class was instantiated with an unknown value. - * - * Use the [known] method instead if you're certain the value is always known or if you want - * to throw for the unknown case. - */ - fun value(): Value = - when (this) { - ENDS_WITH -> Value.ENDS_WITH - EQ -> Value.EQ - EXISTS -> Value.EXISTS - GT -> Value.GT - GTE -> Value.GTE - INCLUDES -> Value.INCLUDES - IS_AFTER -> Value.IS_AFTER - IS_BEFORE -> Value.IS_BEFORE - LT -> Value.LT - LTE -> Value.LTE - NEQ -> Value.NEQ - OMIT -> Value.OMIT - STARTS_WITH -> Value.STARTS_WITH - AND -> Value.AND - OR -> Value.OR - else -> Value._UNKNOWN - } - - /** - * Returns an enum member corresponding to this class instance's value. - * - * Use the [value] method instead if you're uncertain the value is always known and don't - * want to throw for the unknown case. - * - * @throws CourierInvalidDataException if this class instance's value is a not a known - * member. - */ - fun known(): Known = - when (this) { - ENDS_WITH -> Known.ENDS_WITH - EQ -> Known.EQ - EXISTS -> Known.EXISTS - GT -> Known.GT - GTE -> Known.GTE - INCLUDES -> Known.INCLUDES - IS_AFTER -> Known.IS_AFTER - IS_BEFORE -> Known.IS_BEFORE - LT -> Known.LT - LTE -> Known.LTE - NEQ -> Known.NEQ - OMIT -> Known.OMIT - STARTS_WITH -> Known.STARTS_WITH - AND -> Known.AND - OR -> Known.OR - else -> throw CourierInvalidDataException("Unknown Operator: $value") - } - - /** - * Returns this class instance's primitive wire representation. - * - * This differs from the [toString] method because that method is primarily for debugging - * and generally doesn't throw. - * - * @throws CourierInvalidDataException if this class instance's value does not have the - * expected primitive type. - */ - fun asString(): String = - _value().asString().orElseThrow { CourierInvalidDataException("Value is not a String") } - - private var validated: Boolean = false - - fun validate(): Operator = apply { - if (validated) { - return@apply - } - - known() - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: CourierInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object - * recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is Operator && value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is NestedFilterConfig && - operator == other.operator && - rules == other.rules && - additionalProperties == other.additionalProperties - } - - private val hashCode: Int by lazy { Objects.hash(operator, rules, additionalProperties) } - - override fun hashCode(): Int = hashCode - - override fun toString() = - "NestedFilterConfig{operator=$operator, rules=$rules, additionalProperties=$additionalProperties}" -} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/audiences/SingleFilterConfig.kt b/courier-java-core/src/main/kotlin/com/courier/models/audiences/SingleFilterConfig.kt deleted file mode 100644 index f23a3c02..00000000 --- a/courier-java-core/src/main/kotlin/com/courier/models/audiences/SingleFilterConfig.kt +++ /dev/null @@ -1,454 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.Enum -import com.courier.core.ExcludeMissing -import com.courier.core.JsonField -import com.courier.core.JsonMissing -import com.courier.core.JsonValue -import com.courier.core.checkRequired -import com.courier.errors.CourierInvalidDataException -import com.fasterxml.jackson.annotation.JsonAnyGetter -import com.fasterxml.jackson.annotation.JsonAnySetter -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import java.util.Collections -import java.util.Objects -import kotlin.jvm.optionals.getOrNull - -class SingleFilterConfig -@JsonCreator(mode = JsonCreator.Mode.DISABLED) -private constructor( - private val operator: JsonField, - private val path: JsonField, - private val value: JsonField, - private val additionalProperties: MutableMap, -) { - - @JsonCreator - private constructor( - @JsonProperty("operator") @ExcludeMissing operator: JsonField = JsonMissing.of(), - @JsonProperty("path") @ExcludeMissing path: JsonField = JsonMissing.of(), - @JsonProperty("value") @ExcludeMissing value: JsonField = JsonMissing.of(), - ) : this(operator, path, value, mutableMapOf()) - - /** - * The operator to use for filtering - * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun operator(): Operator = operator.getRequired("operator") - - /** - * The attribute name from profile whose value will be operated against the filter value - * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun path(): String = path.getRequired("path") - - /** - * The value to use for filtering - * - * @throws CourierInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun value(): String = value.getRequired("value") - - /** - * Returns the raw JSON value of [operator]. - * - * Unlike [operator], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("operator") @ExcludeMissing fun _operator(): JsonField = operator - - /** - * Returns the raw JSON value of [path]. - * - * Unlike [path], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("path") @ExcludeMissing fun _path(): JsonField = path - - /** - * Returns the raw JSON value of [value]. - * - * Unlike [value], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("value") @ExcludeMissing fun _value(): JsonField = value - - @JsonAnySetter - private fun putAdditionalProperty(key: String, value: JsonValue) { - additionalProperties.put(key, value) - } - - @JsonAnyGetter - @ExcludeMissing - fun _additionalProperties(): Map = - Collections.unmodifiableMap(additionalProperties) - - fun toBuilder() = Builder().from(this) - - companion object { - - /** - * Returns a mutable builder for constructing an instance of [SingleFilterConfig]. - * - * The following fields are required: - * ```java - * .operator() - * .path() - * .value() - * ``` - */ - @JvmStatic fun builder() = Builder() - } - - /** A builder for [SingleFilterConfig]. */ - class Builder internal constructor() { - - private var operator: JsonField? = null - private var path: JsonField? = null - private var value: JsonField? = null - private var additionalProperties: MutableMap = mutableMapOf() - - @JvmSynthetic - internal fun from(singleFilterConfig: SingleFilterConfig) = apply { - operator = singleFilterConfig.operator - path = singleFilterConfig.path - value = singleFilterConfig.value - additionalProperties = singleFilterConfig.additionalProperties.toMutableMap() - } - - /** The operator to use for filtering */ - fun operator(operator: Operator) = operator(JsonField.of(operator)) - - /** - * Sets [Builder.operator] to an arbitrary JSON value. - * - * You should usually call [Builder.operator] with a well-typed [Operator] value instead. - * This method is primarily for setting the field to an undocumented or not yet supported - * value. - */ - fun operator(operator: JsonField) = apply { this.operator = operator } - - /** The attribute name from profile whose value will be operated against the filter value */ - fun path(path: String) = path(JsonField.of(path)) - - /** - * Sets [Builder.path] to an arbitrary JSON value. - * - * You should usually call [Builder.path] with a well-typed [String] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. - */ - fun path(path: JsonField) = apply { this.path = path } - - /** The value to use for filtering */ - fun value(value: String) = value(JsonField.of(value)) - - /** - * Sets [Builder.value] to an arbitrary JSON value. - * - * You should usually call [Builder.value] with a well-typed [String] value instead. This - * method is primarily for setting the field to an undocumented or not yet supported value. - */ - fun value(value: JsonField) = apply { this.value = value } - - fun additionalProperties(additionalProperties: Map) = apply { - this.additionalProperties.clear() - putAllAdditionalProperties(additionalProperties) - } - - fun putAdditionalProperty(key: String, value: JsonValue) = apply { - additionalProperties.put(key, value) - } - - fun putAllAdditionalProperties(additionalProperties: Map) = apply { - this.additionalProperties.putAll(additionalProperties) - } - - fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } - - fun removeAllAdditionalProperties(keys: Set) = apply { - keys.forEach(::removeAdditionalProperty) - } - - /** - * Returns an immutable instance of [SingleFilterConfig]. - * - * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .operator() - * .path() - * .value() - * ``` - * - * @throws IllegalStateException if any required field is unset. - */ - fun build(): SingleFilterConfig = - SingleFilterConfig( - checkRequired("operator", operator), - checkRequired("path", path), - checkRequired("value", value), - additionalProperties.toMutableMap(), - ) - } - - private var validated: Boolean = false - - fun validate(): SingleFilterConfig = apply { - if (validated) { - return@apply - } - - operator().validate() - path() - value() - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: CourierInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic - internal fun validity(): Int = - (operator.asKnown().getOrNull()?.validity() ?: 0) + - (if (path.asKnown().isPresent) 1 else 0) + - (if (value.asKnown().isPresent) 1 else 0) - - /** The operator to use for filtering */ - class Operator @JsonCreator private constructor(private val value: JsonField) : Enum { - - /** - * Returns this class instance's raw value. - * - * This is usually only useful if this instance was deserialized from data that doesn't - * match any known member, and you want to know that value. For example, if the SDK is on an - * older version than the API, then the API may respond with new members that the SDK is - * unaware of. - */ - @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value - - companion object { - - @JvmField val ENDS_WITH = of("ENDS_WITH") - - @JvmField val EQ = of("EQ") - - @JvmField val EXISTS = of("EXISTS") - - @JvmField val GT = of("GT") - - @JvmField val GTE = of("GTE") - - @JvmField val INCLUDES = of("INCLUDES") - - @JvmField val IS_AFTER = of("IS_AFTER") - - @JvmField val IS_BEFORE = of("IS_BEFORE") - - @JvmField val LT = of("LT") - - @JvmField val LTE = of("LTE") - - @JvmField val NEQ = of("NEQ") - - @JvmField val OMIT = of("OMIT") - - @JvmField val STARTS_WITH = of("STARTS_WITH") - - @JvmField val AND = of("AND") - - @JvmField val OR = of("OR") - - @JvmStatic fun of(value: String) = Operator(JsonField.of(value)) - } - - /** An enum containing [Operator]'s known values. */ - enum class Known { - ENDS_WITH, - EQ, - EXISTS, - GT, - GTE, - INCLUDES, - IS_AFTER, - IS_BEFORE, - LT, - LTE, - NEQ, - OMIT, - STARTS_WITH, - AND, - OR, - } - - /** - * An enum containing [Operator]'s known values, as well as an [_UNKNOWN] member. - * - * An instance of [Operator] can contain an unknown value in a couple of cases: - * - It was deserialized from data that doesn't match any known member. For example, if the - * SDK is on an older version than the API, then the API may respond with new members that - * the SDK is unaware of. - * - It was constructed with an arbitrary value using the [of] method. - */ - enum class Value { - ENDS_WITH, - EQ, - EXISTS, - GT, - GTE, - INCLUDES, - IS_AFTER, - IS_BEFORE, - LT, - LTE, - NEQ, - OMIT, - STARTS_WITH, - AND, - OR, - /** An enum member indicating that [Operator] was instantiated with an unknown value. */ - _UNKNOWN, - } - - /** - * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] - * if the class was instantiated with an unknown value. - * - * Use the [known] method instead if you're certain the value is always known or if you want - * to throw for the unknown case. - */ - fun value(): Value = - when (this) { - ENDS_WITH -> Value.ENDS_WITH - EQ -> Value.EQ - EXISTS -> Value.EXISTS - GT -> Value.GT - GTE -> Value.GTE - INCLUDES -> Value.INCLUDES - IS_AFTER -> Value.IS_AFTER - IS_BEFORE -> Value.IS_BEFORE - LT -> Value.LT - LTE -> Value.LTE - NEQ -> Value.NEQ - OMIT -> Value.OMIT - STARTS_WITH -> Value.STARTS_WITH - AND -> Value.AND - OR -> Value.OR - else -> Value._UNKNOWN - } - - /** - * Returns an enum member corresponding to this class instance's value. - * - * Use the [value] method instead if you're uncertain the value is always known and don't - * want to throw for the unknown case. - * - * @throws CourierInvalidDataException if this class instance's value is a not a known - * member. - */ - fun known(): Known = - when (this) { - ENDS_WITH -> Known.ENDS_WITH - EQ -> Known.EQ - EXISTS -> Known.EXISTS - GT -> Known.GT - GTE -> Known.GTE - INCLUDES -> Known.INCLUDES - IS_AFTER -> Known.IS_AFTER - IS_BEFORE -> Known.IS_BEFORE - LT -> Known.LT - LTE -> Known.LTE - NEQ -> Known.NEQ - OMIT -> Known.OMIT - STARTS_WITH -> Known.STARTS_WITH - AND -> Known.AND - OR -> Known.OR - else -> throw CourierInvalidDataException("Unknown Operator: $value") - } - - /** - * Returns this class instance's primitive wire representation. - * - * This differs from the [toString] method because that method is primarily for debugging - * and generally doesn't throw. - * - * @throws CourierInvalidDataException if this class instance's value does not have the - * expected primitive type. - */ - fun asString(): String = - _value().asString().orElseThrow { CourierInvalidDataException("Value is not a String") } - - private var validated: Boolean = false - - fun validate(): Operator = apply { - if (validated) { - return@apply - } - - known() - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: CourierInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object - * recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is Operator && value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is SingleFilterConfig && - operator == other.operator && - path == other.path && - value == other.value && - additionalProperties == other.additionalProperties - } - - private val hashCode: Int by lazy { Objects.hash(operator, path, value, additionalProperties) } - - override fun hashCode(): Int = hashCode - - override fun toString() = - "SingleFilterConfig{operator=$operator, path=$path, value=$value, additionalProperties=$additionalProperties}" -} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/AudienceFilterConfigTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/AudienceFilterConfigTest.kt new file mode 100644 index 00000000..42eb0fb0 --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/AudienceFilterConfigTest.kt @@ -0,0 +1,60 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models + +import com.courier.core.jsonMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class AudienceFilterConfigTest { + + @Test + fun create() { + val audienceFilterConfig = + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) + .build() + + assertThat(audienceFilterConfig.filters()) + .containsExactly( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val audienceFilterConfig = + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) + .build() + + val roundtrippedAudienceFilterConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(audienceFilterConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAudienceFilterConfig).isEqualTo(audienceFilterConfig) + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/ElementalChannelNodeTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/ElementalChannelNodeTest.kt index a6fc5984..39d5c45b 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/ElementalChannelNodeTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/ElementalChannelNodeTest.kt @@ -31,7 +31,7 @@ internal class ElementalChannelNodeTest { assertThat(elementalChannelNode.if_()).contains("if") assertThat(elementalChannelNode.loop()).contains("loop") assertThat(elementalChannelNode.ref()).contains("ref") - assertThat(elementalChannelNode.channel()).isEqualTo("email") + assertThat(elementalChannelNode.channel()).contains("email") assertThat(elementalChannelNode.raw()) .contains( ElementalChannelNode.Raw.builder() diff --git a/courier-java-core/src/test/kotlin/com/courier/models/FilterConfigTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/FilterConfigTest.kt new file mode 100644 index 00000000..7483e4cf --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/FilterConfigTest.kt @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models + +import com.courier.core.jsonMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FilterConfigTest { + + @Test + fun create() { + val filterConfig = + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + + assertThat(filterConfig.operator()).isEqualTo("operator") + assertThat(filterConfig.filters().getOrNull()).containsExactly() + assertThat(filterConfig.path()).contains("path") + assertThat(filterConfig.value()).contains("value") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val filterConfig = + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + + val roundtrippedFilterConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(filterConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFilterConfig).isEqualTo(filterConfig) + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceListResponseTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceListResponseTest.kt index ac1c0c6d..6594c4a5 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceListResponseTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceListResponseTest.kt @@ -3,6 +3,8 @@ package com.courier.models.audiences import com.courier.core.jsonMapper +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import com.courier.models.Paging import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import org.assertj.core.api.Assertions.assertThat @@ -19,15 +21,21 @@ internal class AudienceListResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) .paging(Paging.builder().more(true).cursor("cursor").build()) @@ -39,15 +47,21 @@ internal class AudienceListResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) assertThat(audienceListResponse.paging()) @@ -64,15 +78,21 @@ internal class AudienceListResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) .paging(Paging.builder().more(true).cursor("cursor").build()) diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceTest.kt index d2ff5000..1f10f768 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceTest.kt @@ -3,6 +3,8 @@ package com.courier.models.audiences import com.courier.core.jsonMapper +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -16,32 +18,42 @@ internal class AudienceTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() assertThat(audience.id()).isEqualTo("id") assertThat(audience.createdAt()).isEqualTo("created_at") assertThat(audience.description()).isEqualTo("description") - assertThat(audience.filter()) - .isEqualTo( - Filter.ofSingleFilterConfig( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - ) assertThat(audience.name()).isEqualTo("name") assertThat(audience.updatedAt()).isEqualTo("updated_at") + assertThat(audience.filter()) + .contains( + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) + .build() + ) + assertThat(audience.operator()).contains(Audience.Operator.AND) } @Test @@ -52,15 +64,21 @@ internal class AudienceTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() val roundtrippedAudience = diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateParamsTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateParamsTest.kt index 9f793c64..33942f70 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateParamsTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateParamsTest.kt @@ -2,6 +2,8 @@ package com.courier.models.audiences +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,13 +15,19 @@ internal class AudienceUpdateParamsTest { .audienceId("audience_id") .description("description") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) .name("name") + .operator(AudienceUpdateParams.Operator.AND) .build() } @@ -39,13 +47,19 @@ internal class AudienceUpdateParamsTest { .audienceId("audience_id") .description("description") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) .name("name") + .operator(AudienceUpdateParams.Operator.AND) .build() val body = params._body() @@ -53,15 +67,19 @@ internal class AudienceUpdateParamsTest { assertThat(body.description()).contains("description") assertThat(body.filter()) .contains( - Filter.ofSingleFilterConfig( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) + .build() ) assertThat(body.name()).contains("name") + assertThat(body.operator()).contains(AudienceUpdateParams.Operator.AND) } @Test diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateResponseTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateResponseTest.kt index 7917f856..ad48352a 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateResponseTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/audiences/AudienceUpdateResponseTest.kt @@ -3,6 +3,8 @@ package com.courier.models.audiences import com.courier.core.jsonMapper +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -18,15 +20,21 @@ internal class AudienceUpdateResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) .build() @@ -37,15 +45,21 @@ internal class AudienceUpdateResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) } @@ -60,15 +74,21 @@ internal class AudienceUpdateResponseTest { .id("id") .createdAt("created_at") .description("description") + .name("name") + .updatedAt("updated_at") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) - .name("name") - .updatedAt("updated_at") + .operator(Audience.Operator.AND) .build() ) .build() diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/FilterTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/FilterTest.kt deleted file mode 100644 index 9ab1d655..00000000 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/FilterTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.JsonValue -import com.courier.core.jsonMapper -import com.courier.errors.CourierInvalidDataException -import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.EnumSource - -internal class FilterTest { - - @Test - fun ofSingleFilterConfig() { - val singleFilterConfig = - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - - val filter = Filter.ofSingleFilterConfig(singleFilterConfig) - - assertThat(filter.singleFilterConfig()).contains(singleFilterConfig) - assertThat(filter.nestedFilterConfig()).isEmpty - } - - @Test - fun ofSingleFilterConfigRoundtrip() { - val jsonMapper = jsonMapper() - val filter = - Filter.ofSingleFilterConfig( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - - val roundtrippedFilter = - jsonMapper.readValue(jsonMapper.writeValueAsString(filter), jacksonTypeRef()) - - assertThat(roundtrippedFilter).isEqualTo(filter) - } - - @Test - fun ofNestedFilterConfig() { - val nestedFilterConfig = - NestedFilterConfig.builder() - .operator(NestedFilterConfig.Operator.ENDS_WITH) - .addRule( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - .build() - - val filter = Filter.ofNestedFilterConfig(nestedFilterConfig) - - assertThat(filter.singleFilterConfig()).isEmpty - assertThat(filter.nestedFilterConfig()).contains(nestedFilterConfig) - } - - @Test - fun ofNestedFilterConfigRoundtrip() { - val jsonMapper = jsonMapper() - val filter = - Filter.ofNestedFilterConfig( - NestedFilterConfig.builder() - .operator(NestedFilterConfig.Operator.ENDS_WITH) - .addRule( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - .build() - ) - - val roundtrippedFilter = - jsonMapper.readValue(jsonMapper.writeValueAsString(filter), jacksonTypeRef()) - - assertThat(roundtrippedFilter).isEqualTo(filter) - } - - enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { - BOOLEAN(JsonValue.from(false)), - STRING(JsonValue.from("invalid")), - INTEGER(JsonValue.from(-1)), - FLOAT(JsonValue.from(3.14)), - ARRAY(JsonValue.from(listOf("invalid", "array"))), - } - - @ParameterizedTest - @EnumSource - fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { - val filter = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) - - val e = assertThrows { filter.validate() } - assertThat(e).hasMessageStartingWith("Unknown ") - } -} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/NestedFilterConfigTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/NestedFilterConfigTest.kt deleted file mode 100644 index 943f4e35..00000000 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/NestedFilterConfigTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.jsonMapper -import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -internal class NestedFilterConfigTest { - - @Test - fun create() { - val nestedFilterConfig = - NestedFilterConfig.builder() - .operator(NestedFilterConfig.Operator.ENDS_WITH) - .addRule( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - .build() - - assertThat(nestedFilterConfig.operator()).isEqualTo(NestedFilterConfig.Operator.ENDS_WITH) - assertThat(nestedFilterConfig.rules()) - .containsExactly( - Filter.ofSingleFilterConfig( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - ) - } - - @Test - fun roundtrip() { - val jsonMapper = jsonMapper() - val nestedFilterConfig = - NestedFilterConfig.builder() - .operator(NestedFilterConfig.Operator.ENDS_WITH) - .addRule( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - ) - .build() - - val roundtrippedNestedFilterConfig = - jsonMapper.readValue( - jsonMapper.writeValueAsString(nestedFilterConfig), - jacksonTypeRef(), - ) - - assertThat(roundtrippedNestedFilterConfig).isEqualTo(nestedFilterConfig) - } -} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/audiences/SingleFilterConfigTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/audiences/SingleFilterConfigTest.kt deleted file mode 100644 index 7a8bdfd5..00000000 --- a/courier-java-core/src/test/kotlin/com/courier/models/audiences/SingleFilterConfigTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.courier.models.audiences - -import com.courier.core.jsonMapper -import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -internal class SingleFilterConfigTest { - - @Test - fun create() { - val singleFilterConfig = - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - - assertThat(singleFilterConfig.operator()).isEqualTo(SingleFilterConfig.Operator.ENDS_WITH) - assertThat(singleFilterConfig.path()).isEqualTo("path") - assertThat(singleFilterConfig.value()).isEqualTo("value") - } - - @Test - fun roundtrip() { - val jsonMapper = jsonMapper() - val singleFilterConfig = - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") - .build() - - val roundtrippedSingleFilterConfig = - jsonMapper.readValue( - jsonMapper.writeValueAsString(singleFilterConfig), - jacksonTypeRef(), - ) - - assertThat(roundtrippedSingleFilterConfig).isEqualTo(singleFilterConfig) - } -} diff --git a/courier-java-core/src/test/kotlin/com/courier/services/async/AudienceServiceAsyncTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/async/AudienceServiceAsyncTest.kt index cf2e9935..05dc648c 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/async/AudienceServiceAsyncTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/async/AudienceServiceAsyncTest.kt @@ -4,10 +4,11 @@ package com.courier.services.async import com.courier.TestServerExtension import com.courier.client.okhttp.CourierOkHttpClientAsync +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import com.courier.models.audiences.AudienceListMembersParams import com.courier.models.audiences.AudienceListParams import com.courier.models.audiences.AudienceUpdateParams -import com.courier.models.audiences.SingleFilterConfig import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -47,13 +48,19 @@ internal class AudienceServiceAsyncTest { .audienceId("audience_id") .description("description") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) .name("name") + .operator(AudienceUpdateParams.Operator.AND) .build() ) diff --git a/courier-java-core/src/test/kotlin/com/courier/services/blocking/AudienceServiceTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/blocking/AudienceServiceTest.kt index 07f1b335..3fc654a6 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/blocking/AudienceServiceTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/blocking/AudienceServiceTest.kt @@ -4,10 +4,11 @@ package com.courier.services.blocking import com.courier.TestServerExtension import com.courier.client.okhttp.CourierOkHttpClient +import com.courier.models.AudienceFilterConfig +import com.courier.models.FilterConfig import com.courier.models.audiences.AudienceListMembersParams import com.courier.models.audiences.AudienceListParams import com.courier.models.audiences.AudienceUpdateParams -import com.courier.models.audiences.SingleFilterConfig import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -46,13 +47,19 @@ internal class AudienceServiceTest { .audienceId("audience_id") .description("description") .filter( - SingleFilterConfig.builder() - .operator(SingleFilterConfig.Operator.ENDS_WITH) - .path("path") - .value("value") + AudienceFilterConfig.builder() + .addFilter( + FilterConfig.builder() + .operator("operator") + .filters(listOf()) + .path("path") + .value("value") + .build() + ) .build() ) .name("name") + .operator(AudienceUpdateParams.Operator.AND) .build() ) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts new file mode 100755 index 00000000..729e6f22 --- /dev/null +++ b/scripts/upload-artifacts @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# ANSI Color Codes +GREEN='\033[32m' +RED='\033[31m' +NC='\033[0m' # No Color + +log_error() { + local msg="$1" + local headers="$2" + local body="$3" + echo -e "${RED}${msg}${NC}" + [[ -f "$headers" ]] && echo -e "${RED}Headers:$(cat "$headers")${NC}" + echo -e "${RED}Body: ${body}${NC}" + exit 1 +} + +upload_file() { + local file_name="$1" + local tmp_headers + tmp_headers=$(mktemp) + + if [ -f "$file_name" ]; then + echo -e "${GREEN}Processing file: $file_name${NC}" + pkg_file_name="mvn${file_name#./build/local-maven-repo}" + + # Get signed URL for uploading artifact file + signed_url_response=$(curl -X POST -G "$URL" \ + -sS --retry 5 \ + -D "$tmp_headers" \ + --data-urlencode "filename=$pkg_file_name" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + + # Validate JSON and extract URL + if ! signed_url=$(echo "$signed_url_response" | jq -e -r '.url' 2>/dev/null) || [[ "$signed_url" == "null" ]]; then + log_error "Failed to get valid signed URL" "$tmp_headers" "$signed_url_response" + fi + + # Set content-type based on file extension + local extension="${file_name##*.}" + local content_type + case "$extension" in + jar) content_type="application/java-archive" ;; + md5|sha1|sha256|sha512) content_type="text/plain" ;; + module) content_type="application/json" ;; + pom|xml) content_type="application/xml" ;; + *) content_type="application/octet-stream" ;; + esac + + # Upload file + upload_response=$(curl -v -X PUT \ + --retry 5 \ + -D "$tmp_headers" \ + -H "Content-Type: $content_type" \ + --data-binary "@${file_name}" "$signed_url" 2>&1) + + if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then + log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + fi + + # Insert small throttle to reduce rate limiting risk + sleep 0.1 + fi +} + +walk_tree() { + local current_dir="$1" + + for entry in "$current_dir"/*; do + # Check that entry is valid + [ -e "$entry" ] || [ -h "$entry" ] || continue + + if [ -d "$entry" ]; then + walk_tree "$entry" + else + upload_file "$entry" + fi + done +} + +cd "$(dirname "$0")/.." + +echo "::group::Creating local Maven content" +./gradlew publishMavenPublicationToLocalFileSystemRepository -PpublishLocal +echo "::endgroup::" + +echo "::group::Uploading to pkg.stainless.com" +walk_tree "./build/local-maven-repo" +echo "::endgroup::" + +echo "::group::Generating instructions" +echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +echo "::endgroup::"