From db5b7722e7b7a93968f3bfa9af0cb6c5db4b463c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:05:15 +0000 Subject: [PATCH 1/7] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f4a2e85..53bd8bc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-1391c1e69bcdce517f71265a2f22f4e8d35c3f12a224899e64f85e25897b743a.yml -openapi_spec_hash: f8a2917cc425300d272ae943f88d7bb7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-53bd1b5a6d4207268e23f4d22e28317d660c8c27a2be37c5cc9db2702cf04b6c.yml +openapi_spec_hash: 71974f160905d65b2dc8ad871c994c98 config_hash: 7f48d078645cb8331328bcd6f6ab3281 From 210aa1435c09e44ab9b035f518b40bdb759936c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:15:48 +0000 Subject: [PATCH 2/7] feat(api): api update --- .stats.yml | 4 ++-- LICENSE | 2 +- .../api/models/brand/BrandAiQueryParams.kt | 4 ++-- .../api/models/brand/BrandFontsParams.kt | 4 ++-- .../api/models/brand/BrandScreenshotParams.kt | 7 +++---- .../api/models/brand/BrandStyleguideParams.kt | 4 ++-- .../api/services/async/BrandServiceAsync.kt | 20 +++++++++---------- .../api/services/blocking/BrandService.kt | 20 +++++++++---------- 8 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.stats.yml b/.stats.yml index 53bd8bc..078c968 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-53bd1b5a6d4207268e23f4d22e28317d660c8c27a2be37c5cc9db2702cf04b6c.yml -openapi_spec_hash: 71974f160905d65b2dc8ad871c994c98 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-eb5286aea707c617b23a4a5f5a7720af2bbb3f76b4b7354faa466489073b9bb1.yml +openapi_spec_hash: fbb7a3c05db6c108b979807731752dd6 config_hash: 7f48d078645cb8331328bcd6f6ab3281 diff --git a/LICENSE b/LICENSE index d93da50..295331e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Brand Dev + Copyright 2026 Brand Dev Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiQueryParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiQueryParams.kt index 730c424..3c2f1a5 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiQueryParams.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiQueryParams.kt @@ -24,8 +24,8 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull /** - * Beta feature: 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. + * 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. */ class BrandAiQueryParams private constructor( diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandFontsParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandFontsParams.kt index 4a1fe3e..026053d 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandFontsParams.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandFontsParams.kt @@ -11,8 +11,8 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull /** - * Beta feature: Extract font information from a brand's website including font families, usage - * statistics, fallbacks, and element/word counts. + * Extract font information from a brand's website including font families, usage statistics, + * fallbacks, and element/word counts. */ class BrandFontsParams private constructor( diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandScreenshotParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandScreenshotParams.kt index 073a150..059927e 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandScreenshotParams.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandScreenshotParams.kt @@ -15,10 +15,9 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull /** - * Beta feature: Capture a screenshot of a website. Supports both viewport (standard browser view) - * and full-page screenshots. Can also screenshot specific page types (login, pricing, etc.) by - * using heuristics to find the appropriate URL. Returns a URL to the uploaded screenshot image - * hosted on our CDN. + * Capture a screenshot of a website. Supports both viewport (standard browser view) and full-page + * screenshots. Can also screenshot specific page types (login, pricing, etc.) by using heuristics + * to find the appropriate URL. Returns a URL to the uploaded screenshot image hosted on our CDN. */ class BrandScreenshotParams private constructor( diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandStyleguideParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandStyleguideParams.kt index 8745898..4ff729d 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandStyleguideParams.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandStyleguideParams.kt @@ -15,8 +15,8 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull /** - * Beta feature: Automatically extract comprehensive design system information from a brand's - * website including colors, typography, spacing, shadows, and UI components. + * Automatically extract comprehensive design system information from a brand's website including + * colors, typography, spacing, shadows, and UI components. */ class BrandStyleguideParams private constructor( 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 2a28c61..266e501 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 @@ -69,8 +69,8 @@ interface BrandServiceAsync { retrieve(BrandRetrieveParams.none(), requestOptions) /** - * Beta feature: 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. + * 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. */ fun aiQuery(params: BrandAiQueryParams): CompletableFuture = aiQuery(params, RequestOptions.none()) @@ -82,8 +82,8 @@ interface BrandServiceAsync { ): CompletableFuture /** - * Beta feature: Extract font information from a brand's website including font families, usage - * statistics, fallbacks, and element/word counts. + * Extract font information from a brand's website including font families, usage statistics, + * fallbacks, and element/word counts. */ fun fonts(params: BrandFontsParams): CompletableFuture = fonts(params, RequestOptions.none()) @@ -231,10 +231,10 @@ interface BrandServiceAsync { ): CompletableFuture /** - * Beta feature: Capture a screenshot of a website. Supports both viewport (standard browser - * view) and full-page screenshots. Can also screenshot specific page types (login, pricing, - * etc.) by using heuristics to find the appropriate URL. Returns a URL to the uploaded - * screenshot image hosted on our CDN. + * Capture a screenshot of a website. Supports both viewport (standard browser view) and + * full-page screenshots. Can also screenshot specific page types (login, pricing, etc.) by + * using heuristics to find the appropriate URL. Returns a URL to the uploaded screenshot image + * hosted on our CDN. */ fun screenshot(params: BrandScreenshotParams): CompletableFuture = screenshot(params, RequestOptions.none()) @@ -246,8 +246,8 @@ interface BrandServiceAsync { ): CompletableFuture /** - * Beta feature: Automatically extract comprehensive design system information from a brand's - * website including colors, typography, spacing, shadows, and UI components. + * Automatically extract comprehensive design system information from a brand's website + * including colors, typography, spacing, shadows, and UI components. */ fun styleguide(params: BrandStyleguideParams): CompletableFuture = styleguide(params, RequestOptions.none()) 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 c2880af..0683fe4 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 @@ -68,8 +68,8 @@ interface BrandService { retrieve(BrandRetrieveParams.none(), requestOptions) /** - * Beta feature: 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. + * 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. */ fun aiQuery(params: BrandAiQueryParams): BrandAiQueryResponse = aiQuery(params, RequestOptions.none()) @@ -81,8 +81,8 @@ interface BrandService { ): BrandAiQueryResponse /** - * Beta feature: Extract font information from a brand's website including font families, usage - * statistics, fallbacks, and element/word counts. + * Extract font information from a brand's website including font families, usage statistics, + * fallbacks, and element/word counts. */ fun fonts(params: BrandFontsParams): BrandFontsResponse = fonts(params, RequestOptions.none()) @@ -215,10 +215,10 @@ interface BrandService { ): BrandRetrieveSimplifiedResponse /** - * Beta feature: Capture a screenshot of a website. Supports both viewport (standard browser - * view) and full-page screenshots. Can also screenshot specific page types (login, pricing, - * etc.) by using heuristics to find the appropriate URL. Returns a URL to the uploaded - * screenshot image hosted on our CDN. + * Capture a screenshot of a website. Supports both viewport (standard browser view) and + * full-page screenshots. Can also screenshot specific page types (login, pricing, etc.) by + * using heuristics to find the appropriate URL. Returns a URL to the uploaded screenshot image + * hosted on our CDN. */ fun screenshot(params: BrandScreenshotParams): BrandScreenshotResponse = screenshot(params, RequestOptions.none()) @@ -230,8 +230,8 @@ interface BrandService { ): BrandScreenshotResponse /** - * Beta feature: Automatically extract comprehensive design system information from a brand's - * website including colors, typography, spacing, shadows, and UI components. + * Automatically extract comprehensive design system information from a brand's website + * including colors, typography, spacing, shadows, and UI components. */ fun styleguide(params: BrandStyleguideParams): BrandStyleguideResponse = styleguide(params, RequestOptions.none()) From e393ac81cfda876ea2c4d4734901477bb610a5d2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:56:15 +0000 Subject: [PATCH 3/7] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 078c968..a7e2596 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-eb5286aea707c617b23a4a5f5a7720af2bbb3f76b4b7354faa466489073b9bb1.yml -openapi_spec_hash: fbb7a3c05db6c108b979807731752dd6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-317a4cbdb3460421558cf44d04d0f17a8f2599ccec2bd697f407ccdb1d4bfc47.yml +openapi_spec_hash: e678a71b83ee0a2ac973bebd6e26f823 config_hash: 7f48d078645cb8331328bcd6f6ab3281 From d86150d8aed47491c65f9116001af96de350f76d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:35:42 +0000 Subject: [PATCH 4/7] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a7e2596..5bf29b9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-317a4cbdb3460421558cf44d04d0f17a8f2599ccec2bd697f407ccdb1d4bfc47.yml -openapi_spec_hash: e678a71b83ee0a2ac973bebd6e26f823 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-c45c9faa82a7f8b0b0221deab17a0f6cc6b096906e192aea9ef535e17667008f.yml +openapi_spec_hash: f3330c699ed3a55e48cdeecfce379e7b config_hash: 7f48d078645cb8331328bcd6f6ab3281 From 10c16f9eeea050339c40987512567f1f36842c6f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:36:53 +0000 Subject: [PATCH 5/7] feat(api): manual updates --- .stats.yml | 4 +- README.md | 7 +- .../api/models/brand/BrandAiProductsParams.kt | 568 ++++++++ .../models/brand/BrandAiProductsResponse.kt | 1174 +++++++++++++++++ .../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/BrandAiProductsParamsTest.kt | 35 + .../brand/BrandAiProductsResponseTest.kt | 85 ++ .../services/async/BrandServiceAsyncTest.kt | 24 + .../api/services/blocking/BrandServiceTest.kt | 23 + 12 files changed, 2053 insertions(+), 6 deletions(-) create mode 100644 brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt create mode 100644 brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt create mode 100644 brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt create mode 100644 brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt diff --git a/.stats.yml b/.stats.yml index 5bf29b9..4896159 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 14 +configured_endpoints: 15 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-c45c9faa82a7f8b0b0221deab17a0f6cc6b096906e192aea9ef535e17667008f.yml openapi_spec_hash: f3330c699ed3a55e48cdeecfce379e7b -config_hash: 7f48d078645cb8331328bcd6f6ab3281 +config_hash: 6f10592c7d0c3bafefc1271472283217 diff --git a/README.md b/README.md index 24b84f2..498dfd5 100644 --- a/README.md +++ b/README.md @@ -491,12 +491,11 @@ To forcibly omit a required parameter or property, pass [`JsonMissing`](brand-de ```java import com.branddev.api.core.JsonMissing; -import com.branddev.api.models.brand.BrandAiQueryParams; +import com.branddev.api.models.brand.BrandAiProductsParams; import com.branddev.api.models.brand.BrandRetrieveParams; -BrandRetrieveParams params = BrandAiQueryParams.builder() - .domain("domain") - .dataToExtract(JsonMissing.of()) +BrandRetrieveParams params = BrandAiProductsParams.builder() + .domain(JsonMissing.of()) .build(); ``` diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt new file mode 100644 index 0000000..b12f8cc --- /dev/null +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt @@ -0,0 +1,568 @@ +// 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: Use AI to extract product information from a brand's website. The AI will analyze + * the website and return a list of products with details such as name, description, pricing, + * features, and more. + */ +class BrandAiProductsParams +private constructor( + private val body: Body, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + /** + * The domain name to analyze + * + * @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 domain(): String = body.domain() + + /** + * Maximum number of products to extract. + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun maxProducts(): Optional = body.maxProducts() + + /** + * Optional timeout in milliseconds for the request. If the request takes longer than this + * value, it will be aborted with a 408 status code. 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 [domain]. + * + * Unlike [domain], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _domain(): JsonField = body._domain() + + /** + * Returns the raw JSON value of [maxProducts]. + * + * Unlike [maxProducts], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _maxProducts(): JsonField = body._maxProducts() + + /** + * 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 [BrandAiProductsParams]. + * + * The following fields are required: + * ```java + * .domain() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BrandAiProductsParams]. */ + 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(brandAiProductsParams: BrandAiProductsParams) = apply { + body = brandAiProductsParams.body.toBuilder() + additionalHeaders = brandAiProductsParams.additionalHeaders.toBuilder() + additionalQueryParams = brandAiProductsParams.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: + * - [domain] + * - [maxProducts] + * - [timeoutMs] + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + /** The domain name to analyze */ + fun domain(domain: String) = apply { body.domain(domain) } + + /** + * Sets [Builder.domain] to an arbitrary JSON value. + * + * You should usually call [Builder.domain] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun domain(domain: JsonField) = apply { body.domain(domain) } + + /** Maximum number of products to extract. */ + fun maxProducts(maxProducts: Long) = apply { body.maxProducts(maxProducts) } + + /** + * Sets [Builder.maxProducts] to an arbitrary JSON value. + * + * You should usually call [Builder.maxProducts] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun maxProducts(maxProducts: JsonField) = apply { body.maxProducts(maxProducts) } + + /** + * Optional timeout in milliseconds for the request. If the request takes longer than this + * value, it will be aborted with a 408 status code. 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 [BrandAiProductsParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .domain() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): BrandAiProductsParams = + BrandAiProductsParams( + 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 domain: JsonField, + private val maxProducts: JsonField, + private val timeoutMs: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("domain") @ExcludeMissing domain: JsonField = JsonMissing.of(), + @JsonProperty("maxProducts") + @ExcludeMissing + maxProducts: JsonField = JsonMissing.of(), + @JsonProperty("timeoutMS") @ExcludeMissing timeoutMs: JsonField = JsonMissing.of(), + ) : this(domain, maxProducts, timeoutMs, mutableMapOf()) + + /** + * The domain name to analyze + * + * @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 domain(): String = domain.getRequired("domain") + + /** + * Maximum number of products to extract. + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun maxProducts(): Optional = maxProducts.getOptional("maxProducts") + + /** + * Optional timeout in milliseconds for the request. If the request takes longer than this + * value, it will be aborted with a 408 status code. 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 [domain]. + * + * Unlike [domain], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("domain") @ExcludeMissing fun _domain(): JsonField = domain + + /** + * Returns the raw JSON value of [maxProducts]. + * + * Unlike [maxProducts], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("maxProducts") + @ExcludeMissing + fun _maxProducts(): JsonField = maxProducts + + /** + * 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 + * .domain() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var domain: JsonField? = null + private var maxProducts: JsonField = JsonMissing.of() + private var timeoutMs: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + domain = body.domain + maxProducts = body.maxProducts + timeoutMs = body.timeoutMs + additionalProperties = body.additionalProperties.toMutableMap() + } + + /** The domain name to analyze */ + fun domain(domain: String) = domain(JsonField.of(domain)) + + /** + * Sets [Builder.domain] to an arbitrary JSON value. + * + * You should usually call [Builder.domain] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun domain(domain: JsonField) = apply { this.domain = domain } + + /** Maximum number of products to extract. */ + fun maxProducts(maxProducts: Long) = maxProducts(JsonField.of(maxProducts)) + + /** + * Sets [Builder.maxProducts] to an arbitrary JSON value. + * + * You should usually call [Builder.maxProducts] with a well-typed [Long] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun maxProducts(maxProducts: JsonField) = apply { this.maxProducts = maxProducts } + + /** + * Optional timeout in milliseconds for the request. If the request takes longer than + * this value, it will be aborted with a 408 status code. 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 + * .domain() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): Body = + Body( + checkRequired("domain", domain), + maxProducts, + timeoutMs, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): Body = apply { + if (validated) { + return@apply + } + + domain() + maxProducts() + 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 (domain.asKnown().isPresent) 1 else 0) + + (if (maxProducts.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 && + domain == other.domain && + maxProducts == other.maxProducts && + timeoutMs == other.timeoutMs && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(domain, maxProducts, timeoutMs, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{domain=$domain, maxProducts=$maxProducts, timeoutMs=$timeoutMs, additionalProperties=$additionalProperties}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is BrandAiProductsParams && + body == other.body && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams) + + override fun toString() = + "BrandAiProductsParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} 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 new file mode 100644 index 0000000..55f3a3e --- /dev/null +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsResponse.kt @@ -0,0 +1,1174 @@ +// 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 BrandAiProductsResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val products: JsonField>, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("products") + @ExcludeMissing + products: JsonField> = JsonMissing.of() + ) : this(products, mutableMapOf()) + + /** + * Array of products extracted from the website + * + * @throws BrandDevInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun products(): Optional> = products.getOptional("products") + + /** + * Returns the raw JSON value of [products]. + * + * Unlike [products], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("products") @ExcludeMissing fun _products(): JsonField> = products + + @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 [BrandAiProductsResponse]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [BrandAiProductsResponse]. */ + class Builder internal constructor() { + + private var products: JsonField>? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(brandAiProductsResponse: BrandAiProductsResponse) = apply { + products = brandAiProductsResponse.products.map { it.toMutableList() } + additionalProperties = brandAiProductsResponse.additionalProperties.toMutableMap() + } + + /** Array of products extracted from the website */ + fun products(products: List) = products(JsonField.of(products)) + + /** + * Sets [Builder.products] to an arbitrary JSON value. + * + * You should usually call [Builder.products] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun products(products: JsonField>) = apply { + this.products = products.map { it.toMutableList() } + } + + /** + * Adds a single [Product] to [products]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addProduct(product: Product) = apply { + products = + (products ?: JsonField.of(mutableListOf())).also { + checkKnown("products", it).add(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 [BrandAiProductsResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): BrandAiProductsResponse = + BrandAiProductsResponse( + (products ?: JsonMissing.of()).map { it.toImmutable() }, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + fun validate(): BrandAiProductsResponse = apply { + if (validated) { + return@apply + } + + products().ifPresent { it.forEach { 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 = + (products.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + + 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 BrandAiProductsResponse && + products == other.products && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(products, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "BrandAiProductsResponse{products=$products, 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 266e501..f65c3e1 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.BrandAiProductsParams +import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandAiQueryResponse import com.branddev.api.models.brand.BrandFontsParams @@ -68,6 +70,20 @@ interface BrandServiceAsync { fun retrieve(requestOptions: RequestOptions): CompletableFuture = retrieve(BrandRetrieveParams.none(), requestOptions) + /** + * Beta feature: Use AI to extract product information from a brand's website. The AI will + * analyze the website and return a list of products with details such as name, description, + * pricing, features, and more. + */ + fun aiProducts(params: BrandAiProductsParams): CompletableFuture = + aiProducts(params, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + /** * 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. @@ -295,6 +311,21 @@ interface BrandServiceAsync { ): CompletableFuture> = retrieve(BrandRetrieveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `post /brand/ai/products`, but is otherwise the same as + * [BrandServiceAsync.aiProducts]. + */ + fun aiProducts( + params: BrandAiProductsParams + ): CompletableFuture> = + aiProducts(params, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + /** * 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/async/BrandServiceAsyncImpl.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/services/async/BrandServiceAsyncImpl.kt index f569549..b041b94 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.BrandAiProductsParams +import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandAiQueryResponse import com.branddev.api.models.brand.BrandFontsParams @@ -65,6 +67,13 @@ class BrandServiceAsyncImpl internal constructor(private val clientOptions: Clie // get /brand/retrieve withRawResponse().retrieve(params, requestOptions).thenApply { it.parse() } + override fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // post /brand/ai/products + withRawResponse().aiProducts(params, requestOptions).thenApply { it.parse() } + override fun aiQuery( params: BrandAiQueryParams, requestOptions: RequestOptions, @@ -199,6 +208,37 @@ class BrandServiceAsyncImpl internal constructor(private val clientOptions: Clie } } + private val aiProductsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("brand", "ai", "products") + .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 { aiProductsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } + private val aiQueryHandler: 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 0683fe4..403fd63 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.BrandAiProductsParams +import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandAiQueryResponse import com.branddev.api.models.brand.BrandFontsParams @@ -67,6 +69,20 @@ interface BrandService { fun retrieve(requestOptions: RequestOptions): BrandRetrieveResponse = retrieve(BrandRetrieveParams.none(), requestOptions) + /** + * Beta feature: Use AI to extract product information from a brand's website. The AI will + * analyze the website and return a list of products with details such as name, description, + * pricing, features, and more. + */ + fun aiProducts(params: BrandAiProductsParams): BrandAiProductsResponse = + aiProducts(params, RequestOptions.none()) + + /** @see aiProducts */ + fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): BrandAiProductsResponse + /** * 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. @@ -278,6 +294,21 @@ interface BrandService { fun retrieve(requestOptions: RequestOptions): HttpResponseFor = retrieve(BrandRetrieveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `post /brand/ai/products`, but is otherwise the same as + * [BrandService.aiProducts]. + */ + @MustBeClosed + fun aiProducts(params: BrandAiProductsParams): HttpResponseFor = + aiProducts(params, RequestOptions.none()) + + /** @see aiProducts */ + @MustBeClosed + fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + /** * 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/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 5f4e4a1..7881de3 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.BrandAiProductsParams +import com.branddev.api.models.brand.BrandAiProductsResponse import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandAiQueryResponse import com.branddev.api.models.brand.BrandFontsParams @@ -64,6 +66,13 @@ class BrandServiceImpl internal constructor(private val clientOptions: ClientOpt // get /brand/retrieve withRawResponse().retrieve(params, requestOptions).parse() + override fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions, + ): BrandAiProductsResponse = + // post /brand/ai/products + withRawResponse().aiProducts(params, requestOptions).parse() + override fun aiQuery( params: BrandAiQueryParams, requestOptions: RequestOptions, @@ -195,6 +204,34 @@ class BrandServiceImpl internal constructor(private val clientOptions: ClientOpt } } + private val aiProductsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun aiProducts( + params: BrandAiProductsParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("brand", "ai", "products") + .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 { aiProductsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + private val aiQueryHandler: Handler = jsonHandler(clientOptions.jsonMapper) 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 new file mode 100644 index 0000000..7b7169f --- /dev/null +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsParamsTest.kt @@ -0,0 +1,35 @@ +// 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 BrandAiProductsParamsTest { + + @Test + fun create() { + BrandAiProductsParams.builder().domain("domain").maxProducts(1L).timeoutMs(1L).build() + } + + @Test + fun body() { + val params = + BrandAiProductsParams.builder().domain("domain").maxProducts(1L).timeoutMs(1L).build() + + val body = params._body() + + assertThat(body.domain()).isEqualTo("domain") + assertThat(body.maxProducts()).contains(1L) + assertThat(body.timeoutMs()).contains(1L) + } + + @Test + fun bodyWithoutOptionalFields() { + val params = BrandAiProductsParams.builder().domain("domain").build() + + val body = params._body() + + assertThat(body.domain()).isEqualTo("domain") + } +} 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 new file mode 100644 index 0000000..a8453f7 --- /dev/null +++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandAiProductsResponseTest.kt @@ -0,0 +1,85 @@ +// 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 kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class BrandAiProductsResponseTest { + + @Test + fun create() { + val brandAiProductsResponse = + BrandAiProductsResponse.builder() + .addProduct( + BrandAiProductsResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductsResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductsResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + .build() + + assertThat(brandAiProductsResponse.products().getOrNull()) + .containsExactly( + BrandAiProductsResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductsResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductsResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val brandAiProductsResponse = + BrandAiProductsResponse.builder() + .addProduct( + BrandAiProductsResponse.Product.builder() + .description("description") + .addFeature("string") + .name("name") + .addTag("string") + .addTargetAudience("string") + .billingFrequency(BrandAiProductsResponse.Product.BillingFrequency.MONTHLY) + .category("category") + .currency("currency") + .imageUrl("image_url") + .price(0.0) + .pricingModel(BrandAiProductsResponse.Product.PricingModel.PER_SEAT) + .url("url") + .build() + ) + .build() + + val roundtrippedBrandAiProductsResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(brandAiProductsResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedBrandAiProductsResponse).isEqualTo(brandAiProductsResponse) + } +} 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 8c04e1f..51af267 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.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandFontsParams import com.branddev.api.models.brand.BrandIdentifyFromTransactionParams @@ -50,6 +51,29 @@ internal class BrandServiceAsyncTest { brand.validate() } + @Disabled("Prism tests are disabled") + @Test + fun aiProducts() { + val client = + BrandDevOkHttpClientAsync.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My API Key") + .build() + val brandServiceAsync = client.brand() + + val responseFuture = + brandServiceAsync.aiProducts( + BrandAiProductsParams.builder() + .domain("domain") + .maxProducts(1L) + .timeoutMs(1L) + .build() + ) + + val response = responseFuture.get() + response.validate() + } + @Disabled("Prism tests are disabled") @Test fun aiQuery() { 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 baa9f79..2d696f9 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.BrandAiProductsParams import com.branddev.api.models.brand.BrandAiQueryParams import com.branddev.api.models.brand.BrandFontsParams import com.branddev.api.models.brand.BrandIdentifyFromTransactionParams @@ -49,6 +50,28 @@ internal class BrandServiceTest { brand.validate() } + @Disabled("Prism tests are disabled") + @Test + fun aiProducts() { + val client = + BrandDevOkHttpClient.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My API Key") + .build() + val brandService = client.brand() + + val response = + brandService.aiProducts( + BrandAiProductsParams.builder() + .domain("domain") + .maxProducts(1L) + .timeoutMs(1L) + .build() + ) + + response.validate() + } + @Disabled("Prism tests are disabled") @Test fun aiQuery() { From 6e0e1d4fa0db226f953467c4ab83ee8f46203609 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:46:28 +0000 Subject: [PATCH 6/7] feat(api): api update --- .stats.yml | 4 ++-- .../com/branddev/api/models/brand/BrandAiProductsParams.kt | 4 ++-- .../com/branddev/api/services/async/BrandServiceAsync.kt | 6 +++--- .../com/branddev/api/services/blocking/BrandService.kt | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4896159..ccc6aee 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-c45c9faa82a7f8b0b0221deab17a0f6cc6b096906e192aea9ef535e17667008f.yml -openapi_spec_hash: f3330c699ed3a55e48cdeecfce379e7b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-863ddc13e032497459a639cf02a16349831dda7e39557cbd5ce33da34d086b02.yml +openapi_spec_hash: f972aac9618fe8df340d96344b3d0578 config_hash: 6f10592c7d0c3bafefc1271472283217 diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt index b12f8cc..478cf30 100644 --- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt +++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandAiProductsParams.kt @@ -20,8 +20,8 @@ import java.util.Objects import java.util.Optional /** - * Beta feature: Use AI to extract product information from a brand's website. The AI will analyze - * the website and return a list of products with details such as name, description, pricing, + * 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, * features, and more. */ class BrandAiProductsParams 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 f65c3e1..37db106 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 @@ -71,9 +71,9 @@ interface BrandServiceAsync { retrieve(BrandRetrieveParams.none(), requestOptions) /** - * Beta feature: Use AI to extract product information from a brand's website. The AI will - * analyze the website and return a list of products with details such as name, description, - * pricing, features, and more. + * 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, + * features, and more. */ fun aiProducts(params: BrandAiProductsParams): CompletableFuture = aiProducts(params, RequestOptions.none()) 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 403fd63..a4ec1a5 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 @@ -70,9 +70,9 @@ interface BrandService { retrieve(BrandRetrieveParams.none(), requestOptions) /** - * Beta feature: Use AI to extract product information from a brand's website. The AI will - * analyze the website and return a list of products with details such as name, description, - * pricing, features, and more. + * 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, + * features, and more. */ fun aiProducts(params: BrandAiProductsParams): BrandAiProductsResponse = aiProducts(params, RequestOptions.none()) From 60fa9d18f995c5399e61248f65d10e396f8b2452 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:46:53 +0000 Subject: [PATCH 7/7] release: 0.1.0-alpha.26 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ README.md | 10 +++++----- build.gradle.kts | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3188ced..315f7d3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.25" + ".": "0.1.0-alpha.26" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 27af7ac..b3a2053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.1.0-alpha.26 (2026-01-05) + +Full Changelog: [v0.1.0-alpha.25...v0.1.0-alpha.26](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.25...v0.1.0-alpha.26) + +### Features + +* **api:** api update ([6e0e1d4](https://github.com/brand-dot-dev/java-sdk/commit/6e0e1d4fa0db226f953467c4ab83ee8f46203609)) +* **api:** api update ([210aa14](https://github.com/brand-dot-dev/java-sdk/commit/210aa1435c09e44ab9b035f518b40bdb759936c4)) +* **api:** manual updates ([10c16f9](https://github.com/brand-dot-dev/java-sdk/commit/10c16f9eeea050339c40987512567f1f36842c6f)) + ## 0.1.0-alpha.25 (2025-12-22) Full Changelog: [v0.1.0-alpha.24...v0.1.0-alpha.25](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.24...v0.1.0-alpha.25) diff --git a/README.md b/README.md index 498dfd5..ee3a67c 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.25) -[![javadoc](https://javadoc.io/badge2/com.branddev.api/brand-dev-java/0.1.0-alpha.25/javadoc.svg)](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.25) +[![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.26) +[![javadoc](https://javadoc.io/badge2/com.branddev.api/brand-dev-java/0.1.0-alpha.26/javadoc.svg)](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.26) @@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/). -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.25). +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.26). @@ -24,7 +24,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.25") +implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.26") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.25") com.branddev.api brand-dev-java - 0.1.0-alpha.25 + 0.1.0-alpha.26 ``` diff --git a/build.gradle.kts b/build.gradle.kts index f60bb3f..7086b78 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.branddev.api" - version = "0.1.0-alpha.25" // x-release-please-version + version = "0.1.0-alpha.26" // x-release-please-version } subprojects {