From b35a73a0490def16c33f9eb125e02221fb962a40 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 01:26:31 +0000 Subject: [PATCH 01/11] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3c2db8b..2443510 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-10f7ae53f4fe4f2394c22788b648d9db742a178ed41a87beb39de741660e646b.yml -openapi_spec_hash: 9885c47a02677471a38f16dddbad1823 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-f6fec0ae4fa4572aefa111e660f98f6acfb6149c22cbd413bd3defad6c100478.yml +openapi_spec_hash: a82bf07982eae3814e8a60eb368e0ce5 config_hash: 6f10592c7d0c3bafefc1271472283217 From 8588622a8b75ea541a9be7fc2a47a4ddd1554416 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 02:00:00 +0000 Subject: [PATCH 02/11] feat(api): manual updates --- .stats.yml | 4 +- .../api/models/brand/BrandAiProductParams.kt | 490 ++++++ .../models/brand/BrandAiProductResponse.kt | 1382 +++++++++++++++++ .../api/services/async/BrandServiceAsync.kt | 31 + .../services/async/BrandServiceAsyncImpl.kt | 40 + .../api/services/blocking/BrandService.kt | 31 + .../api/services/blocking/BrandServiceImpl.kt | 37 + .../models/brand/BrandAiProductParamsTest.kt | 33 + .../brand/BrandAiProductResponseTest.kt | 91 ++ .../services/async/BrandServiceAsyncTest.kt | 20 + .../api/services/blocking/BrandServiceTest.kt | 19 + 11 files changed, 2176 insertions(+), 2 deletions(-) create mode 100644 brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductParams.kt create mode 100644 brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt create mode 100644 brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt create mode 100644 brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt diff --git a/.stats.yml b/.stats.yml index 2443510..a831e36 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 15 +configured_endpoints: 16 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-f6fec0ae4fa4572aefa111e660f98f6acfb6149c22cbd413bd3defad6c100478.yml openapi_spec_hash: a82bf07982eae3814e8a60eb368e0ce5 -config_hash: 6f10592c7d0c3bafefc1271472283217 +config_hash: c3aaaa9794dba44d524c06591ab17894 diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductParams.kt new file mode 100644 index 0000000..8e1018b --- /dev/null +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductParams.kt @@ -0,0 +1,490 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.branddev.api.models.brand + +import com.branddev.api.core.ExcludeMissing +import com.branddev.api.core.JsonField +import com.branddev.api.core.JsonMissing +import com.branddev.api.core.JsonValue +import com.branddev.api.core.Params +import com.branddev.api.core.checkRequired +import com.branddev.api.core.http.Headers +import com.branddev.api.core.http.QueryParams +import com.branddev.api.errors.BrandDevInvalidDataException +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 + +/** + * Beta feature: Given a single URL, determines if it is a product detail page, classifies the + * platform/product type, and extracts the product information. Supports Amazon, TikTok Shop, Etsy, + * and generic ecommerce sites. + */ +class BrandAiProductParams +private constructor( + private val body: Body, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + /** + * The product page URL to extract product data from. + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun url(): String = body.url() + + /** + * Optional timeout in milliseconds for the request. Maximum allowed value is 300000ms (5 + * minutes). + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeoutMs(): Optional = body.timeoutMs() + + /** + * Returns the raw JSON value of [url]. + * + * Unlike [url], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _url(): JsonField = body._url() + + /** + * Returns the raw JSON value of [timeoutMs]. + * + * Unlike [timeoutMs], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _timeoutMs(): JsonField = body._timeoutMs() + + fun _additionalBodyProperties(): Map = body._additionalProperties() + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [BrandAiProductParams]. + * + * The following fields are required: + * ```java + * .url() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BrandAiProductParams]. */ + class Builder internal constructor() { + + private var body: Body.Builder = Body.builder() + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(brandAiProductParams: BrandAiProductParams) = apply { + body = brandAiProductParams.body.toBuilder() + additionalHeaders = brandAiProductParams.additionalHeaders.toBuilder() + additionalQueryParams = brandAiProductParams.additionalQueryParams.toBuilder() + } + + /** + * Sets the entire request body. + * + * This is generally only useful if you are already constructing the body separately. + * Otherwise, it's more convenient to use the top-level setters instead: + * - [url] + * - [timeoutMs] + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + /** The product page URL to extract product data from. */ + fun url(url: String) = apply { body.url(url) } + + /** + * Sets [Builder.url] to an arbitrary JSON value. + * + * You should usually call [Builder.url] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun url(url: JsonField) = apply { body.url(url) } + + /** + * Optional timeout in milliseconds for the request. Maximum allowed value is 300000ms (5 + * minutes). + */ + fun timeoutMs(timeoutMs: Long) = apply { body.timeoutMs(timeoutMs) } + + /** + * Sets [Builder.timeoutMs] to an arbitrary JSON value. + * + * You should usually call [Builder.timeoutMs] with a well-typed [Long] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun timeoutMs(timeoutMs: JsonField) = apply { body.timeoutMs(timeoutMs) } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + body.additionalProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + body.putAdditionalProperty(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + body.putAllAdditionalProperties(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { body.removeAdditionalProperty(key) } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + body.removeAllAdditionalProperties(keys) + } + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [BrandAiProductParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .url() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): BrandAiProductParams = + BrandAiProductParams( + body.build(), + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _body(): Body = body + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + class Body + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val url: JsonField, + private val timeoutMs: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(), + @JsonProperty("timeoutMS") @ExcludeMissing timeoutMs: JsonField = JsonMissing.of(), + ) : this(url, timeoutMs, mutableMapOf()) + + /** + * The product page URL to extract product data from. + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun url(): String = url.getRequired("url") + + /** + * Optional timeout in milliseconds for the request. Maximum allowed value is 300000ms (5 + * minutes). + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun timeoutMs(): Optional = timeoutMs.getOptional("timeoutMS") + + /** + * Returns the raw JSON value of [url]. + * + * Unlike [url], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("url") @ExcludeMissing fun _url(): JsonField = url + + /** + * Returns the raw JSON value of [timeoutMs]. + * + * Unlike [timeoutMs], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("timeoutMS") @ExcludeMissing fun _timeoutMs(): JsonField = timeoutMs + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [Body]. + * + * The following fields are required: + * ```java + * .url() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var url: JsonField? = null + private var timeoutMs: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + url = body.url + timeoutMs = body.timeoutMs + additionalProperties = body.additionalProperties.toMutableMap() + } + + /** The product page URL to extract product data from. */ + fun url(url: String) = url(JsonField.of(url)) + + /** + * Sets [Builder.url] to an arbitrary JSON value. + * + * You should usually call [Builder.url] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun url(url: JsonField) = apply { this.url = url } + + /** + * Optional timeout in milliseconds for the request. Maximum allowed value is 300000ms + * (5 minutes). + */ + fun timeoutMs(timeoutMs: Long) = timeoutMs(JsonField.of(timeoutMs)) + + /** + * Sets [Builder.timeoutMs] to an arbitrary JSON value. + * + * You should usually call [Builder.timeoutMs] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun timeoutMs(timeoutMs: JsonField) = apply { this.timeoutMs = timeoutMs } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Body]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .url() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): Body = + Body(checkRequired("url", url), timeoutMs, additionalProperties.toMutableMap()) + } + + private var validated: Boolean = false + + fun validate(): Body = apply { + if (validated) { + return@apply + } + + url() + timeoutMs() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (url.asKnown().isPresent) 1 else 0) + (if (timeoutMs.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Body && + url == other.url && + timeoutMs == other.timeoutMs && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(url, timeoutMs, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{url=$url, timeoutMs=$timeoutMs, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is BrandAiProductParams && + body == other.body && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams) + + override fun toString() = + "BrandAiProductParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt new file mode 100644 index 0000000..3beb8ee --- /dev/null +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt @@ -0,0 +1,1382 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.branddev.api.models.brand + +import com.branddev.api.core.Enum +import com.branddev.api.core.ExcludeMissing +import com.branddev.api.core.JsonField +import com.branddev.api.core.JsonMissing +import com.branddev.api.core.JsonValue +import com.branddev.api.core.checkKnown +import com.branddev.api.core.checkRequired +import com.branddev.api.core.toImmutable +import com.branddev.api.errors.BrandDevInvalidDataException +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 BrandAiProductResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val isProductPage: JsonField, + private val platform: JsonField, + private val product: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("is_product_page") + @ExcludeMissing + isProductPage: JsonField = JsonMissing.of(), + @JsonProperty("platform") @ExcludeMissing platform: JsonField = JsonMissing.of(), + @JsonProperty("product") @ExcludeMissing product: JsonField = JsonMissing.of(), + ) : this(isProductPage, platform, product, mutableMapOf()) + + /** + * Whether the given URL is a product detail page + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun isProductPage(): Optional = isProductPage.getOptional("is_product_page") + + /** + * The detected ecommerce platform, or null if not a product page + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun platform(): Optional = platform.getOptional("platform") + + /** + * The extracted product data, or null if not a product page + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun product(): Optional = product.getOptional("product") + + /** + * Returns the raw JSON value of [isProductPage]. + * + * Unlike [isProductPage], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("is_product_page") + @ExcludeMissing + fun _isProductPage(): JsonField = isProductPage + + /** + * Returns the raw JSON value of [platform]. + * + * Unlike [platform], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("platform") @ExcludeMissing fun _platform(): JsonField = platform + + /** + * Returns the raw JSON value of [product]. + * + * Unlike [product], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("product") @ExcludeMissing fun _product(): JsonField = product + + @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 [BrandAiProductResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BrandAiProductResponse]. */ + class Builder internal constructor() { + + private var isProductPage: JsonField = JsonMissing.of() + private var platform: JsonField = JsonMissing.of() + private var product: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(brandAiProductResponse: BrandAiProductResponse) = apply { + isProductPage = brandAiProductResponse.isProductPage + platform = brandAiProductResponse.platform + product = brandAiProductResponse.product + additionalProperties = brandAiProductResponse.additionalProperties.toMutableMap() + } + + /** Whether the given URL is a product detail page */ + fun isProductPage(isProductPage: Boolean) = isProductPage(JsonField.of(isProductPage)) + + /** + * Sets [Builder.isProductPage] to an arbitrary JSON value. + * + * You should usually call [Builder.isProductPage] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun isProductPage(isProductPage: JsonField) = apply { + this.isProductPage = isProductPage + } + + /** The detected ecommerce platform, or null if not a product page */ + fun platform(platform: Platform?) = platform(JsonField.ofNullable(platform)) + + /** Alias for calling [Builder.platform] with `platform.orElse(null)`. */ + fun platform(platform: Optional) = platform(platform.getOrNull()) + + /** + * Sets [Builder.platform] to an arbitrary JSON value. + * + * You should usually call [Builder.platform] with a well-typed [Platform] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun platform(platform: JsonField) = apply { this.platform = platform } + + /** The extracted product data, or null if not a product page */ + fun product(product: Product?) = product(JsonField.ofNullable(product)) + + /** Alias for calling [Builder.product] with `product.orElse(null)`. */ + fun product(product: Optional) = product(product.getOrNull()) + + /** + * Sets [Builder.product] to an arbitrary JSON value. + * + * You should usually call [Builder.product] with a well-typed [Product] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun product(product: JsonField) = apply { this.product = product } + + 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 [BrandAiProductResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): BrandAiProductResponse = + BrandAiProductResponse( + isProductPage, + platform, + product, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): BrandAiProductResponse = apply { + if (validated) { + return@apply + } + + isProductPage() + platform().ifPresent { it.validate() } + product().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + 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 (isProductPage.asKnown().isPresent) 1 else 0) + + (platform.asKnown().getOrNull()?.validity() ?: 0) + + (product.asKnown().getOrNull()?.validity() ?: 0) + + /** The detected ecommerce platform, or null if not a product page */ + class Platform @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 AMAZON = of("amazon") + + @JvmField val TIKTOK_SHOP = of("tiktok_shop") + + @JvmField val ETSY = of("etsy") + + @JvmField val GENERIC = of("generic") + + @JvmStatic fun of(value: String) = Platform(JsonField.of(value)) + } + + /** An enum containing [Platform]'s known values. */ + enum class Known { + AMAZON, + TIKTOK_SHOP, + ETSY, + GENERIC, + } + + /** + * An enum containing [Platform]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Platform] 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 { + AMAZON, + TIKTOK_SHOP, + ETSY, + GENERIC, + /** An enum member indicating that [Platform] 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) { + AMAZON -> Value.AMAZON + TIKTOK_SHOP -> Value.TIKTOK_SHOP + ETSY -> Value.ETSY + GENERIC -> Value.GENERIC + 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 BrandDevInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + AMAZON -> Known.AMAZON + TIKTOK_SHOP -> Known.TIKTOK_SHOP + ETSY -> Known.ETSY + GENERIC -> Known.GENERIC + else -> throw BrandDevInvalidDataException("Unknown Platform: $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 BrandDevInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BrandDevInvalidDataException("Value is not a String") + } + + private var validated: Boolean = false + + fun validate(): Platform = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + 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 Platform && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** The extracted product data, or null if not a product page */ + class Product + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val description: JsonField, + private val features: JsonField>, + private val name: JsonField, + private val tags: JsonField>, + private val targetAudience: JsonField>, + private val billingFrequency: JsonField, + private val category: JsonField, + private val currency: JsonField, + private val imageUrl: JsonField, + private val price: JsonField, + private val pricingModel: JsonField, + private val url: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("description") + @ExcludeMissing + description: JsonField = JsonMissing.of(), + @JsonProperty("features") + @ExcludeMissing + features: JsonField> = JsonMissing.of(), + @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), + @JsonProperty("tags") @ExcludeMissing tags: JsonField> = JsonMissing.of(), + @JsonProperty("target_audience") + @ExcludeMissing + targetAudience: JsonField> = JsonMissing.of(), + @JsonProperty("billing_frequency") + @ExcludeMissing + billingFrequency: JsonField = JsonMissing.of(), + @JsonProperty("category") + @ExcludeMissing + category: JsonField = JsonMissing.of(), + @JsonProperty("currency") + @ExcludeMissing + currency: JsonField = JsonMissing.of(), + @JsonProperty("image_url") + @ExcludeMissing + imageUrl: JsonField = JsonMissing.of(), + @JsonProperty("price") @ExcludeMissing price: JsonField = JsonMissing.of(), + @JsonProperty("pricing_model") + @ExcludeMissing + pricingModel: JsonField = JsonMissing.of(), + @JsonProperty("url") @ExcludeMissing url: JsonField = JsonMissing.of(), + ) : this( + description, + features, + name, + tags, + targetAudience, + billingFrequency, + category, + currency, + imageUrl, + price, + pricingModel, + url, + mutableMapOf(), + ) + + /** + * Description of the product + * + * @throws BrandDevInvalidDataException 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 description(): String = description.getRequired("description") + + /** + * List of product features + * + * @throws BrandDevInvalidDataException 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 features(): List = features.getRequired("features") + + /** + * Name of the product + * + * @throws BrandDevInvalidDataException 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 name(): String = name.getRequired("name") + + /** + * Tags associated with the product + * + * @throws BrandDevInvalidDataException 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 tags(): List = tags.getRequired("tags") + + /** + * Target audience for the product (array of strings) + * + * @throws BrandDevInvalidDataException 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 targetAudience(): List = targetAudience.getRequired("target_audience") + + /** + * Billing frequency for the product + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun billingFrequency(): Optional = + billingFrequency.getOptional("billing_frequency") + + /** + * Category of the product + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun category(): Optional = category.getOptional("category") + + /** + * Currency code for the price (e.g., USD, EUR) + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun currency(): Optional = currency.getOptional("currency") + + /** + * URL to the product image + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun imageUrl(): Optional = imageUrl.getOptional("image_url") + + /** + * Price of the product + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun price(): Optional = price.getOptional("price") + + /** + * Pricing model for the product + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun pricingModel(): Optional = pricingModel.getOptional("pricing_model") + + /** + * URL to the product page + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun url(): Optional = url.getOptional("url") + + /** + * Returns the raw JSON value of [description]. + * + * Unlike [description], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("description") + @ExcludeMissing + fun _description(): JsonField = description + + /** + * Returns the raw JSON value of [features]. + * + * Unlike [features], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("features") + @ExcludeMissing + fun _features(): JsonField> = features + + /** + * Returns the raw JSON value of [name]. + * + * Unlike [name], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("name") @ExcludeMissing fun _name(): JsonField = name + + /** + * Returns the raw JSON value of [tags]. + * + * Unlike [tags], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("tags") @ExcludeMissing fun _tags(): JsonField> = tags + + /** + * Returns the raw JSON value of [targetAudience]. + * + * Unlike [targetAudience], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("target_audience") + @ExcludeMissing + fun _targetAudience(): JsonField> = targetAudience + + /** + * Returns the raw JSON value of [billingFrequency]. + * + * Unlike [billingFrequency], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("billing_frequency") + @ExcludeMissing + fun _billingFrequency(): JsonField = billingFrequency + + /** + * Returns the raw JSON value of [category]. + * + * Unlike [category], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("category") @ExcludeMissing fun _category(): JsonField = category + + /** + * Returns the raw JSON value of [currency]. + * + * Unlike [currency], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("currency") @ExcludeMissing fun _currency(): JsonField = currency + + /** + * Returns the raw JSON value of [imageUrl]. + * + * Unlike [imageUrl], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("image_url") @ExcludeMissing fun _imageUrl(): JsonField = imageUrl + + /** + * Returns the raw JSON value of [price]. + * + * Unlike [price], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("price") @ExcludeMissing fun _price(): JsonField = price + + /** + * Returns the raw JSON value of [pricingModel]. + * + * Unlike [pricingModel], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("pricing_model") + @ExcludeMissing + fun _pricingModel(): JsonField = pricingModel + + /** + * Returns the raw JSON value of [url]. + * + * Unlike [url], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("url") @ExcludeMissing fun _url(): JsonField = url + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [Product]. + * + * The following fields are required: + * ```java + * .description() + * .features() + * .name() + * .tags() + * .targetAudience() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Product]. */ + class Builder internal constructor() { + + private var description: JsonField? = null + private var features: JsonField>? = null + private var name: JsonField? = null + private var tags: JsonField>? = null + private var targetAudience: JsonField>? = null + private var billingFrequency: JsonField = JsonMissing.of() + private var category: JsonField = JsonMissing.of() + private var currency: JsonField = JsonMissing.of() + private var imageUrl: JsonField = JsonMissing.of() + private var price: JsonField = JsonMissing.of() + private var pricingModel: JsonField = JsonMissing.of() + private var url: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(product: Product) = apply { + description = product.description + features = product.features.map { it.toMutableList() } + name = product.name + tags = product.tags.map { it.toMutableList() } + targetAudience = product.targetAudience.map { it.toMutableList() } + billingFrequency = product.billingFrequency + category = product.category + currency = product.currency + imageUrl = product.imageUrl + price = product.price + pricingModel = product.pricingModel + url = product.url + additionalProperties = product.additionalProperties.toMutableMap() + } + + /** Description of the product */ + fun description(description: String) = description(JsonField.of(description)) + + /** + * Sets [Builder.description] to an arbitrary JSON value. + * + * You should usually call [Builder.description] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun description(description: JsonField) = apply { + this.description = description + } + + /** List of product features */ + fun features(features: List) = features(JsonField.of(features)) + + /** + * Sets [Builder.features] to an arbitrary JSON value. + * + * You should usually call [Builder.features] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun features(features: JsonField>) = apply { + this.features = features.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [features]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addFeature(feature: String) = apply { + features = + (features ?: JsonField.of(mutableListOf())).also { + checkKnown("features", it).add(feature) + } + } + + /** Name of the product */ + fun name(name: String) = name(JsonField.of(name)) + + /** + * Sets [Builder.name] to an arbitrary JSON value. + * + * You should usually call [Builder.name] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun name(name: JsonField) = apply { this.name = name } + + /** Tags associated with the product */ + fun tags(tags: List) = tags(JsonField.of(tags)) + + /** + * Sets [Builder.tags] to an arbitrary JSON value. + * + * You should usually call [Builder.tags] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun tags(tags: JsonField>) = apply { + this.tags = tags.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [tags]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addTag(tag: String) = apply { + tags = + (tags ?: JsonField.of(mutableListOf())).also { checkKnown("tags", it).add(tag) } + } + + /** Target audience for the product (array of strings) */ + fun targetAudience(targetAudience: List) = + targetAudience(JsonField.of(targetAudience)) + + /** + * Sets [Builder.targetAudience] to an arbitrary JSON value. + * + * You should usually call [Builder.targetAudience] with a well-typed `List` + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun targetAudience(targetAudience: JsonField>) = apply { + this.targetAudience = targetAudience.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [Builder.targetAudience]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addTargetAudience(targetAudience: String) = apply { + this.targetAudience = + (this.targetAudience ?: JsonField.of(mutableListOf())).also { + checkKnown("targetAudience", it).add(targetAudience) + } + } + + /** Billing frequency for the product */ + fun billingFrequency(billingFrequency: BillingFrequency?) = + billingFrequency(JsonField.ofNullable(billingFrequency)) + + /** + * Alias for calling [Builder.billingFrequency] with `billingFrequency.orElse(null)`. + */ + fun billingFrequency(billingFrequency: Optional) = + billingFrequency(billingFrequency.getOrNull()) + + /** + * Sets [Builder.billingFrequency] to an arbitrary JSON value. + * + * You should usually call [Builder.billingFrequency] with a well-typed + * [BillingFrequency] value instead. This method is primarily for setting the field to + * an undocumented or not yet supported value. + */ + fun billingFrequency(billingFrequency: JsonField) = apply { + this.billingFrequency = billingFrequency + } + + /** Category of the product */ + fun category(category: String?) = category(JsonField.ofNullable(category)) + + /** Alias for calling [Builder.category] with `category.orElse(null)`. */ + fun category(category: Optional) = category(category.getOrNull()) + + /** + * Sets [Builder.category] to an arbitrary JSON value. + * + * You should usually call [Builder.category] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun category(category: JsonField) = apply { this.category = category } + + /** Currency code for the price (e.g., USD, EUR) */ + fun currency(currency: String?) = currency(JsonField.ofNullable(currency)) + + /** Alias for calling [Builder.currency] with `currency.orElse(null)`. */ + fun currency(currency: Optional) = currency(currency.getOrNull()) + + /** + * Sets [Builder.currency] to an arbitrary JSON value. + * + * You should usually call [Builder.currency] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun currency(currency: JsonField) = apply { this.currency = currency } + + /** URL to the product image */ + fun imageUrl(imageUrl: String?) = imageUrl(JsonField.ofNullable(imageUrl)) + + /** Alias for calling [Builder.imageUrl] with `imageUrl.orElse(null)`. */ + fun imageUrl(imageUrl: Optional) = imageUrl(imageUrl.getOrNull()) + + /** + * Sets [Builder.imageUrl] to an arbitrary JSON value. + * + * You should usually call [Builder.imageUrl] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun imageUrl(imageUrl: JsonField) = apply { this.imageUrl = imageUrl } + + /** Price of the product */ + fun price(price: Double?) = price(JsonField.ofNullable(price)) + + /** + * Alias for [Builder.price]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun price(price: Double) = price(price as Double?) + + /** Alias for calling [Builder.price] with `price.orElse(null)`. */ + fun price(price: Optional) = price(price.getOrNull()) + + /** + * Sets [Builder.price] to an arbitrary JSON value. + * + * You should usually call [Builder.price] with a well-typed [Double] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun price(price: JsonField) = apply { this.price = price } + + /** Pricing model for the product */ + fun pricingModel(pricingModel: PricingModel?) = + pricingModel(JsonField.ofNullable(pricingModel)) + + /** Alias for calling [Builder.pricingModel] with `pricingModel.orElse(null)`. */ + fun pricingModel(pricingModel: Optional) = + pricingModel(pricingModel.getOrNull()) + + /** + * Sets [Builder.pricingModel] to an arbitrary JSON value. + * + * You should usually call [Builder.pricingModel] with a well-typed [PricingModel] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun pricingModel(pricingModel: JsonField) = apply { + this.pricingModel = pricingModel + } + + /** URL to the product page */ + fun url(url: String?) = url(JsonField.ofNullable(url)) + + /** Alias for calling [Builder.url] with `url.orElse(null)`. */ + fun url(url: Optional) = url(url.getOrNull()) + + /** + * Sets [Builder.url] to an arbitrary JSON value. + * + * You should usually call [Builder.url] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun url(url: JsonField) = apply { this.url = url } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Product]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .description() + * .features() + * .name() + * .tags() + * .targetAudience() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): Product = + Product( + checkRequired("description", description), + checkRequired("features", features).map { it.toImmutable() }, + checkRequired("name", name), + checkRequired("tags", tags).map { it.toImmutable() }, + checkRequired("targetAudience", targetAudience).map { it.toImmutable() }, + billingFrequency, + category, + currency, + imageUrl, + price, + pricingModel, + url, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): Product = apply { + if (validated) { + return@apply + } + + description() + features() + name() + tags() + targetAudience() + billingFrequency().ifPresent { it.validate() } + category() + currency() + imageUrl() + price() + pricingModel().ifPresent { it.validate() } + url() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + 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 (description.asKnown().isPresent) 1 else 0) + + (features.asKnown().getOrNull()?.size ?: 0) + + (if (name.asKnown().isPresent) 1 else 0) + + (tags.asKnown().getOrNull()?.size ?: 0) + + (targetAudience.asKnown().getOrNull()?.size ?: 0) + + (billingFrequency.asKnown().getOrNull()?.validity() ?: 0) + + (if (category.asKnown().isPresent) 1 else 0) + + (if (currency.asKnown().isPresent) 1 else 0) + + (if (imageUrl.asKnown().isPresent) 1 else 0) + + (if (price.asKnown().isPresent) 1 else 0) + + (pricingModel.asKnown().getOrNull()?.validity() ?: 0) + + (if (url.asKnown().isPresent) 1 else 0) + + /** Billing frequency for the product */ + class BillingFrequency + @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 MONTHLY = of("monthly") + + @JvmField val YEARLY = of("yearly") + + @JvmField val ONE_TIME = of("one_time") + + @JvmField val USAGE_BASED = of("usage_based") + + @JvmStatic fun of(value: String) = BillingFrequency(JsonField.of(value)) + } + + /** An enum containing [BillingFrequency]'s known values. */ + enum class Known { + MONTHLY, + YEARLY, + ONE_TIME, + USAGE_BASED, + } + + /** + * An enum containing [BillingFrequency]'s known values, as well as an [_UNKNOWN] + * member. + * + * An instance of [BillingFrequency] 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 { + MONTHLY, + YEARLY, + ONE_TIME, + USAGE_BASED, + /** + * An enum member indicating that [BillingFrequency] 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) { + MONTHLY -> Value.MONTHLY + YEARLY -> Value.YEARLY + ONE_TIME -> Value.ONE_TIME + USAGE_BASED -> Value.USAGE_BASED + 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 BrandDevInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + MONTHLY -> Known.MONTHLY + YEARLY -> Known.YEARLY + ONE_TIME -> Known.ONE_TIME + USAGE_BASED -> Known.USAGE_BASED + else -> throw BrandDevInvalidDataException("Unknown BillingFrequency: $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 BrandDevInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BrandDevInvalidDataException("Value is not a String") + } + + private var validated: Boolean = false + + fun validate(): BillingFrequency = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + 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 BillingFrequency && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + /** Pricing model for the product */ + class PricingModel @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 PER_SEAT = of("per_seat") + + @JvmField val FLAT = of("flat") + + @JvmField val TIERED = of("tiered") + + @JvmField val FREEMIUM = of("freemium") + + @JvmField val CUSTOM = of("custom") + + @JvmStatic fun of(value: String) = PricingModel(JsonField.of(value)) + } + + /** An enum containing [PricingModel]'s known values. */ + enum class Known { + PER_SEAT, + FLAT, + TIERED, + FREEMIUM, + CUSTOM, + } + + /** + * An enum containing [PricingModel]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [PricingModel] 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 { + PER_SEAT, + FLAT, + TIERED, + FREEMIUM, + CUSTOM, + /** + * An enum member indicating that [PricingModel] 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) { + PER_SEAT -> Value.PER_SEAT + FLAT -> Value.FLAT + TIERED -> Value.TIERED + FREEMIUM -> Value.FREEMIUM + CUSTOM -> Value.CUSTOM + 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 BrandDevInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + PER_SEAT -> Known.PER_SEAT + FLAT -> Known.FLAT + TIERED -> Known.TIERED + FREEMIUM -> Known.FREEMIUM + CUSTOM -> Known.CUSTOM + else -> throw BrandDevInvalidDataException("Unknown PricingModel: $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 BrandDevInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + BrandDevInvalidDataException("Value is not a String") + } + + private var validated: Boolean = false + + fun validate(): PricingModel = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: BrandDevInvalidDataException) { + 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 PricingModel && 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 Product && + description == other.description && + features == other.features && + name == other.name && + tags == other.tags && + targetAudience == other.targetAudience && + billingFrequency == other.billingFrequency && + category == other.category && + currency == other.currency && + imageUrl == other.imageUrl && + price == other.price && + pricingModel == other.pricingModel && + url == other.url && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + description, + features, + name, + tags, + targetAudience, + billingFrequency, + category, + currency, + imageUrl, + price, + pricingModel, + url, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Product{description=$description, features=$features, name=$name, tags=$tags, targetAudience=$targetAudience, billingFrequency=$billingFrequency, category=$category, currency=$currency, imageUrl=$imageUrl, price=$price, pricingModel=$pricingModel, url=$url, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is BrandAiProductResponse && + isProductPage == other.isProductPage && + platform == other.platform && + product == other.product && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(isProductPage, platform, product, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "BrandAiProductResponse{isProductPage=$isProductPage, platform=$platform, product=$product, additionalProperties=$additionalProperties}" +} diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt index 4500458..55778f1 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt @@ -5,6 +5,8 @@ package com.branddev.api.services.async import com.branddev.api.core.ClientOptions import com.branddev.api.core.RequestOptions import com.branddev.api.core.http.HttpResponseFor +import com.branddev.api.models.brand.BrandAiProductParams +import com.branddev.api.models.brand.BrandAiProductResponse import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams @@ -62,6 +64,20 @@ interface BrandServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture + /** + * Beta feature: Given a single URL, determines if it is a product detail page, classifies the + * platform/product type, and extracts the product information. Supports Amazon, TikTok Shop, + * Etsy, and generic ecommerce sites. + */ + fun aiProduct(params: BrandAiProductParams): CompletableFuture = + aiProduct(params, RequestOptions.none()) + + /** @see aiProduct */ + fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + /** * Beta feature: Extract product information from a brand's website. Brand.dev will analyze the * website and return a list of products with details such as name, description, image, pricing, @@ -293,6 +309,21 @@ interface BrandServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture> + /** + * Returns a raw HTTP response for `post /brand/ai/product`, but is otherwise the same as + * [BrandServiceAsync.aiProduct]. + */ + fun aiProduct( + params: BrandAiProductParams + ): CompletableFuture> = + aiProduct(params, RequestOptions.none()) + + /** @see aiProduct */ + fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + /** * Returns a raw HTTP response for `post /brand/ai/products`, but is otherwise the same as * [BrandServiceAsync.aiProducts]. diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsyncImpl.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsyncImpl.kt index b041b94..4592ca1 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsyncImpl.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsyncImpl.kt @@ -15,6 +15,8 @@ import com.branddev.api.core.http.HttpResponseFor import com.branddev.api.core.http.json import com.branddev.api.core.http.parseable import com.branddev.api.core.prepareAsync +import com.branddev.api.models.brand.BrandAiProductParams +import com.branddev.api.models.brand.BrandAiProductResponse import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams @@ -67,6 +69,13 @@ class BrandServiceAsyncImpl internal constructor(private val clientOptions: Clie // get /brand/retrieve withRawResponse().retrieve(params, requestOptions).thenApply { it.parse() } + override fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // post /brand/ai/product + withRawResponse().aiProduct(params, requestOptions).thenApply { it.parse() } + override fun aiProducts( params: BrandAiProductsParams, requestOptions: RequestOptions, @@ -208,6 +217,37 @@ class BrandServiceAsyncImpl internal constructor(private val clientOptions: Clie } } + private val aiProductHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("brand", "ai", "product") + .body(json(clientOptions.jsonMapper, params._body())) + .build() + .prepareAsync(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + return request + .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) } + .thenApply { response -> + errorHandler.handle(response).parseable { + response + .use { aiProductHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } + private val aiProductsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt index b467fc0..e1b4d44 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt @@ -5,6 +5,8 @@ package com.branddev.api.services.blocking import com.branddev.api.core.ClientOptions import com.branddev.api.core.RequestOptions import com.branddev.api.core.http.HttpResponseFor +import com.branddev.api.models.brand.BrandAiProductParams +import com.branddev.api.models.brand.BrandAiProductResponse import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams @@ -62,6 +64,20 @@ interface BrandService { requestOptions: RequestOptions = RequestOptions.none(), ): BrandRetrieveResponse + /** + * Beta feature: Given a single URL, determines if it is a product detail page, classifies the + * platform/product type, and extracts the product information. Supports Amazon, TikTok Shop, + * Etsy, and generic ecommerce sites. + */ + fun aiProduct(params: BrandAiProductParams): BrandAiProductResponse = + aiProduct(params, RequestOptions.none()) + + /** @see aiProduct */ + fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): BrandAiProductResponse + /** * Beta feature: Extract product information from a brand's website. Brand.dev will analyze the * website and return a list of products with details such as name, description, image, pricing, @@ -276,6 +292,21 @@ interface BrandService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor + /** + * Returns a raw HTTP response for `post /brand/ai/product`, but is otherwise the same as + * [BrandService.aiProduct]. + */ + @MustBeClosed + fun aiProduct(params: BrandAiProductParams): HttpResponseFor = + aiProduct(params, RequestOptions.none()) + + /** @see aiProduct */ + @MustBeClosed + fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + /** * Returns a raw HTTP response for `post /brand/ai/products`, but is otherwise the same as * [BrandService.aiProducts]. diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandServiceImpl.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandServiceImpl.kt index 7881de3..1fa6a43 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandServiceImpl.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandServiceImpl.kt @@ -15,6 +15,8 @@ import com.branddev.api.core.http.HttpResponseFor import com.branddev.api.core.http.json import com.branddev.api.core.http.parseable import com.branddev.api.core.prepare +import com.branddev.api.models.brand.BrandAiProductParams +import com.branddev.api.models.brand.BrandAiProductResponse import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams @@ -66,6 +68,13 @@ class BrandServiceImpl internal constructor(private val clientOptions: ClientOpt // get /brand/retrieve withRawResponse().retrieve(params, requestOptions).parse() + override fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions, + ): BrandAiProductResponse = + // post /brand/ai/product + withRawResponse().aiProduct(params, requestOptions).parse() + override fun aiProducts( params: BrandAiProductsParams, requestOptions: RequestOptions, @@ -204,6 +213,34 @@ class BrandServiceImpl internal constructor(private val clientOptions: ClientOpt } } + private val aiProductHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun aiProduct( + params: BrandAiProductParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("brand", "ai", "product") + .body(json(clientOptions.jsonMapper, params._body())) + .build() + .prepare(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + val response = clientOptions.httpClient.execute(request, requestOptions) + return errorHandler.handle(response).parseable { + response + .use { aiProductHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + private val aiProductsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt new file mode 100644 index 0000000..d8b455d --- /dev/null +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.branddev.api.models.brand + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class BrandAiProductParamsTest { + + @Test + fun create() { + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + } + + @Test + fun body() { + val params = BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + + val body = params._body() + + assertThat(body.url()).isEqualTo("https://example.com") + assertThat(body.timeoutMs()).contains(1L) + } + + @Test + fun bodyWithoutOptionalFields() { + val params = BrandAiProductParams.builder().url("https://example.com").build() + + val body = params._body() + + assertThat(body.url()).isEqualTo("https://example.com") + } +} diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt new file mode 100644 index 0000000..7c9676c --- /dev/null +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt @@ -0,0 +1,91 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.branddev.api.models.brand + +import com.branddev.api.core.jsonMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class BrandAiProductResponseTest { + + @Test + fun create() { + val brandAiProductResponse = + BrandAiProductResponse.builder() + .isProductPage(true) + .platform(BrandAiProductResponse.Platform.AMAZON) + .product( + BrandAiProductResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + .build() + + assertThat(brandAiProductResponse.isProductPage()).contains(true) + assertThat(brandAiProductResponse.platform()) + .contains(BrandAiProductResponse.Platform.AMAZON) + assertThat(brandAiProductResponse.product()) + .contains( + BrandAiProductResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val brandAiProductResponse = + BrandAiProductResponse.builder() + .isProductPage(true) + .platform(BrandAiProductResponse.Platform.AMAZON) + .product( + BrandAiProductResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + .build() + + val roundtrippedBrandAiProductResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(brandAiProductResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedBrandAiProductResponse).isEqualTo(brandAiProductResponse) + } +} diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt index e600a78..85df72f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt @@ -5,6 +5,7 @@ package com.branddev.api.services.async import com.branddev.api.TestServerExtension import com.branddev.api.client.okhttp.BrandDevOkHttpClientAsync import com.branddev.api.core.JsonValue +import com.branddev.api.models.brand.BrandAiProductParams import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandFontsParams @@ -51,6 +52,25 @@ internal class BrandServiceAsyncTest { brand.validate() } + @Disabled("Prism tests are disabled") + @Test + fun aiProduct() { + val client = + BrandDevOkHttpClientAsync.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My API Key") + .build() + val brandServiceAsync = client.brand() + + val responseFuture = + brandServiceAsync.aiProduct( + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + ) + + val response = responseFuture.get() + response.validate() + } + @Disabled("Prism tests are disabled") @Test fun aiProducts() { diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt index c042765..f4cb373 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt @@ -5,6 +5,7 @@ package com.branddev.api.services.blocking import com.branddev.api.TestServerExtension import com.branddev.api.client.okhttp.BrandDevOkHttpClient import com.branddev.api.core.JsonValue +import com.branddev.api.models.brand.BrandAiProductParams import com.branddev.api.models.brand.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandFontsParams @@ -50,6 +51,24 @@ internal class BrandServiceTest { brand.validate() } + @Disabled("Prism tests are disabled") + @Test + fun aiProduct() { + val client = + BrandDevOkHttpClient.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My API Key") + .build() + val brandService = client.brand() + + val response = + brandService.aiProduct( + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + ) + + response.validate() + } + @Disabled("Prism tests are disabled") @Test fun aiProducts() { From 176daa6ade5920957e1afb073ab0f14b2dc93705 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 03:54:43 +0000 Subject: [PATCH 03/11] feat(api): api update --- .stats.yml | 4 +-- .../models/brand/BrandAiProductParamsTest.kt | 7 ++-- .../models/brand/BrandAiProductsParamsTest.kt | 6 ++-- .../models/brand/BrandAiQueryParamsTest.kt | 6 ++-- .../api/models/brand/BrandFontsParamsTest.kt | 8 +++-- .../BrandIdentifyFromTransactionParamsTest.kt | 6 ++-- .../brand/BrandPrefetchByEmailParamsTest.kt | 6 ++-- .../models/brand/BrandPrefetchParamsTest.kt | 6 ++-- .../brand/BrandRetrieveByEmailParamsTest.kt | 6 ++-- .../brand/BrandRetrieveByIsinParamsTest.kt | 6 ++-- .../brand/BrandRetrieveByNameParamsTest.kt | 6 ++-- .../brand/BrandRetrieveByTickerParamsTest.kt | 6 ++-- .../brand/BrandRetrieveNaicsParamsTest.kt | 6 ++-- .../models/brand/BrandRetrieveParamsTest.kt | 6 ++-- .../BrandRetrieveSimplifiedParamsTest.kt | 9 +++-- .../models/brand/BrandStyleguideParamsTest.kt | 6 ++-- .../api/services/ErrorHandlingTest.kt | 34 +++++++++---------- .../api/services/ServiceParamsTest.kt | 2 +- .../services/async/BrandServiceAsyncTest.kt | 30 ++++++++-------- .../api/services/blocking/BrandServiceTest.kt | 30 ++++++++-------- 20 files changed, 101 insertions(+), 95 deletions(-) diff --git a/.stats.yml b/.stats.yml index a831e36..a1fb573 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-f6fec0ae4fa4572aefa111e660f98f6acfb6149c22cbd413bd3defad6c100478.yml -openapi_spec_hash: a82bf07982eae3814e8a60eb368e0ce5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-ec0e897d65bee0947046681fd3cd465ee5b44c98b020594736fb9f274ee2c00b.yml +openapi_spec_hash: 9c76eb4b912f046fc93a030b9a7489a3 config_hash: c3aaaa9794dba44d524c06591ab17894 diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt index d8b455d..56369c7 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductParamsTest.kt @@ -9,17 +9,18 @@ internal class BrandAiProductParamsTest { @Test fun create() { - BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1000L).build() } @Test fun body() { - val params = BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + val params = + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1000L).build() val body = params._body() assertThat(body.url()).isEqualTo("https://example.com") - assertThat(body.timeoutMs()).contains(1L) + assertThat(body.timeoutMs()).contains(1000L) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt index 5d5ad09..fb23d73 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandAiProductsParamsTest { BrandAiProductsParams.Body.ByDomain.builder() .domain("domain") .maxProducts(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) .build() @@ -28,7 +28,7 @@ internal class BrandAiProductsParamsTest { BrandAiProductsParams.Body.ByDomain.builder() .domain("domain") .maxProducts(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) .build() @@ -41,7 +41,7 @@ internal class BrandAiProductsParamsTest { BrandAiProductsParams.Body.ByDomain.builder() .domain("domain") .maxProducts(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) ) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiQueryParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiQueryParamsTest.kt index 7449dd8..e721f3e 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiQueryParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiQueryParamsTest.kt @@ -40,7 +40,7 @@ internal class BrandAiQueryParamsTest { .termsAndConditions(true) .build() ) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -82,7 +82,7 @@ internal class BrandAiQueryParamsTest { .termsAndConditions(true) .build() ) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val body = params._body() @@ -118,7 +118,7 @@ internal class BrandAiQueryParamsTest { .termsAndConditions(true) .build() ) - assertThat(body.timeoutMs()).contains(1L) + assertThat(body.timeoutMs()).contains(1000L) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandFontsParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandFontsParamsTest.kt index 9ce2e95..5e6e635 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandFontsParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandFontsParamsTest.kt @@ -10,17 +10,19 @@ internal class BrandFontsParamsTest { @Test fun create() { - BrandFontsParams.builder().domain("domain").timeoutMs(1L).build() + BrandFontsParams.builder().domain("domain").timeoutMs(1000L).build() } @Test fun queryParams() { - val params = BrandFontsParams.builder().domain("domain").timeoutMs(1L).build() + val params = BrandFontsParams.builder().domain("domain").timeoutMs(1000L).build() val queryParams = params._queryParams() assertThat(queryParams) - .isEqualTo(QueryParams.builder().put("domain", "domain").put("timeoutMS", "1").build()) + .isEqualTo( + QueryParams.builder().put("domain", "domain").put("timeoutMS", "1000").build() + ) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandIdentifyFromTransactionParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandIdentifyFromTransactionParamsTest.kt index 3b04b9a..a8dc9df 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandIdentifyFromTransactionParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandIdentifyFromTransactionParamsTest.kt @@ -18,7 +18,7 @@ internal class BrandIdentifyFromTransactionParamsTest { .maxSpeed(true) .mcc("mcc") .phone(0.0) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -33,7 +33,7 @@ internal class BrandIdentifyFromTransactionParamsTest { .maxSpeed(true) .mcc("mcc") .phone(0.0) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -48,7 +48,7 @@ internal class BrandIdentifyFromTransactionParamsTest { .put("maxSpeed", "true") .put("mcc", "mcc") .put("phone", "0.0") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchByEmailParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchByEmailParamsTest.kt index 8a47dd9..8ecbb92 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchByEmailParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchByEmailParamsTest.kt @@ -9,18 +9,18 @@ internal class BrandPrefetchByEmailParamsTest { @Test fun create() { - BrandPrefetchByEmailParams.builder().email("dev@stainless.com").timeoutMs(1L).build() + BrandPrefetchByEmailParams.builder().email("dev@stainless.com").timeoutMs(1000L).build() } @Test fun body() { val params = - BrandPrefetchByEmailParams.builder().email("dev@stainless.com").timeoutMs(1L).build() + BrandPrefetchByEmailParams.builder().email("dev@stainless.com").timeoutMs(1000L).build() val body = params._body() assertThat(body.email()).isEqualTo("dev@stainless.com") - assertThat(body.timeoutMs()).contains(1L) + assertThat(body.timeoutMs()).contains(1000L) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchParamsTest.kt index b2ef496..6d48741 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandPrefetchParamsTest.kt @@ -9,17 +9,17 @@ internal class BrandPrefetchParamsTest { @Test fun create() { - BrandPrefetchParams.builder().domain("domain").timeoutMs(1L).build() + BrandPrefetchParams.builder().domain("domain").timeoutMs(1000L).build() } @Test fun body() { - val params = BrandPrefetchParams.builder().domain("domain").timeoutMs(1L).build() + val params = BrandPrefetchParams.builder().domain("domain").timeoutMs(1000L).build() val body = params._body() assertThat(body.domain()).isEqualTo("domain") - assertThat(body.timeoutMs()).contains(1L) + assertThat(body.timeoutMs()).contains(1000L) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByEmailParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByEmailParamsTest.kt index 91df822..7623105 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByEmailParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByEmailParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandRetrieveByEmailParamsTest { .email("dev@stainless.com") .forceLanguage(BrandRetrieveByEmailParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -25,7 +25,7 @@ internal class BrandRetrieveByEmailParamsTest { .email("dev@stainless.com") .forceLanguage(BrandRetrieveByEmailParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -36,7 +36,7 @@ internal class BrandRetrieveByEmailParamsTest { .put("email", "dev@stainless.com") .put("force_language", "albanian") .put("maxSpeed", "true") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByIsinParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByIsinParamsTest.kt index a0c5032..482fdc9 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByIsinParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByIsinParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandRetrieveByIsinParamsTest { .isin("SE60513A9993") .forceLanguage(BrandRetrieveByIsinParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -25,7 +25,7 @@ internal class BrandRetrieveByIsinParamsTest { .isin("SE60513A9993") .forceLanguage(BrandRetrieveByIsinParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -36,7 +36,7 @@ internal class BrandRetrieveByIsinParamsTest { .put("isin", "SE60513A9993") .put("force_language", "albanian") .put("maxSpeed", "true") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt index 6a8c443..bd3d663 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandRetrieveByNameParamsTest { .name("xxx") .forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -25,7 +25,7 @@ internal class BrandRetrieveByNameParamsTest { .name("xxx") .forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -36,7 +36,7 @@ internal class BrandRetrieveByNameParamsTest { .put("name", "xxx") .put("force_language", "albanian") .put("maxSpeed", "true") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByTickerParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByTickerParamsTest.kt index a8abcba..da35924 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByTickerParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByTickerParamsTest.kt @@ -15,7 +15,7 @@ internal class BrandRetrieveByTickerParamsTest { .forceLanguage(BrandRetrieveByTickerParams.ForceLanguage.ALBANIAN) .maxSpeed(true) .tickerExchange(BrandRetrieveByTickerParams.TickerExchange.AMEX) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -27,7 +27,7 @@ internal class BrandRetrieveByTickerParamsTest { .forceLanguage(BrandRetrieveByTickerParams.ForceLanguage.ALBANIAN) .maxSpeed(true) .tickerExchange(BrandRetrieveByTickerParams.TickerExchange.AMEX) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -39,7 +39,7 @@ internal class BrandRetrieveByTickerParamsTest { .put("force_language", "albanian") .put("maxSpeed", "true") .put("ticker_exchange", "AMEX") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveNaicsParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveNaicsParamsTest.kt index e785ab4..287374f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveNaicsParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveNaicsParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandRetrieveNaicsParamsTest { .input("input") .maxResults(1L) .minResults(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -25,7 +25,7 @@ internal class BrandRetrieveNaicsParamsTest { .input("input") .maxResults(1L) .minResults(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -36,7 +36,7 @@ internal class BrandRetrieveNaicsParamsTest { .put("input", "input") .put("maxResults", "1") .put("minResults", "1") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveParamsTest.kt index 5f9b0b2..6da63ce 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveParamsTest.kt @@ -14,7 +14,7 @@ internal class BrandRetrieveParamsTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -25,7 +25,7 @@ internal class BrandRetrieveParamsTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -36,7 +36,7 @@ internal class BrandRetrieveParamsTest { .put("domain", "domain") .put("force_language", "albanian") .put("maxSpeed", "true") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveSimplifiedParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveSimplifiedParamsTest.kt index 07f3ffa..85d2603 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveSimplifiedParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveSimplifiedParamsTest.kt @@ -10,17 +10,20 @@ internal class BrandRetrieveSimplifiedParamsTest { @Test fun create() { - BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1L).build() + BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1000L).build() } @Test fun queryParams() { - val params = BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1L).build() + val params = + BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1000L).build() val queryParams = params._queryParams() assertThat(queryParams) - .isEqualTo(QueryParams.builder().put("domain", "domain").put("timeoutMS", "1").build()) + .isEqualTo( + QueryParams.builder().put("domain", "domain").put("timeoutMS", "1000").build() + ) } @Test diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandStyleguideParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandStyleguideParamsTest.kt index 768b6e5..55ded8f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandStyleguideParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandStyleguideParamsTest.kt @@ -13,7 +13,7 @@ internal class BrandStyleguideParamsTest { BrandStyleguideParams.builder() .domain("domain") .prioritize(BrandStyleguideParams.Prioritize.SPEED) - .timeoutMs(1L) + .timeoutMs(1000L) .build() } @@ -23,7 +23,7 @@ internal class BrandStyleguideParamsTest { BrandStyleguideParams.builder() .domain("domain") .prioritize(BrandStyleguideParams.Prioritize.SPEED) - .timeoutMs(1L) + .timeoutMs(1000L) .build() val queryParams = params._queryParams() @@ -33,7 +33,7 @@ internal class BrandStyleguideParamsTest { QueryParams.builder() .put("domain", "domain") .put("prioritize", "speed") - .put("timeoutMS", "1") + .put("timeoutMS", "1000") .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ErrorHandlingTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ErrorHandlingTest.kt index 5b14671..1e62e5f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ErrorHandlingTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ErrorHandlingTest.kt @@ -75,7 +75,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -102,7 +102,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -129,7 +129,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -156,7 +156,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -183,7 +183,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -210,7 +210,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -237,7 +237,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -264,7 +264,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -291,7 +291,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -318,7 +318,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -345,7 +345,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -372,7 +372,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -399,7 +399,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -426,7 +426,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -453,7 +453,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -480,7 +480,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } @@ -505,7 +505,7 @@ internal class ErrorHandlingTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) } diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt index 4788b35..853772d 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt @@ -45,7 +45,7 @@ internal class ServiceParamsTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .putAdditionalHeader("Secret-Header", "42") .putAdditionalQueryParam("secret_query_param", "42") .build() diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt index 85df72f..faf3bc2 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt @@ -44,7 +44,7 @@ internal class BrandServiceAsyncTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -64,7 +64,7 @@ internal class BrandServiceAsyncTest { val responseFuture = brandServiceAsync.aiProduct( - BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1000L).build() ) val response = responseFuture.get() @@ -88,7 +88,7 @@ internal class BrandServiceAsyncTest { BrandAiProductsParams.Body.ByDomain.builder() .domain("domain") .maxProducts(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) .build() @@ -148,7 +148,7 @@ internal class BrandServiceAsyncTest { .termsAndConditions(true) .build() ) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -168,7 +168,7 @@ internal class BrandServiceAsyncTest { val responseFuture = brandServiceAsync.fonts( - BrandFontsParams.builder().domain("domain").timeoutMs(1L).build() + BrandFontsParams.builder().domain("domain").timeoutMs(1000L).build() ) val response = responseFuture.get() @@ -195,7 +195,7 @@ internal class BrandServiceAsyncTest { .maxSpeed(true) .mcc("mcc") .phone(0.0) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -215,7 +215,7 @@ internal class BrandServiceAsyncTest { val responseFuture = brandServiceAsync.prefetch( - BrandPrefetchParams.builder().domain("domain").timeoutMs(1L).build() + BrandPrefetchParams.builder().domain("domain").timeoutMs(1000L).build() ) val response = responseFuture.get() @@ -236,7 +236,7 @@ internal class BrandServiceAsyncTest { brandServiceAsync.prefetchByEmail( BrandPrefetchByEmailParams.builder() .email("dev@stainless.com") - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -260,7 +260,7 @@ internal class BrandServiceAsyncTest { .email("dev@stainless.com") .forceLanguage(BrandRetrieveByEmailParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -284,7 +284,7 @@ internal class BrandServiceAsyncTest { .isin("SE60513A9993") .forceLanguage(BrandRetrieveByIsinParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -308,7 +308,7 @@ internal class BrandServiceAsyncTest { .name("xxx") .forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -333,7 +333,7 @@ internal class BrandServiceAsyncTest { .forceLanguage(BrandRetrieveByTickerParams.ForceLanguage.ALBANIAN) .maxSpeed(true) .tickerExchange(BrandRetrieveByTickerParams.TickerExchange.AMEX) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -357,7 +357,7 @@ internal class BrandServiceAsyncTest { .input("input") .maxResults(1L) .minResults(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -377,7 +377,7 @@ internal class BrandServiceAsyncTest { val responseFuture = brandServiceAsync.retrieveSimplified( - BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1L).build() + BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1000L).build() ) val response = responseFuture.get() @@ -423,7 +423,7 @@ internal class BrandServiceAsyncTest { BrandStyleguideParams.builder() .domain("domain") .prioritize(BrandStyleguideParams.Prioritize.SPEED) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt index f4cb373..ad019f9 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt @@ -44,7 +44,7 @@ internal class BrandServiceTest { .domain("domain") .forceLanguage(BrandRetrieveParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -63,7 +63,7 @@ internal class BrandServiceTest { val response = brandService.aiProduct( - BrandAiProductParams.builder().url("https://example.com").timeoutMs(1L).build() + BrandAiProductParams.builder().url("https://example.com").timeoutMs(1000L).build() ) response.validate() @@ -86,7 +86,7 @@ internal class BrandServiceTest { BrandAiProductsParams.Body.ByDomain.builder() .domain("domain") .maxProducts(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) .build() @@ -145,7 +145,7 @@ internal class BrandServiceTest { .termsAndConditions(true) .build() ) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -163,7 +163,7 @@ internal class BrandServiceTest { val brandService = client.brand() val response = - brandService.fonts(BrandFontsParams.builder().domain("domain").timeoutMs(1L).build()) + brandService.fonts(BrandFontsParams.builder().domain("domain").timeoutMs(1000L).build()) response.validate() } @@ -188,7 +188,7 @@ internal class BrandServiceTest { .maxSpeed(true) .mcc("mcc") .phone(0.0) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -207,7 +207,7 @@ internal class BrandServiceTest { val response = brandService.prefetch( - BrandPrefetchParams.builder().domain("domain").timeoutMs(1L).build() + BrandPrefetchParams.builder().domain("domain").timeoutMs(1000L).build() ) response.validate() @@ -227,7 +227,7 @@ internal class BrandServiceTest { brandService.prefetchByEmail( BrandPrefetchByEmailParams.builder() .email("dev@stainless.com") - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -250,7 +250,7 @@ internal class BrandServiceTest { .email("dev@stainless.com") .forceLanguage(BrandRetrieveByEmailParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -273,7 +273,7 @@ internal class BrandServiceTest { .isin("SE60513A9993") .forceLanguage(BrandRetrieveByIsinParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -296,7 +296,7 @@ internal class BrandServiceTest { .name("xxx") .forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN) .maxSpeed(true) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -320,7 +320,7 @@ internal class BrandServiceTest { .forceLanguage(BrandRetrieveByTickerParams.ForceLanguage.ALBANIAN) .maxSpeed(true) .tickerExchange(BrandRetrieveByTickerParams.TickerExchange.AMEX) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -343,7 +343,7 @@ internal class BrandServiceTest { .input("input") .maxResults(1L) .minResults(1L) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) @@ -362,7 +362,7 @@ internal class BrandServiceTest { val response = brandService.retrieveSimplified( - BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1L).build() + BrandRetrieveSimplifiedParams.builder().domain("domain").timeoutMs(1000L).build() ) response.validate() @@ -406,7 +406,7 @@ internal class BrandServiceTest { BrandStyleguideParams.builder() .domain("domain") .prioritize(BrandStyleguideParams.Prioritize.SPEED) - .timeoutMs(1L) + .timeoutMs(1000L) .build() ) From 8ec7bbbdf6d8c8a3e62817f69120817f85a89f9f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:17:25 +0000 Subject: [PATCH 04/11] chore(internal): update `TestServerExtension` comment --- .../com/branddev/api/TestServerExtension.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt index 89c422f..9813639 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt @@ -15,25 +15,12 @@ class TestServerExtension : BeforeAllCallback, ExecutionCondition { } catch (e: Exception) { throw RuntimeException( """ - The test suite will not run without a mock Prism server running against your OpenAPI spec. + The test suite will not run without a mock server running against your OpenAPI spec. You can set the environment variable `SKIP_MOCK_TESTS` to `true` to skip running any tests that require the mock server. - To fix: - - 1. Install Prism (requires Node 16+): - - With npm: - $ npm install -g @stoplight/prism-cli - - With yarn: - $ yarn global add @stoplight/prism-cli - - 2. Run the mock server - - To run the server, pass in the path of your OpenAPI spec to the prism command: - $ prism mock path/to/your.openapi.yml + To fix run `./scripts/mock` in a separate terminal. """ .trimIndent(), e, From 7176a5a3d63596c1f550864a28e4aedc1f751976 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:19:00 +0000 Subject: [PATCH 05/11] chore(internal): make `OkHttp` constructor internal --- .../main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt index 445709f..f225a5b 100644 --- a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt +++ b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt @@ -33,7 +33,7 @@ import okhttp3.logging.HttpLoggingInterceptor import okio.BufferedSink class OkHttpClient -private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient { +internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient { override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse { val call = newCall(request, requestOptions) From 63d98a3572690648e96e2a16a8bc06874887cdcf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:21:56 +0000 Subject: [PATCH 06/11] feat(client): add more convenience service method overloads --- .../api/services/async/BrandServiceAsync.kt | 74 +++++++++++++++++++ .../api/services/blocking/BrandService.kt | 74 +++++++++++++++++++ .../services/async/BrandServiceAsyncTest.kt | 12 +-- .../api/services/blocking/BrandServiceTest.kt | 12 +-- 4 files changed, 156 insertions(+), 16 deletions(-) diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt index 55778f1..1846ad9 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsync.kt @@ -92,6 +92,41 @@ interface BrandServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture + /** @see aiProducts */ + fun aiProducts( + body: BrandAiProductsParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + aiProducts(BrandAiProductsParams.builder().body(body).build(), requestOptions) + + /** @see aiProducts */ + fun aiProducts(body: BrandAiProductsParams.Body): CompletableFuture = + aiProducts(body, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + aiProducts(BrandAiProductsParams.Body.ofByDomain(byDomain), requestOptions) + + /** @see aiProducts */ + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain + ): CompletableFuture = aiProducts(byDomain, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + aiProducts(BrandAiProductsParams.Body.ofByDirectUrl(byDirectUrl), requestOptions) + + /** @see aiProducts */ + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl + ): CompletableFuture = aiProducts(byDirectUrl, RequestOptions.none()) + /** * Use AI to extract specific data points from a brand's website. The AI will crawl the website * and extract the requested information based on the provided data points. @@ -339,6 +374,45 @@ interface BrandServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture> + /** @see aiProducts */ + fun aiProducts( + body: BrandAiProductsParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + aiProducts(BrandAiProductsParams.builder().body(body).build(), requestOptions) + + /** @see aiProducts */ + fun aiProducts( + body: BrandAiProductsParams.Body + ): CompletableFuture> = + aiProducts(body, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + aiProducts(BrandAiProductsParams.Body.ofByDomain(byDomain), requestOptions) + + /** @see aiProducts */ + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain + ): CompletableFuture> = + aiProducts(byDomain, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + aiProducts(BrandAiProductsParams.Body.ofByDirectUrl(byDirectUrl), requestOptions) + + /** @see aiProducts */ + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl + ): CompletableFuture> = + aiProducts(byDirectUrl, RequestOptions.none()) + /** * Returns a raw HTTP response for `post /brand/ai/query`, but is otherwise the same as * [BrandServiceAsync.aiQuery]. diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt index e1b4d44..01b6c49 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/blocking/BrandService.kt @@ -92,6 +92,39 @@ interface BrandService { requestOptions: RequestOptions = RequestOptions.none(), ): BrandAiProductsResponse + /** @see aiProducts */ + fun aiProducts( + body: BrandAiProductsParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): BrandAiProductsResponse = + aiProducts(BrandAiProductsParams.builder().body(body).build(), requestOptions) + + /** @see aiProducts */ + fun aiProducts(body: BrandAiProductsParams.Body): BrandAiProductsResponse = + aiProducts(body, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain, + requestOptions: RequestOptions = RequestOptions.none(), + ): BrandAiProductsResponse = + aiProducts(BrandAiProductsParams.Body.ofByDomain(byDomain), requestOptions) + + /** @see aiProducts */ + fun aiProducts(byDomain: BrandAiProductsParams.Body.ByDomain): BrandAiProductsResponse = + aiProducts(byDomain, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl, + requestOptions: RequestOptions = RequestOptions.none(), + ): BrandAiProductsResponse = + aiProducts(BrandAiProductsParams.Body.ofByDirectUrl(byDirectUrl), requestOptions) + + /** @see aiProducts */ + fun aiProducts(byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl): BrandAiProductsResponse = + aiProducts(byDirectUrl, RequestOptions.none()) + /** * Use AI to extract specific data points from a brand's website. The AI will crawl the website * and extract the requested information based on the provided data points. @@ -322,6 +355,47 @@ interface BrandService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + body: BrandAiProductsParams.Body, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + aiProducts(BrandAiProductsParams.builder().body(body).build(), requestOptions) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts(body: BrandAiProductsParams.Body): HttpResponseFor = + aiProducts(body, RequestOptions.none()) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + aiProducts(BrandAiProductsParams.Body.ofByDomain(byDomain), requestOptions) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + byDomain: BrandAiProductsParams.Body.ByDomain + ): HttpResponseFor = aiProducts(byDomain, RequestOptions.none()) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + aiProducts(BrandAiProductsParams.Body.ofByDirectUrl(byDirectUrl), requestOptions) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + byDirectUrl: BrandAiProductsParams.Body.ByDirectUrl + ): HttpResponseFor = aiProducts(byDirectUrl, RequestOptions.none()) + /** * Returns a raw HTTP response for `post /brand/ai/query`, but is otherwise the same as * [BrandService.aiQuery]. diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt index faf3bc2..22afc69 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt @@ -83,14 +83,10 @@ internal class BrandServiceAsyncTest { val responseFuture = brandServiceAsync.aiProducts( - BrandAiProductsParams.builder() - .body( - BrandAiProductsParams.Body.ByDomain.builder() - .domain("domain") - .maxProducts(1L) - .timeoutMs(1000L) - .build() - ) + BrandAiProductsParams.Body.ByDomain.builder() + .domain("domain") + .maxProducts(1L) + .timeoutMs(1000L) .build() ) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt index ad019f9..50f3f45 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt @@ -81,14 +81,10 @@ internal class BrandServiceTest { val response = brandService.aiProducts( - BrandAiProductsParams.builder() - .body( - BrandAiProductsParams.Body.ByDomain.builder() - .domain("domain") - .maxProducts(1L) - .timeoutMs(1000L) - .build() - ) + BrandAiProductsParams.Body.ByDomain.builder() + .domain("domain") + .maxProducts(1L) + .timeoutMs(1000L) .build() ) From 50588f3d193a48d8e764c93e72f579649f189558 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:23:50 +0000 Subject: [PATCH 07/11] feat(client): add connection pooling option --- README.md | 19 ++++++++ .../api/client/okhttp/BrandDevOkHttpClient.kt | 44 +++++++++++++++++++ .../okhttp/BrandDevOkHttpClientAsync.kt | 44 +++++++++++++++++++ .../api/client/okhttp/OkHttpClient.kt | 42 ++++++++++++++++++ 4 files changed, 149 insertions(+) diff --git a/README.md b/README.md index a597701..3b820f7 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,25 @@ BrandDevClient client = BrandDevOkHttpClient.builder() .build(); ``` +### Connection pooling + +To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods: + +```java +import com.branddev.api.client.BrandDevClient; +import com.branddev.api.client.okhttp.BrandDevOkHttpClient; +import java.time.Duration; + +BrandDevClient client = BrandDevOkHttpClient.builder() + .fromEnv() + // If `maxIdleConnections` is set, then `keepAliveDuration` must be set, and vice versa. + .maxIdleConnections(10) + .keepAliveDuration(Duration.ofMinutes(2)) + .build(); +``` + +If both options are unset, OkHttp's default connection pool settings are used. + ### HTTPS > [!NOTE] diff --git a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClient.kt b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClient.kt index bd414fd..8c827a1 100644 --- a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClient.kt +++ b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClient.kt @@ -47,6 +47,8 @@ class BrandDevOkHttpClient private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null + private var maxIdleConnections: Int? = null + private var keepAliveDuration: Duration? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null @@ -75,6 +77,46 @@ class BrandDevOkHttpClient private constructor() { /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * The maximum number of idle connections kept by the underlying OkHttp connection pool. + * + * If this is set, then [keepAliveDuration] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun maxIdleConnections(maxIdleConnections: Int?) = apply { + this.maxIdleConnections = maxIdleConnections + } + + /** + * Alias for [Builder.maxIdleConnections]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun maxIdleConnections(maxIdleConnections: Int) = + maxIdleConnections(maxIdleConnections as Int?) + + /** + * Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`. + */ + fun maxIdleConnections(maxIdleConnections: Optional) = + maxIdleConnections(maxIdleConnections.getOrNull()) + + /** + * The keep-alive duration for idle connections in the underlying OkHttp connection pool. + * + * If this is set, then [maxIdleConnections] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun keepAliveDuration(keepAliveDuration: Duration?) = apply { + this.keepAliveDuration = keepAliveDuration + } + + /** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */ + fun keepAliveDuration(keepAliveDuration: Optional) = + keepAliveDuration(keepAliveDuration.getOrNull()) + /** * The socket factory used to secure HTTPS connections. * @@ -317,6 +359,8 @@ class BrandDevOkHttpClient private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .maxIdleConnections(maxIdleConnections) + .keepAliveDuration(keepAliveDuration) .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) diff --git a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClientAsync.kt b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClientAsync.kt index 398393f..1269673 100644 --- a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClientAsync.kt +++ b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/BrandDevOkHttpClientAsync.kt @@ -47,6 +47,8 @@ class BrandDevOkHttpClientAsync private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null + private var maxIdleConnections: Int? = null + private var keepAliveDuration: Duration? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null @@ -75,6 +77,46 @@ class BrandDevOkHttpClientAsync private constructor() { /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * The maximum number of idle connections kept by the underlying OkHttp connection pool. + * + * If this is set, then [keepAliveDuration] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun maxIdleConnections(maxIdleConnections: Int?) = apply { + this.maxIdleConnections = maxIdleConnections + } + + /** + * Alias for [Builder.maxIdleConnections]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun maxIdleConnections(maxIdleConnections: Int) = + maxIdleConnections(maxIdleConnections as Int?) + + /** + * Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`. + */ + fun maxIdleConnections(maxIdleConnections: Optional) = + maxIdleConnections(maxIdleConnections.getOrNull()) + + /** + * The keep-alive duration for idle connections in the underlying OkHttp connection pool. + * + * If this is set, then [maxIdleConnections] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun keepAliveDuration(keepAliveDuration: Duration?) = apply { + this.keepAliveDuration = keepAliveDuration + } + + /** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */ + fun keepAliveDuration(keepAliveDuration: Optional) = + keepAliveDuration(keepAliveDuration.getOrNull()) + /** * The socket factory used to secure HTTPS connections. * @@ -317,6 +359,8 @@ class BrandDevOkHttpClientAsync private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .maxIdleConnections(maxIdleConnections) + .keepAliveDuration(keepAliveDuration) .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) diff --git a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt index f225a5b..2d686cb 100644 --- a/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt +++ b/brand-dev-java-client-okhttp/src/main/kotlin/com/branddev/api/client/okhttp/OkHttpClient.kt @@ -16,11 +16,13 @@ import java.time.Duration import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutorService +import java.util.concurrent.TimeUnit import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager import okhttp3.Call import okhttp3.Callback +import okhttp3.ConnectionPool import okhttp3.Dispatcher import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType @@ -200,6 +202,8 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null + private var maxIdleConnections: Int? = null + private var keepAliveDuration: Duration? = null private var dispatcherExecutorService: ExecutorService? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null @@ -211,6 +215,28 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + /** + * Sets the maximum number of idle connections kept by the underlying [ConnectionPool]. + * + * If this is set, then [keepAliveDuration] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun maxIdleConnections(maxIdleConnections: Int?) = apply { + this.maxIdleConnections = maxIdleConnections + } + + /** + * Sets the keep-alive duration for idle connections in the underlying [ConnectionPool]. + * + * If this is set, then [maxIdleConnections] must also be set. + * + * If unset, then OkHttp's default is used. + */ + fun keepAliveDuration(keepAliveDuration: Duration?) = apply { + this.keepAliveDuration = keepAliveDuration + } + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { this.dispatcherExecutorService = dispatcherExecutorService } @@ -240,6 +266,22 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie .apply { dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) } + val maxIdleConnections = maxIdleConnections + val keepAliveDuration = keepAliveDuration + if (maxIdleConnections != null && keepAliveDuration != null) { + connectionPool( + ConnectionPool( + maxIdleConnections, + keepAliveDuration.toNanos(), + TimeUnit.NANOSECONDS, + ) + ) + } else { + check((maxIdleConnections != null) == (keepAliveDuration != null)) { + "Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set" + } + } + val sslSocketFactory = sslSocketFactory val trustManager = trustManager if (sslSocketFactory != null && trustManager != null) { From 34d975a09009a15f51c843d4c4f3e3ad8822109a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:20:56 +0000 Subject: [PATCH 08/11] chore(internal): remove mock server code --- .../com/branddev/api/TestServerExtension.kt | 49 --------- .../services/async/BrandServiceAsyncTest.kt | 99 +++---------------- .../api/services/blocking/BrandServiceTest.kt | 99 +++---------------- scripts/mock | 41 -------- scripts/test | 46 --------- 5 files changed, 32 insertions(+), 302 deletions(-) delete mode 100644 brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt delete mode 100755 scripts/mock diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt deleted file mode 100644 index 9813639..0000000 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/TestServerExtension.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.branddev.api - -import java.lang.RuntimeException -import java.net.URL -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.ConditionEvaluationResult -import org.junit.jupiter.api.extension.ExecutionCondition -import org.junit.jupiter.api.extension.ExtensionContext - -class TestServerExtension : BeforeAllCallback, ExecutionCondition { - - override fun beforeAll(context: ExtensionContext?) { - try { - URL(BASE_URL).openConnection().connect() - } catch (e: Exception) { - throw RuntimeException( - """ - The test suite will not run without a mock server running against your OpenAPI spec. - - You can set the environment variable `SKIP_MOCK_TESTS` to `true` to skip running any tests - that require the mock server. - - To fix run `./scripts/mock` in a separate terminal. - """ - .trimIndent(), - e, - ) - } - } - - override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult { - return if (System.getenv(SKIP_TESTS_ENV).toBoolean()) { - ConditionEvaluationResult.disabled( - "Environment variable $SKIP_TESTS_ENV is set to true" - ) - } else { - ConditionEvaluationResult.enabled( - "Environment variable $SKIP_TESTS_ENV is not set to true" - ) - } - } - - companion object { - - val BASE_URL = System.getenv("TEST_API_BASE_URL") ?: "http://localhost:4010" - - const val SKIP_TESTS_ENV: String = "SKIP_MOCK_TESTS" - } -} diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt index 22afc69..a0f538f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt @@ -2,7 +2,6 @@ package com.branddev.api.services.async -import com.branddev.api.TestServerExtension import com.branddev.api.client.okhttp.BrandDevOkHttpClientAsync import com.branddev.api.core.JsonValue import com.branddev.api.models.brand.BrandAiProductParams @@ -23,19 +22,13 @@ import com.branddev.api.models.brand.BrandScreenshotParams import com.branddev.api.models.brand.BrandStyleguideParams import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -@ExtendWith(TestServerExtension::class) internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieve() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val brandFuture = @@ -55,11 +48,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun aiProduct() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -74,11 +63,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun aiProducts() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -97,11 +82,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun aiQuery() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -155,11 +136,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun fonts() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -174,11 +151,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun identifyFromTransaction() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -202,11 +175,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun prefetch() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -221,11 +190,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun prefetchByEmail() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -243,11 +208,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveByEmail() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -267,11 +228,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveByIsin() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -291,11 +248,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveByName() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -315,11 +268,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveByTicker() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -340,11 +289,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveNaics() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -364,11 +309,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun retrieveSimplified() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -383,11 +324,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun screenshot() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = @@ -407,11 +344,7 @@ internal class BrandServiceAsyncTest { @Disabled("Prism tests are disabled") @Test fun styleguide() { - val client = - BrandDevOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() val brandServiceAsync = client.brand() val responseFuture = diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt index 50f3f45..78d304b 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt @@ -2,7 +2,6 @@ package com.branddev.api.services.blocking -import com.branddev.api.TestServerExtension import com.branddev.api.client.okhttp.BrandDevOkHttpClient import com.branddev.api.core.JsonValue import com.branddev.api.models.brand.BrandAiProductParams @@ -23,19 +22,13 @@ import com.branddev.api.models.brand.BrandScreenshotParams import com.branddev.api.models.brand.BrandStyleguideParams import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -@ExtendWith(TestServerExtension::class) internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieve() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val brand = @@ -54,11 +47,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun aiProduct() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -72,11 +61,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun aiProducts() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -94,11 +79,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun aiQuery() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -151,11 +132,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun fonts() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -167,11 +144,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun identifyFromTransaction() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -194,11 +167,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun prefetch() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -212,11 +181,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun prefetchByEmail() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -233,11 +198,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveByEmail() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -256,11 +217,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveByIsin() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -279,11 +236,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveByName() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -302,11 +255,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveByTicker() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -326,11 +275,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveNaics() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -349,11 +294,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun retrieveSimplified() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -367,11 +308,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun screenshot() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = @@ -390,11 +327,7 @@ internal class BrandServiceTest { @Disabled("Prism tests are disabled") @Test fun styleguide() { - val client = - BrandDevOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .build() + val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() val brandService = client.brand() val response = diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 0b28f6e..0000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" -fi diff --git a/scripts/test b/scripts/test index 047bc1d..904aea6 100755 --- a/scripts/test +++ b/scripts/test @@ -4,53 +4,7 @@ set -e cd "$(dirname "$0")/.." -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi echo "==> Running tests" ./gradlew test "$@" From e7cf4232d2397f736fce8b3dbecc80bfde182144 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:21:48 +0000 Subject: [PATCH 09/11] chore: update mock server docs --- .../api/services/ServiceParamsTest.kt | 2 +- .../services/async/BrandServiceAsyncTest.kt | 32 +++++++++---------- .../api/services/blocking/BrandServiceTest.kt | 32 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt index 853772d..8fa67c9 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/ServiceParamsTest.kt @@ -34,7 +34,7 @@ internal class ServiceParamsTest { .build() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieve() { val brandService = client.brand() diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt index a0f538f..6e8d17c 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/async/BrandServiceAsyncTest.kt @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test internal class BrandServiceAsyncTest { - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieve() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -45,7 +45,7 @@ internal class BrandServiceAsyncTest { brand.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiProduct() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -60,7 +60,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiProducts() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -79,7 +79,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiQuery() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -133,7 +133,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun fonts() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -148,7 +148,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun identifyFromTransaction() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -172,7 +172,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun prefetch() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -187,7 +187,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun prefetchByEmail() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -205,7 +205,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByEmail() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -225,7 +225,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByIsin() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -245,7 +245,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByName() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -265,7 +265,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByTicker() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -286,7 +286,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveNaics() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -306,7 +306,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveSimplified() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -321,7 +321,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun screenshot() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() @@ -341,7 +341,7 @@ internal class BrandServiceAsyncTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun styleguide() { val client = BrandDevOkHttpClientAsync.builder().apiKey("My API Key").build() diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt index 78d304b..01150cf 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/services/blocking/BrandServiceTest.kt @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test internal class BrandServiceTest { - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieve() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -44,7 +44,7 @@ internal class BrandServiceTest { brand.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiProduct() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -58,7 +58,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiProducts() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -76,7 +76,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun aiQuery() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -129,7 +129,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun fonts() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -141,7 +141,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun identifyFromTransaction() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -164,7 +164,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun prefetch() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -178,7 +178,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun prefetchByEmail() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -195,7 +195,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByEmail() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -214,7 +214,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByIsin() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -233,7 +233,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByName() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -252,7 +252,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveByTicker() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -272,7 +272,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveNaics() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -291,7 +291,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun retrieveSimplified() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -305,7 +305,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun screenshot() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() @@ -324,7 +324,7 @@ internal class BrandServiceTest { response.validate() } - @Disabled("Prism tests are disabled") + @Disabled("Mock server tests are disabled") @Test fun styleguide() { val client = BrandDevOkHttpClient.builder().apiKey("My API Key").build() From e09f05c64e70af02954b142ea11b42983a7758d1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:25:52 +0000 Subject: [PATCH 10/11] feat(api): api update --- .stats.yml | 4 +- .../models/brand/BrandAiProductResponse.kt | 57 ++++++++++++++++++- .../models/brand/BrandAiProductsResponse.kt | 57 ++++++++++++++++++- .../brand/BrandAiProductResponseTest.kt | 3 + .../brand/BrandAiProductsResponseTest.kt | 3 + 5 files changed, 120 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index a1fb573..d2dca25 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-ec0e897d65bee0947046681fd3cd465ee5b44c98b020594736fb9f274ee2c00b.yml -openapi_spec_hash: 9c76eb4b912f046fc93a030b9a7489a3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-98ef96cef5b06ad7a29dadba48258da7d9ea0a2b3938dc9e714ae06eb9afa1a3.yml +openapi_spec_hash: 9e957a30999dff7d4ada925e437bd202 config_hash: c3aaaa9794dba44d524c06591ab17894 diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt index 3beb8ee..0a659fd 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductResponse.kt @@ -373,6 +373,7 @@ private constructor( private constructor( private val description: JsonField, private val features: JsonField>, + private val images: JsonField>, private val name: JsonField, private val tags: JsonField>, private val targetAudience: JsonField>, @@ -394,6 +395,9 @@ private constructor( @JsonProperty("features") @ExcludeMissing features: JsonField> = JsonMissing.of(), + @JsonProperty("images") + @ExcludeMissing + images: JsonField> = JsonMissing.of(), @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), @JsonProperty("tags") @ExcludeMissing tags: JsonField> = JsonMissing.of(), @JsonProperty("target_audience") @@ -419,6 +423,7 @@ private constructor( ) : this( description, features, + images, name, tags, targetAudience, @@ -448,6 +453,14 @@ private constructor( */ fun features(): List = features.getRequired("features") + /** + * URLs to product images on the page (up to 7) + * + * @throws BrandDevInvalidDataException 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 images(): List = images.getRequired("images") + /** * Name of the product * @@ -547,6 +560,13 @@ private constructor( @ExcludeMissing fun _features(): JsonField> = features + /** + * Returns the raw JSON value of [images]. + * + * Unlike [images], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("images") @ExcludeMissing fun _images(): JsonField> = images + /** * Returns the raw JSON value of [name]. * @@ -647,6 +667,7 @@ private constructor( * ```java * .description() * .features() + * .images() * .name() * .tags() * .targetAudience() @@ -660,6 +681,7 @@ private constructor( private var description: JsonField? = null private var features: JsonField>? = null + private var images: JsonField>? = null private var name: JsonField? = null private var tags: JsonField>? = null private var targetAudience: JsonField>? = null @@ -676,6 +698,7 @@ private constructor( internal fun from(product: Product) = apply { description = product.description features = product.features.map { it.toMutableList() } + images = product.images.map { it.toMutableList() } name = product.name tags = product.tags.map { it.toMutableList() } targetAudience = product.targetAudience.map { it.toMutableList() } @@ -729,6 +752,32 @@ private constructor( } } + /** URLs to product images on the page (up to 7) */ + fun images(images: List) = images(JsonField.of(images)) + + /** + * Sets [Builder.images] to an arbitrary JSON value. + * + * You should usually call [Builder.images] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun images(images: JsonField>) = apply { + this.images = images.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [images]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addImage(image: String) = apply { + images = + (images ?: JsonField.of(mutableListOf())).also { + checkKnown("images", it).add(image) + } + } + /** Name of the product */ fun name(name: String) = name(JsonField.of(name)) @@ -942,6 +991,7 @@ private constructor( * ```java * .description() * .features() + * .images() * .name() * .tags() * .targetAudience() @@ -953,6 +1003,7 @@ private constructor( Product( checkRequired("description", description), checkRequired("features", features).map { it.toImmutable() }, + checkRequired("images", images).map { it.toImmutable() }, checkRequired("name", name), checkRequired("tags", tags).map { it.toImmutable() }, checkRequired("targetAudience", targetAudience).map { it.toImmutable() }, @@ -976,6 +1027,7 @@ private constructor( description() features() + images() name() tags() targetAudience() @@ -1007,6 +1059,7 @@ private constructor( internal fun validity(): Int = (if (description.asKnown().isPresent) 1 else 0) + (features.asKnown().getOrNull()?.size ?: 0) + + (images.asKnown().getOrNull()?.size ?: 0) + (if (name.asKnown().isPresent) 1 else 0) + (tags.asKnown().getOrNull()?.size ?: 0) + (targetAudience.asKnown().getOrNull()?.size ?: 0) + @@ -1322,6 +1375,7 @@ private constructor( return other is Product && description == other.description && features == other.features && + images == other.images && name == other.name && tags == other.tags && targetAudience == other.targetAudience && @@ -1339,6 +1393,7 @@ private constructor( Objects.hash( description, features, + images, name, tags, targetAudience, @@ -1356,7 +1411,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "Product{description=$description, features=$features, name=$name, tags=$tags, targetAudience=$targetAudience, billingFrequency=$billingFrequency, category=$category, currency=$currency, imageUrl=$imageUrl, price=$price, pricingModel=$pricingModel, url=$url, additionalProperties=$additionalProperties}" + "Product{description=$description, features=$features, images=$images, name=$name, tags=$tags, targetAudience=$targetAudience, billingFrequency=$billingFrequency, category=$category, currency=$currency, imageUrl=$imageUrl, price=$price, pricingModel=$pricingModel, url=$url, additionalProperties=$additionalProperties}" } override fun equals(other: Any?): Boolean { diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt index 55f3a3e..5a30c16 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt @@ -169,6 +169,7 @@ private constructor( private constructor( private val description: JsonField, private val features: JsonField>, + private val images: JsonField>, private val name: JsonField, private val tags: JsonField>, private val targetAudience: JsonField>, @@ -190,6 +191,9 @@ private constructor( @JsonProperty("features") @ExcludeMissing features: JsonField> = JsonMissing.of(), + @JsonProperty("images") + @ExcludeMissing + images: JsonField> = JsonMissing.of(), @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), @JsonProperty("tags") @ExcludeMissing tags: JsonField> = JsonMissing.of(), @JsonProperty("target_audience") @@ -215,6 +219,7 @@ private constructor( ) : this( description, features, + images, name, tags, targetAudience, @@ -244,6 +249,14 @@ private constructor( */ fun features(): List = features.getRequired("features") + /** + * URLs to product images on the page (up to 7) + * + * @throws BrandDevInvalidDataException 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 images(): List = images.getRequired("images") + /** * Name of the product * @@ -343,6 +356,13 @@ private constructor( @ExcludeMissing fun _features(): JsonField> = features + /** + * Returns the raw JSON value of [images]. + * + * Unlike [images], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("images") @ExcludeMissing fun _images(): JsonField> = images + /** * Returns the raw JSON value of [name]. * @@ -443,6 +463,7 @@ private constructor( * ```java * .description() * .features() + * .images() * .name() * .tags() * .targetAudience() @@ -456,6 +477,7 @@ private constructor( private var description: JsonField? = null private var features: JsonField>? = null + private var images: JsonField>? = null private var name: JsonField? = null private var tags: JsonField>? = null private var targetAudience: JsonField>? = null @@ -472,6 +494,7 @@ private constructor( internal fun from(product: Product) = apply { description = product.description features = product.features.map { it.toMutableList() } + images = product.images.map { it.toMutableList() } name = product.name tags = product.tags.map { it.toMutableList() } targetAudience = product.targetAudience.map { it.toMutableList() } @@ -525,6 +548,32 @@ private constructor( } } + /** URLs to product images on the page (up to 7) */ + fun images(images: List) = images(JsonField.of(images)) + + /** + * Sets [Builder.images] to an arbitrary JSON value. + * + * You should usually call [Builder.images] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun images(images: JsonField>) = apply { + this.images = images.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [images]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addImage(image: String) = apply { + images = + (images ?: JsonField.of(mutableListOf())).also { + checkKnown("images", it).add(image) + } + } + /** Name of the product */ fun name(name: String) = name(JsonField.of(name)) @@ -738,6 +787,7 @@ private constructor( * ```java * .description() * .features() + * .images() * .name() * .tags() * .targetAudience() @@ -749,6 +799,7 @@ private constructor( Product( checkRequired("description", description), checkRequired("features", features).map { it.toImmutable() }, + checkRequired("images", images).map { it.toImmutable() }, checkRequired("name", name), checkRequired("tags", tags).map { it.toImmutable() }, checkRequired("targetAudience", targetAudience).map { it.toImmutable() }, @@ -772,6 +823,7 @@ private constructor( description() features() + images() name() tags() targetAudience() @@ -803,6 +855,7 @@ private constructor( internal fun validity(): Int = (if (description.asKnown().isPresent) 1 else 0) + (features.asKnown().getOrNull()?.size ?: 0) + + (images.asKnown().getOrNull()?.size ?: 0) + (if (name.asKnown().isPresent) 1 else 0) + (tags.asKnown().getOrNull()?.size ?: 0) + (targetAudience.asKnown().getOrNull()?.size ?: 0) + @@ -1118,6 +1171,7 @@ private constructor( return other is Product && description == other.description && features == other.features && + images == other.images && name == other.name && tags == other.tags && targetAudience == other.targetAudience && @@ -1135,6 +1189,7 @@ private constructor( Objects.hash( description, features, + images, name, tags, targetAudience, @@ -1152,7 +1207,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "Product{description=$description, features=$features, name=$name, tags=$tags, targetAudience=$targetAudience, billingFrequency=$billingFrequency, category=$category, currency=$currency, imageUrl=$imageUrl, price=$price, pricingModel=$pricingModel, url=$url, additionalProperties=$additionalProperties}" + "Product{description=$description, features=$features, images=$images, name=$name, tags=$tags, targetAudience=$targetAudience, billingFrequency=$billingFrequency, category=$category, currency=$currency, imageUrl=$imageUrl, price=$price, pricingModel=$pricingModel, url=$url, additionalProperties=$additionalProperties}" } override fun equals(other: Any?): Boolean { diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt index 7c9676c..fca439f 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductResponseTest.kt @@ -19,6 +19,7 @@ internal class BrandAiProductResponseTest { BrandAiProductResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") @@ -41,6 +42,7 @@ internal class BrandAiProductResponseTest { BrandAiProductResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") @@ -66,6 +68,7 @@ internal class BrandAiProductResponseTest { BrandAiProductResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt index a8453f7..038bfc9 100644 --- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt @@ -18,6 +18,7 @@ internal class BrandAiProductsResponseTest { BrandAiProductsResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") @@ -37,6 +38,7 @@ internal class BrandAiProductsResponseTest { BrandAiProductsResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") @@ -60,6 +62,7 @@ internal class BrandAiProductsResponseTest { BrandAiProductsResponse.Product.builder() .description("description") .addFeature("string") + .addImage("string") .name("name") .addTag("string") .addTargetAudience("string") From b8a077f488abf4843ac3c0b391c60deb2acbbc2a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:26:07 +0000 Subject: [PATCH 11/11] release: 0.1.0-alpha.30 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ README.md | 10 +++++----- build.gradle.kts | 2 +- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c412e97..52b3e83 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.29" + ".": "0.1.0-alpha.30" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b35897..a3f1ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.1.0-alpha.30 (2026-02-22) + +Full Changelog: [v0.1.0-alpha.29...v0.1.0-alpha.30](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.29...v0.1.0-alpha.30) + +### Features + +* **api:** api update ([e09f05c](https://github.com/brand-dot-dev/java-sdk/commit/e09f05c64e70af02954b142ea11b42983a7758d1)) +* **api:** api update ([176daa6](https://github.com/brand-dot-dev/java-sdk/commit/176daa6ade5920957e1afb073ab0f14b2dc93705)) +* **api:** manual updates ([8588622](https://github.com/brand-dot-dev/java-sdk/commit/8588622a8b75ea541a9be7fc2a47a4ddd1554416)) +* **client:** add connection pooling option ([50588f3](https://github.com/brand-dot-dev/java-sdk/commit/50588f3d193a48d8e764c93e72f579649f189558)) +* **client:** add more convenience service method overloads ([63d98a3](https://github.com/brand-dot-dev/java-sdk/commit/63d98a3572690648e96e2a16a8bc06874887cdcf)) + + +### Chores + +* **internal:** make `OkHttp` constructor internal ([7176a5a](https://github.com/brand-dot-dev/java-sdk/commit/7176a5a3d63596c1f550864a28e4aedc1f751976)) +* **internal:** remove mock server code ([34d975a](https://github.com/brand-dot-dev/java-sdk/commit/34d975a09009a15f51c843d4c4f3e3ad8822109a)) +* **internal:** update `TestServerExtension` comment ([8ec7bbb](https://github.com/brand-dot-dev/java-sdk/commit/8ec7bbbdf6d8c8a3e62817f69120817f85a89f9f)) +* update mock server docs ([e7cf423](https://github.com/brand-dot-dev/java-sdk/commit/e7cf4232d2397f736fce8b3dbecc80bfde182144)) + ## 0.1.0-alpha.29 (2026-02-07) Full Changelog: [v0.1.0-alpha.28...v0.1.0-alpha.29](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.28...v0.1.0-alpha.29) diff --git a/README.md b/README.md index 3b820f7..60a78e7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.branddev.api/brand-dev-java)](https://central.sonatype.com/artifact/com.branddev.api/brand-dev-java/0.1.0-alpha.29) -[![javadoc](https://javadoc.io/badge2/com.branddev.api/brand-dev-java/0.1.0-alpha.29/javadoc.svg)](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.29) +[![Maven Central](https://img.shields.io/maven-central/v/com.branddev.api/brand-dev-java)](https://central.sonatype.com/artifact/com.branddev.api/brand-dev-java/0.1.0-alpha.30) +[![javadoc](https://javadoc.io/badge2/com.branddev.api/brand-dev-java/0.1.0-alpha.30/javadoc.svg)](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.30) @@ -22,7 +22,7 @@ Use the Brand Dev MCP Server to enable AI assistants to interact with this API, -The REST API documentation can be found on [docs.brand.dev](https://docs.brand.dev/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.29). +The REST API documentation can be found on [docs.brand.dev](https://docs.brand.dev/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.30). @@ -33,7 +33,7 @@ The REST API documentation can be found on [docs.brand.dev](https://docs.brand.d ### Gradle ```kotlin -implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.29") +implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.30") ``` ### Maven @@ -42,7 +42,7 @@ implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.29") com.branddev.api brand-dev-java - 0.1.0-alpha.29 + 0.1.0-alpha.30 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 0f41d01..ce30364 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.branddev.api" - version = "0.1.0-alpha.29" // x-release-please-version + version = "0.1.0-alpha.30" // x-release-please-version } subprojects {